mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-16 01:22:02 +00:00
feat(core): new design of PIN dialogs
[no changelog]
This commit is contained in:
parent
10449759bf
commit
672d6b7d13
@ -85,6 +85,7 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_max_len;
|
MP_QSTR_max_len;
|
||||||
MP_QSTR_max_rounds;
|
MP_QSTR_max_rounds;
|
||||||
MP_QSTR_min_count;
|
MP_QSTR_min_count;
|
||||||
|
MP_QSTR_multiple_pages_texts;
|
||||||
MP_QSTR_notification;
|
MP_QSTR_notification;
|
||||||
MP_QSTR_notification_level;
|
MP_QSTR_notification_level;
|
||||||
MP_QSTR_page_count;
|
MP_QSTR_page_count;
|
||||||
|
@ -617,6 +617,15 @@ where
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cancel cross on left and right arrow facing down.
|
||||||
|
pub fn up_arrow_none_arrow_wide() -> Self {
|
||||||
|
Self::new(
|
||||||
|
Some(ButtonDetails::up_arrow_icon()),
|
||||||
|
None,
|
||||||
|
Some(ButtonDetails::down_arrow_icon_wide()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Cancel cross on left and right arrow facing down.
|
/// Cancel cross on left and right arrow facing down.
|
||||||
pub fn cancel_none_arrow_down() -> Self {
|
pub fn cancel_none_arrow_down() -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
@ -756,6 +765,11 @@ impl ButtonActions {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only confirming with middle
|
||||||
|
pub fn none_confirm_none() -> Self {
|
||||||
|
Self::new(None, Some(ButtonAction::Confirm), None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Going to last page with left, to the next page with right
|
/// Going to last page with left, to the next page with right
|
||||||
pub fn last_none_next() -> Self {
|
pub fn last_none_next() -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
@ -799,6 +813,11 @@ impl ButtonActions {
|
|||||||
Self::new(None, None, Some(ButtonAction::NextPage))
|
Self::new(None, None, Some(ButtonAction::NextPage))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only going to the next page with middle
|
||||||
|
pub fn none_next_none() -> Self {
|
||||||
|
Self::new(None, Some(ButtonAction::NextPage), None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Only going to the prev page with left
|
/// Only going to the prev page with left
|
||||||
pub fn prev_none_none() -> Self {
|
pub fn prev_none_none() -> Self {
|
||||||
Self::new(Some(ButtonAction::PrevPage), None, None)
|
Self::new(Some(ButtonAction::PrevPage), None, None)
|
||||||
|
@ -16,6 +16,8 @@ pub struct ChangingTextLine<T> {
|
|||||||
font: Font,
|
font: Font,
|
||||||
/// Whether to show the text. Can be disabled.
|
/// Whether to show the text. Can be disabled.
|
||||||
show_content: bool,
|
show_content: bool,
|
||||||
|
/// What to show in front of the text if it doesn't fit.
|
||||||
|
ellipsis: &'static str,
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,6 +31,7 @@ where
|
|||||||
text,
|
text,
|
||||||
font,
|
font,
|
||||||
show_content: true,
|
show_content: true,
|
||||||
|
ellipsis: "...",
|
||||||
alignment,
|
alignment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,6 +44,12 @@ where
|
|||||||
Self::new(text, Font::BOLD, Alignment::Center)
|
Self::new(text, Font::BOLD, Alignment::Center)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Not showing ellipsis at the beginning of longer texts.
|
||||||
|
pub fn without_ellipsis(mut self) -> Self {
|
||||||
|
self.ellipsis = "";
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the text to be displayed in the line.
|
/// Update the text to be displayed in the line.
|
||||||
pub fn update_text(&mut self, text: T) {
|
pub fn update_text(&mut self, text: T) {
|
||||||
self.text = text;
|
self.text = text;
|
||||||
@ -93,7 +102,7 @@ where
|
|||||||
fn paint_long_content_with_ellipsis(&self) {
|
fn paint_long_content_with_ellipsis(&self) {
|
||||||
let text_to_display = long_line_content_with_ellipsis(
|
let text_to_display = long_line_content_with_ellipsis(
|
||||||
self.text.as_ref(),
|
self.text.as_ref(),
|
||||||
"...",
|
self.ellipsis,
|
||||||
self.font,
|
self.font,
|
||||||
self.pad.area.width(),
|
self.pad.area.width(),
|
||||||
);
|
);
|
||||||
|
@ -23,6 +23,7 @@ enum PinAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MAX_PIN_LENGTH: usize = 50;
|
const MAX_PIN_LENGTH: usize = 50;
|
||||||
|
const EMPTY_PIN_STR: &str = "_";
|
||||||
|
|
||||||
const CHOICE_LENGTH: usize = 13;
|
const CHOICE_LENGTH: usize = 13;
|
||||||
const NUMBER_START_INDEX: usize = 3;
|
const NUMBER_START_INDEX: usize = 3;
|
||||||
@ -80,9 +81,9 @@ impl<T: StringType + Clone> ChoiceFactory<T> for ChoiceFactoryPIN {
|
|||||||
pub struct PinEntry<T: StringType + Clone> {
|
pub struct PinEntry<T: StringType + Clone> {
|
||||||
choice_page: ChoicePage<ChoiceFactoryPIN, T, PinAction>,
|
choice_page: ChoicePage<ChoiceFactoryPIN, T, PinAction>,
|
||||||
pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
|
pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
|
||||||
subprompt_line: Child<ChangingTextLine<T>>,
|
subprompt: T,
|
||||||
prompt: T,
|
|
||||||
show_real_pin: bool,
|
show_real_pin: bool,
|
||||||
|
show_last_digit: bool,
|
||||||
textbox: TextBox<MAX_PIN_LENGTH>,
|
textbox: TextBox<MAX_PIN_LENGTH>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,61 +91,62 @@ impl<T> PinEntry<T>
|
|||||||
where
|
where
|
||||||
T: StringType + Clone,
|
T: StringType + Clone,
|
||||||
{
|
{
|
||||||
pub fn new(prompt: T, subprompt: T) -> Self {
|
pub fn new(subprompt: T) -> Self {
|
||||||
let choices = ChoiceFactoryPIN;
|
let pin_line_content = if !subprompt.as_ref().is_empty() {
|
||||||
|
String::from(subprompt.as_ref())
|
||||||
|
} else {
|
||||||
|
String::from(EMPTY_PIN_STR)
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
// Starting at a random digit.
|
// Starting at a random digit.
|
||||||
choice_page: ChoicePage::new(choices)
|
choice_page: ChoicePage::new(ChoiceFactoryPIN)
|
||||||
.with_initial_page_counter(get_random_digit_position())
|
.with_initial_page_counter(get_random_digit_position())
|
||||||
.with_carousel(true),
|
.with_carousel(true),
|
||||||
pin_line: Child::new(ChangingTextLine::center_bold(String::from(prompt.as_ref()))),
|
pin_line: Child::new(
|
||||||
subprompt_line: Child::new(ChangingTextLine::center_mono(subprompt)),
|
ChangingTextLine::center_bold(pin_line_content).without_ellipsis(),
|
||||||
prompt,
|
),
|
||||||
|
subprompt,
|
||||||
show_real_pin: false,
|
show_real_pin: false,
|
||||||
|
show_last_digit: false,
|
||||||
textbox: TextBox::empty(),
|
textbox: TextBox::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs overall update of the screen.
|
/// Performs overall update of the screen.
|
||||||
fn update(&mut self, ctx: &mut EventCtx) {
|
fn update(&mut self, ctx: &mut EventCtx) {
|
||||||
self.update_header_info(ctx);
|
self.update_pin_line(ctx);
|
||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the header information - (sub)prompt and visible PIN.
|
/// Show updated content in the changing line.
|
||||||
/// If PIN is empty, showing prompt in `pin_line` and sub-prompt in the
|
/// Many possibilities, according to the PIN state.
|
||||||
/// `subprompt_line`. Otherwise disabling the `subprompt_line` and showing
|
fn update_pin_line(&mut self, ctx: &mut EventCtx) {
|
||||||
/// the PIN - either in real numbers or masked in asterisks.
|
let pin_line_text = if self.is_empty() && !self.subprompt.as_ref().is_empty() {
|
||||||
fn update_header_info(&mut self, ctx: &mut EventCtx) {
|
String::from(self.subprompt.as_ref())
|
||||||
let show_prompts = self.is_empty();
|
} else if self.is_empty() {
|
||||||
|
String::from(EMPTY_PIN_STR)
|
||||||
let text = if show_prompts {
|
|
||||||
String::from(self.prompt.as_ref())
|
|
||||||
} else if self.show_real_pin {
|
} else if self.show_real_pin {
|
||||||
String::from(self.pin())
|
String::from(self.pin())
|
||||||
} else {
|
} else {
|
||||||
// Showing asterisks and the last digit.
|
// Showing asterisks and possibly the last digit.
|
||||||
let mut dots: String<MAX_PIN_LENGTH> = String::new();
|
let mut dots: String<MAX_PIN_LENGTH> = String::new();
|
||||||
for _ in 0..self.textbox.len() - 1 {
|
for _ in 0..self.textbox.len() - 1 {
|
||||||
unwrap!(dots.push('*'));
|
unwrap!(dots.push('*'));
|
||||||
}
|
}
|
||||||
let last_char = unwrap!(self.textbox.content().chars().last());
|
let last_char = if self.show_last_digit {
|
||||||
|
unwrap!(self.textbox.content().chars().last())
|
||||||
|
} else {
|
||||||
|
'*'
|
||||||
|
};
|
||||||
unwrap!(dots.push(last_char));
|
unwrap!(dots.push(last_char));
|
||||||
dots
|
dots
|
||||||
};
|
};
|
||||||
|
|
||||||
// Force repaint of the whole header.
|
|
||||||
// Putting the current text into the PIN line.
|
|
||||||
self.pin_line.mutate(ctx, |ctx, pin_line| {
|
self.pin_line.mutate(ctx, |ctx, pin_line| {
|
||||||
pin_line.update_text(text);
|
pin_line.update_text(pin_line_text);
|
||||||
pin_line.request_complete_repaint(ctx);
|
pin_line.request_complete_repaint(ctx);
|
||||||
});
|
});
|
||||||
// Showing subprompt only conditionally.
|
|
||||||
self.subprompt_line.mutate(ctx, |ctx, subprompt_line| {
|
|
||||||
subprompt_line.show_or_not(show_prompts);
|
|
||||||
subprompt_line.request_complete_repaint(ctx);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pin(&self) -> &str {
|
pub fn pin(&self) -> &str {
|
||||||
@ -168,21 +170,23 @@ where
|
|||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
let pin_height = self.pin_line.inner().needed_height();
|
let pin_height = self.pin_line.inner().needed_height();
|
||||||
let subtitle_height = self.subprompt_line.inner().needed_height();
|
let (pin_area, choice_area) = bounds.split_top(pin_height);
|
||||||
let (title_area, subtitle_and_choice_area) = bounds.split_top(pin_height);
|
self.pin_line.place(pin_area);
|
||||||
let (subtitle_area, choice_area) = subtitle_and_choice_area.split_top(subtitle_height);
|
|
||||||
self.pin_line.place(title_area);
|
|
||||||
self.subprompt_line.place(subtitle_area);
|
|
||||||
self.choice_page.place(choice_area);
|
self.choice_page.place(choice_area);
|
||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
// Any event when showing real PIN should hide it
|
// Any event when showing real PIN should hide it
|
||||||
|
// Same with showing last digit
|
||||||
if self.show_real_pin {
|
if self.show_real_pin {
|
||||||
self.show_real_pin = false;
|
self.show_real_pin = false;
|
||||||
self.update(ctx)
|
self.update(ctx)
|
||||||
}
|
}
|
||||||
|
if self.show_last_digit {
|
||||||
|
self.show_last_digit = false;
|
||||||
|
self.update(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
match self.choice_page.event(ctx, event) {
|
match self.choice_page.event(ctx, event) {
|
||||||
Some(PinAction::Delete) => {
|
Some(PinAction::Delete) => {
|
||||||
@ -201,6 +205,7 @@ where
|
|||||||
// Choosing random digit to be shown next
|
// Choosing random digit to be shown next
|
||||||
self.choice_page
|
self.choice_page
|
||||||
.set_page_counter(ctx, get_random_digit_position());
|
.set_page_counter(ctx, get_random_digit_position());
|
||||||
|
self.show_last_digit = true;
|
||||||
self.update(ctx);
|
self.update(ctx);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -210,7 +215,6 @@ where
|
|||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
self.pin_line.paint();
|
self.pin_line.paint();
|
||||||
self.subprompt_line.paint();
|
|
||||||
self.choice_page.paint();
|
self.choice_page.paint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,11 +228,7 @@ where
|
|||||||
{
|
{
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("PinKeyboard");
|
t.component("PinKeyboard");
|
||||||
t.string("prompt", self.prompt.as_ref());
|
t.string("subprompt", self.subprompt.as_ref());
|
||||||
let subprompt = self.subprompt_line.inner().get_text();
|
|
||||||
if !subprompt.as_ref().is_empty() {
|
|
||||||
t.string("subprompt", subprompt.as_ref());
|
|
||||||
}
|
|
||||||
t.string("pin", self.textbox.content());
|
t.string("pin", self.textbox.content());
|
||||||
t.child("choice_page", &self.choice_page);
|
t.child("choice_page", &self.choice_page);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
ComponentExt, FormattedText, LineBreaking, Timeout,
|
ComponentExt, FormattedText, LineBreaking, Timeout,
|
||||||
},
|
},
|
||||||
display, geometry,
|
display,
|
||||||
|
geometry::{self, Alignment},
|
||||||
layout::{
|
layout::{
|
||||||
obj::{ComponentMsgObj, LayoutObj},
|
obj::{ComponentMsgObj, LayoutObj},
|
||||||
result::{CANCELLED, CONFIRMED, INFO},
|
result::{CANCELLED, CONFIRMED, INFO},
|
||||||
@ -740,7 +741,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
|||||||
"CONFIRM",
|
"CONFIRM",
|
||||||
"Press both left and right at the same\ntime to confirm.",
|
"Press both left and right at the same\ntime to confirm.",
|
||||||
ButtonLayout::none_armed_none("CONFIRM".into()),
|
ButtonLayout::none_armed_none("CONFIRM".into()),
|
||||||
ButtonActions::prev_next_none(),
|
ButtonActions::none_next_none(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
5 => {
|
5 => {
|
||||||
@ -775,6 +776,33 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
|||||||
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_error(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
|
||||||
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
|
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||||
|
|
||||||
|
let get_page = move |page_index| {
|
||||||
|
assert!(page_index == 0);
|
||||||
|
|
||||||
|
let btn_layout = ButtonLayout::none_armed_none(button.clone());
|
||||||
|
let btn_actions = ButtonActions::none_confirm_none();
|
||||||
|
let ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL)
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.text_bold(title.clone())
|
||||||
|
.newline()
|
||||||
|
.text_normal(description.clone());
|
||||||
|
let formatted = FormattedText::new(ops).vertically_aligned(Alignment::Center);
|
||||||
|
Page::new(btn_layout, btn_actions, formatted)
|
||||||
|
};
|
||||||
|
let pages = FlowPages::new(get_page, 1);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(Flow::new(pages))?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_confirm_modify_fee(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 sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?;
|
let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?;
|
||||||
@ -814,6 +842,61 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m
|
|||||||
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_multiple_pages_texts(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 verb: StrBuffer = kwargs.get(Qstr::MP_QSTR_verb)?.try_into()?;
|
||||||
|
let items: Gc<List> = kwargs.get(Qstr::MP_QSTR_items)?.try_into()?;
|
||||||
|
|
||||||
|
// Cache the page count so that we can move `items` into the closure.
|
||||||
|
let page_count = items.len();
|
||||||
|
|
||||||
|
// Closure to lazy-load the information on given page index.
|
||||||
|
// Done like this to allow arbitrarily many pages without
|
||||||
|
// the need of any allocation here in Rust.
|
||||||
|
let get_page = move |page_index| {
|
||||||
|
let item_obj = unwrap!(items.get(page_index));
|
||||||
|
let text = unwrap!(item_obj.try_into());
|
||||||
|
|
||||||
|
let (btn_layout, btn_actions) = if page_count == 1 {
|
||||||
|
// There is only one page
|
||||||
|
(
|
||||||
|
ButtonLayout::cancel_none_text(verb.clone()),
|
||||||
|
ButtonActions::cancel_none_confirm(),
|
||||||
|
)
|
||||||
|
} else if page_index == 0 {
|
||||||
|
// First page
|
||||||
|
(
|
||||||
|
ButtonLayout::cancel_none_arrow_wide(),
|
||||||
|
ButtonActions::cancel_none_next(),
|
||||||
|
)
|
||||||
|
} else if page_index == page_count - 1 {
|
||||||
|
// Last page
|
||||||
|
(
|
||||||
|
ButtonLayout::up_arrow_none_text(verb.clone()),
|
||||||
|
ButtonActions::prev_none_confirm(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Page in the middle
|
||||||
|
(
|
||||||
|
ButtonLayout::up_arrow_none_arrow_wide(),
|
||||||
|
ButtonActions::prev_none_next(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text);
|
||||||
|
let formatted = FormattedText::new(ops).vertically_aligned(Alignment::Center);
|
||||||
|
|
||||||
|
Page::new(btn_layout, btn_actions, formatted)
|
||||||
|
};
|
||||||
|
|
||||||
|
let pages = FlowPages::new(get_page, page_count);
|
||||||
|
let obj = LayoutObj::new(Flow::new(pages).with_common_title(title))?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_confirm_fido(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()?;
|
||||||
@ -999,7 +1082,9 @@ extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map)
|
|||||||
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
||||||
let subprompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
|
let subprompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
|
||||||
|
|
||||||
let obj = LayoutObj::new(PinEntry::new(prompt, subprompt))?;
|
let obj =
|
||||||
|
LayoutObj::new(Frame::new(prompt, PinEntry::new(subprompt)).with_title_centered())?;
|
||||||
|
|
||||||
Ok(obj.into())
|
Ok(obj.into())
|
||||||
};
|
};
|
||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
@ -1442,6 +1527,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// """Show user how to interact with the device."""
|
/// """Show user how to interact with the device."""
|
||||||
Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(),
|
Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(),
|
||||||
|
|
||||||
|
/// def show_error(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// description: str,
|
||||||
|
/// button: str,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Show a popup with text centered both vertically and horizontally. With just a middle button."""
|
||||||
|
Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(),
|
||||||
|
|
||||||
/// def confirm_modify_fee(
|
/// def confirm_modify_fee(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str, # ignored
|
/// title: str, # ignored
|
||||||
@ -1466,6 +1560,15 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// """
|
/// """
|
||||||
Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(),
|
Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(),
|
||||||
|
|
||||||
|
/// def multiple_pages_texts(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// verb: str,
|
||||||
|
/// items: list[str],
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Show multiple texts, each on its own page."""
|
||||||
|
Qstr::MP_QSTR_multiple_pages_texts => obj_fn_kw!(0, new_multiple_pages_texts).as_obj(),
|
||||||
|
|
||||||
/// def show_info(
|
/// def show_info(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str,
|
/// title: str,
|
||||||
|
@ -149,6 +149,16 @@ def tutorial() -> object:
|
|||||||
"""Show user how to interact with the device."""
|
"""Show user how to interact with the device."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tr/layout.rs
|
||||||
|
def show_error(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
description: str,
|
||||||
|
button: str,
|
||||||
|
) -> object:
|
||||||
|
"""Show a popup with text centered both vertically and horizontally. With just a middle button."""
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_tr/layout.rs
|
# rust/src/ui/model_tr/layout.rs
|
||||||
def confirm_modify_fee(
|
def confirm_modify_fee(
|
||||||
*,
|
*,
|
||||||
@ -174,6 +184,16 @@ def confirm_fido(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tr/layout.rs
|
||||||
|
def multiple_pages_texts(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
verb: str,
|
||||||
|
items: list[str],
|
||||||
|
) -> object:
|
||||||
|
"""Show multiple texts, each on its own page."""
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_tr/layout.rs
|
# rust/src/ui/model_tr/layout.rs
|
||||||
def show_info(
|
def show_info(
|
||||||
*,
|
*,
|
||||||
|
@ -52,10 +52,10 @@ async def change_pin(msg: ChangePin) -> Success:
|
|||||||
msg_screen = "PIN changed."
|
msg_screen = "PIN changed."
|
||||||
msg_wire = "PIN changed"
|
msg_wire = "PIN changed"
|
||||||
else:
|
else:
|
||||||
msg_screen = "PIN protection enabled."
|
msg_screen = "PIN protection\nturned on."
|
||||||
msg_wire = "PIN enabled"
|
msg_wire = "PIN enabled"
|
||||||
else:
|
else:
|
||||||
msg_screen = "PIN protection disabled."
|
msg_screen = "PIN protection\nturned off."
|
||||||
msg_wire = "PIN removed"
|
msg_wire = "PIN removed"
|
||||||
|
|
||||||
await show_success("success_pin", msg_screen)
|
await show_success("success_pin", msg_screen)
|
||||||
@ -67,34 +67,30 @@ def _require_confirm_change_pin(msg: ChangePin) -> Awaitable[None]:
|
|||||||
|
|
||||||
has_pin = config.has_pin()
|
has_pin = config.has_pin()
|
||||||
|
|
||||||
br_type = "set_pin"
|
|
||||||
title = "PIN settings"
|
title = "PIN settings"
|
||||||
|
|
||||||
if msg.remove and has_pin: # removing pin
|
if msg.remove and has_pin: # removing pin
|
||||||
return confirm_action(
|
return confirm_action(
|
||||||
br_type,
|
"disable_pin",
|
||||||
title,
|
title,
|
||||||
description="Do you want to disable PIN protection?",
|
description="Are you sure you want to turn off PIN protection?",
|
||||||
verb="Disable",
|
verb="Turn off",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not msg.remove and has_pin: # changing pin
|
if not msg.remove and has_pin: # changing pin
|
||||||
return confirm_action(
|
return confirm_action(
|
||||||
br_type,
|
"change_pin",
|
||||||
title,
|
title,
|
||||||
description="Do you want to change your PIN?",
|
description="Change PIN?",
|
||||||
verb="Change",
|
verb="Change",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not msg.remove and not has_pin: # setting new pin
|
if not msg.remove and not has_pin: # setting new pin
|
||||||
return confirm_set_new_pin(
|
return confirm_set_new_pin(
|
||||||
br_type,
|
"set_pin",
|
||||||
title,
|
title,
|
||||||
"Do you want to enable PIN protection?",
|
"PIN",
|
||||||
[
|
"PIN will be required to access this device.",
|
||||||
"PIN will be used to access this device.",
|
|
||||||
"PIN should be 4-50 digits long.",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# removing non-existing PIN
|
# removing non-existing PIN
|
||||||
|
@ -68,15 +68,15 @@ def _require_confirm_action(
|
|||||||
return confirm_action(
|
return confirm_action(
|
||||||
"disable_wipe_code",
|
"disable_wipe_code",
|
||||||
title,
|
title,
|
||||||
description="Do you want to disable wipe code protection?",
|
description="Turn off wipe code protection?",
|
||||||
verb="Disable",
|
verb="Turn off",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not msg.remove and has_wipe_code:
|
if not msg.remove and has_wipe_code:
|
||||||
return confirm_action(
|
return confirm_action(
|
||||||
"change_wipe_code",
|
"change_wipe_code",
|
||||||
title,
|
title,
|
||||||
description="Do you want to change the wipe code?",
|
description="Change wipe code?",
|
||||||
verb="Change",
|
verb="Change",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,10 +84,8 @@ def _require_confirm_action(
|
|||||||
return confirm_set_new_pin(
|
return confirm_set_new_pin(
|
||||||
"set_wipe_code",
|
"set_wipe_code",
|
||||||
title,
|
title,
|
||||||
"Do you want to enable wipe code?",
|
"wipe code",
|
||||||
[
|
"Wipe code can be used to erase all data from this device.",
|
||||||
"Wipe code can be used to erase all data from this device.",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Removing non-existing wipe code.
|
# Removing non-existing wipe code.
|
||||||
|
@ -1130,7 +1130,7 @@ async def request_pin_on_device(
|
|||||||
result = await interact(
|
result = await interact(
|
||||||
RustLayout(
|
RustLayout(
|
||||||
trezorui2.request_pin(
|
trezorui2.request_pin(
|
||||||
prompt=prompt,
|
prompt=prompt.upper(),
|
||||||
subprompt=subprompt,
|
subprompt=subprompt,
|
||||||
allow_cancel=allow_cancel,
|
allow_cancel=allow_cancel,
|
||||||
wrong_pin=wrong_pin,
|
wrong_pin=wrong_pin,
|
||||||
@ -1151,33 +1151,73 @@ async def confirm_reenter_pin(
|
|||||||
) -> None:
|
) -> None:
|
||||||
br_type = "reenter_wipe_code" if is_wipe_code else "reenter_pin"
|
br_type = "reenter_wipe_code" if is_wipe_code else "reenter_pin"
|
||||||
title = "CHECK WIPE CODE" if is_wipe_code else "CHECK PIN"
|
title = "CHECK WIPE CODE" if is_wipe_code else "CHECK PIN"
|
||||||
|
description = "wipe code" if is_wipe_code else "PIN"
|
||||||
return await confirm_action(
|
return await confirm_action(
|
||||||
br_type,
|
br_type,
|
||||||
title,
|
title,
|
||||||
action="Please re-enter to confirm.",
|
description=f"Please re-enter {description} to confirm.",
|
||||||
verb="BEGIN",
|
verb="CONTINUE",
|
||||||
br_code=BR_TYPE_OTHER,
|
br_code=BR_TYPE_OTHER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def show_error(
|
||||||
|
br_type: str,
|
||||||
|
title: str,
|
||||||
|
description: str,
|
||||||
|
button: str,
|
||||||
|
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
||||||
|
) -> None:
|
||||||
|
await interact(
|
||||||
|
RustLayout(
|
||||||
|
trezorui2.show_error(
|
||||||
|
title=title,
|
||||||
|
description=description,
|
||||||
|
button=button,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
br_type,
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def confirm_multiple_pages_texts(
|
||||||
|
br_type: str,
|
||||||
|
title: str,
|
||||||
|
items: list[str],
|
||||||
|
verb: str,
|
||||||
|
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
||||||
|
) -> None:
|
||||||
|
await raise_if_not_confirmed(
|
||||||
|
interact(
|
||||||
|
RustLayout(
|
||||||
|
trezorui2.multiple_pages_texts(
|
||||||
|
title=title,
|
||||||
|
verb=verb,
|
||||||
|
items=items,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
br_type,
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def pin_mismatch_popup(
|
async def pin_mismatch_popup(
|
||||||
is_wipe_code: bool = False,
|
is_wipe_code: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
title = "WIPE CODE MISMATCH" if is_wipe_code else "PIN MISMATCH"
|
|
||||||
description = "wipe codes" if is_wipe_code else "PINs"
|
description = "wipe codes" if is_wipe_code else "PINs"
|
||||||
return await confirm_action(
|
br_code = "wipe_code_mismatch" if is_wipe_code else "pin_mismatch"
|
||||||
"pin_mismatch",
|
return await show_error(
|
||||||
title,
|
br_code,
|
||||||
description=f"The {description} you entered do not match.\nPlease try again.",
|
f"Entered {description} do not match!",
|
||||||
verb="TRY AGAIN",
|
"Please check again.",
|
||||||
verb_cancel=None,
|
"CHECK AGAIN",
|
||||||
br_code=BR_TYPE_OTHER,
|
BR_TYPE_OTHER,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def wipe_code_same_as_pin_popup(
|
async def wipe_code_same_as_pin_popup() -> None:
|
||||||
is_wipe_code: bool = False,
|
|
||||||
) -> None:
|
|
||||||
return await confirm_action(
|
return await confirm_action(
|
||||||
"wipe_code_same_as_pin",
|
"wipe_code_same_as_pin",
|
||||||
"INVALID WIPE CODE",
|
"INVALID WIPE CODE",
|
||||||
@ -1192,34 +1232,33 @@ async def confirm_set_new_pin(
|
|||||||
br_type: str,
|
br_type: str,
|
||||||
title: str,
|
title: str,
|
||||||
description: str,
|
description: str,
|
||||||
information: list[str],
|
information: str,
|
||||||
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
||||||
) -> None:
|
) -> None:
|
||||||
await confirm_action(
|
question = f"Turn on {description} protection?"
|
||||||
|
await confirm_multiple_pages_texts(
|
||||||
br_type,
|
br_type,
|
||||||
title,
|
title.upper(),
|
||||||
description=description,
|
[question, information],
|
||||||
verb="ENABLE",
|
"TURN ON",
|
||||||
br_code=br_code,
|
br_code,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Additional information for the user to know about PIN/WIPE CODE
|
# Not showing extra info for wipe code
|
||||||
|
|
||||||
if "wipe_code" in br_type:
|
if "wipe_code" in br_type:
|
||||||
verb = "HODL TO BEGIN" # Easter egg from @Hannsek
|
return
|
||||||
else:
|
|
||||||
information.append(
|
|
||||||
"Position of individual numbers will change between entries for enhanced security."
|
|
||||||
)
|
|
||||||
verb = "HOLD TO BEGIN"
|
|
||||||
|
|
||||||
return await confirm_action(
|
# Additional information for the user to know about PIN
|
||||||
|
next_info = [
|
||||||
|
"PIN should be 4-50 digits long.",
|
||||||
|
"Position of the cursor will change between entries for enhanced security.",
|
||||||
|
]
|
||||||
|
await confirm_multiple_pages_texts(
|
||||||
br_type,
|
br_type,
|
||||||
"",
|
title.upper(),
|
||||||
description="\n\r".join(information),
|
next_info,
|
||||||
verb=verb,
|
"CONTINUE",
|
||||||
hold=True,
|
br_code,
|
||||||
br_code=br_code,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1217,17 +1217,37 @@ async def confirm_set_new_pin(
|
|||||||
br_type: str,
|
br_type: str,
|
||||||
title: str,
|
title: str,
|
||||||
description: str,
|
description: str,
|
||||||
information: list[str], # unused on TT
|
information: str,
|
||||||
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
br_code: ButtonRequestType = BR_TYPE_OTHER,
|
||||||
) -> None:
|
) -> None:
|
||||||
await confirm_action(
|
await raise_if_not_confirmed(
|
||||||
br_type,
|
interact(
|
||||||
title,
|
RustLayout(
|
||||||
description=description,
|
trezorui2.confirm_emphasized(
|
||||||
verb="ENABLE",
|
title=title.upper(),
|
||||||
br_code=br_code,
|
items=(
|
||||||
|
"Turn on ",
|
||||||
|
(True, description),
|
||||||
|
" protection?\n\n",
|
||||||
|
information,
|
||||||
|
),
|
||||||
|
verb="TURN ON",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
br_type,
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# await confirm_action(
|
||||||
|
# ctx,
|
||||||
|
# br_type,
|
||||||
|
# title,
|
||||||
|
# description=description,
|
||||||
|
# verb="TURN ON",
|
||||||
|
# br_code=br_code,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
async def mnemonic_word_entering() -> None:
|
async def mnemonic_word_entering() -> None:
|
||||||
"""Not supported for TT."""
|
"""Not supported for TT."""
|
||||||
|
@ -308,7 +308,7 @@ class LayoutContent(UnstructuredJSONReader):
|
|||||||
|
|
||||||
def pin(self) -> str:
|
def pin(self) -> str:
|
||||||
"""Get PIN from the layout."""
|
"""Get PIN from the layout."""
|
||||||
assert self.main_component() == "PinKeyboard"
|
assert "PinKeyboard" in self.all_components()
|
||||||
return self.find_unique_value_by_key("pin", default="", only_type=str)
|
return self.find_unique_value_by_key("pin", default="", only_type=str)
|
||||||
|
|
||||||
def passphrase(self) -> str:
|
def passphrase(self) -> str:
|
||||||
|
@ -58,7 +58,7 @@ def set_autolock_delay(device_handler: "BackgroundDeviceHandler", delay_ms: int)
|
|||||||
|
|
||||||
device_handler.run(device.apply_settings, auto_lock_delay_ms=delay_ms) # type: ignore
|
device_handler.run(device.apply_settings, auto_lock_delay_ms=delay_ms) # type: ignore
|
||||||
|
|
||||||
assert debug.wait_layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in debug.wait_layout().all_components()
|
||||||
|
|
||||||
debug.input("1234")
|
debug.input("1234")
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ def unlock_dry_run(debug: "DebugLink") -> "LayoutContent":
|
|||||||
in debug.wait_layout().text_content()
|
in debug.wait_layout().text_content()
|
||||||
)
|
)
|
||||||
layout = go_next(debug, wait=True)
|
layout = go_next(debug, wait=True)
|
||||||
assert layout.main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout.all_components()
|
||||||
|
|
||||||
layout = debug.input(PIN4, wait=True)
|
layout = debug.input(PIN4, wait=True)
|
||||||
assert layout is not None
|
assert layout is not None
|
||||||
@ -276,7 +276,7 @@ def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandle
|
|||||||
# lockscreen triggered automatically
|
# lockscreen triggered automatically
|
||||||
debug.wait_layout(wait_for_external_change=True)
|
debug.wait_layout(wait_for_external_change=True)
|
||||||
layout = go_next(debug, wait=True)
|
layout = go_next(debug, wait=True)
|
||||||
assert layout.main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout.all_components()
|
||||||
layout = debug.input(PIN4, wait=True)
|
layout = debug.input(PIN4, wait=True)
|
||||||
assert layout is not None
|
assert layout is not None
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
|
|||||||
# unlock with message
|
# unlock with message
|
||||||
device_handler.run(common.get_test_address)
|
device_handler.run(common.get_test_address)
|
||||||
|
|
||||||
assert debug.wait_layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in debug.wait_layout().all_components()
|
||||||
debug.input("1234", wait=True)
|
debug.input("1234", wait=True)
|
||||||
assert device_handler.result()
|
assert device_handler.result()
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
|
|||||||
layout = debug.wait_layout()
|
layout = debug.wait_layout()
|
||||||
else:
|
else:
|
||||||
layout = debug.click(buttons.INFO, wait=True)
|
layout = debug.click(buttons.INFO, wait=True)
|
||||||
assert layout.main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout.all_components()
|
||||||
debug.input("1234", wait=True)
|
debug.input("1234", wait=True)
|
||||||
|
|
||||||
assert device_handler.features().unlocked is True
|
assert device_handler.features().unlocked is True
|
||||||
|
@ -83,19 +83,19 @@ def prepare(
|
|||||||
elif situation == Situation.PIN_SETUP:
|
elif situation == Situation.PIN_SETUP:
|
||||||
# Set new PIN
|
# Set new PIN
|
||||||
device_handler.run(device.change_pin) # type: ignore
|
device_handler.run(device.change_pin) # type: ignore
|
||||||
assert "enable PIN protection" in debug.wait_layout().text_content()
|
assert "Turn on" in debug.wait_layout().text_content()
|
||||||
if debug.model == "T":
|
if debug.model == "T":
|
||||||
go_next(debug)
|
go_next(debug)
|
||||||
elif debug.model == "R":
|
elif debug.model == "R":
|
||||||
go_next(debug, wait=True)
|
go_next(debug, wait=True)
|
||||||
go_next(debug, wait=True)
|
go_next(debug, wait=True)
|
||||||
go_next(debug, wait=True)
|
go_next(debug, wait=True)
|
||||||
debug.press_right_htc(1000)
|
go_next(debug, wait=True)
|
||||||
elif situation == Situation.PIN_CHANGE:
|
elif situation == Situation.PIN_CHANGE:
|
||||||
# Change PIN
|
# Change PIN
|
||||||
device_handler.run(device.change_pin) # type: ignore
|
device_handler.run(device.change_pin) # type: ignore
|
||||||
_input_see_confirm(debug, old_pin)
|
_input_see_confirm(debug, old_pin)
|
||||||
assert "change your PIN" in debug.read_layout().text_content()
|
assert "Change PIN" in debug.read_layout().text_content()
|
||||||
go_next(debug, wait=True)
|
go_next(debug, wait=True)
|
||||||
_input_see_confirm(debug, old_pin)
|
_input_see_confirm(debug, old_pin)
|
||||||
elif situation == Situation.WIPE_CODE_SETUP:
|
elif situation == Situation.WIPE_CODE_SETUP:
|
||||||
@ -103,10 +103,12 @@ def prepare(
|
|||||||
device_handler.run(device.change_wipe_code) # type: ignore
|
device_handler.run(device.change_wipe_code) # type: ignore
|
||||||
if old_pin:
|
if old_pin:
|
||||||
_input_see_confirm(debug, old_pin)
|
_input_see_confirm(debug, old_pin)
|
||||||
assert "enable wipe code" in debug.wait_layout().text_content()
|
assert "Turn on" in debug.wait_layout().text_content()
|
||||||
go_next(debug, wait=True)
|
go_next(debug, wait=True)
|
||||||
if debug.model == "R":
|
if debug.model == "R":
|
||||||
debug.press_right_htc(1000)
|
go_next(debug, wait=True)
|
||||||
|
go_next(debug, wait=True)
|
||||||
|
go_next(debug, wait=True)
|
||||||
if old_pin:
|
if old_pin:
|
||||||
debug.wait_layout()
|
debug.wait_layout()
|
||||||
_input_see_confirm(debug, old_pin)
|
_input_see_confirm(debug, old_pin)
|
||||||
@ -119,7 +121,7 @@ def prepare(
|
|||||||
|
|
||||||
|
|
||||||
def _assert_pin_entry(debug: "DebugLink") -> None:
|
def _assert_pin_entry(debug: "DebugLink") -> None:
|
||||||
assert debug.read_layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in debug.read_layout().all_components()
|
||||||
|
|
||||||
|
|
||||||
def _input_pin(debug: "DebugLink", pin: str, check: bool = False) -> None:
|
def _input_pin(debug: "DebugLink", pin: str, check: bool = False) -> None:
|
||||||
@ -262,10 +264,11 @@ def test_pin_setup(device_handler: "BackgroundDeviceHandler"):
|
|||||||
def test_pin_setup_mismatch(device_handler: "BackgroundDeviceHandler"):
|
def test_pin_setup_mismatch(device_handler: "BackgroundDeviceHandler"):
|
||||||
with PIN_CANCELLED, prepare(device_handler, Situation.PIN_SETUP) as debug:
|
with PIN_CANCELLED, prepare(device_handler, Situation.PIN_SETUP) as debug:
|
||||||
_enter_two_times(debug, "1", "2")
|
_enter_two_times(debug, "1", "2")
|
||||||
go_next(debug)
|
|
||||||
if debug.model == "T":
|
if debug.model == "T":
|
||||||
|
go_next(debug)
|
||||||
_cancel_pin(debug)
|
_cancel_pin(debug)
|
||||||
elif debug.model == "R":
|
elif debug.model == "R":
|
||||||
|
debug.press_middle()
|
||||||
debug.press_no()
|
debug.press_no()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1278,7 +1278,7 @@ def test_prevtx_forbidden_fields(client: Client, field, value):
|
|||||||
"field, value",
|
"field, value",
|
||||||
(("expiry", 9), ("timestamp", 42), ("version_group_id", 69), ("branch_id", 13)),
|
(("expiry", 9), ("timestamp", 42), ("version_group_id", 69), ("branch_id", 13)),
|
||||||
)
|
)
|
||||||
def test_signtx_forbidden_fields(client: Client, field, value):
|
def test_signtx_forbidden_fields(client: Client, field: str, value: int):
|
||||||
inp0 = messages.TxInputType(
|
inp0 = messages.TxInputType(
|
||||||
address_n=parse_path("m/44h/0h/0h/0/0"), # 1JAd7XCBzGudGpJQSDSfpmJhiygtLQWaGL
|
address_n=parse_path("m/44h/0h/0h/0/0"), # 1JAd7XCBzGudGpJQSDSfpmJhiygtLQWaGL
|
||||||
prev_hash=TXHASH_157041,
|
prev_hash=TXHASH_157041,
|
||||||
@ -1357,7 +1357,9 @@ def test_incorrect_input_script_type(client: Client, script_type):
|
|||||||
messages.OutputScriptType.PAYTOSCRIPTHASH,
|
messages.OutputScriptType.PAYTOSCRIPTHASH,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_incorrect_output_script_type(client: Client, script_type):
|
def test_incorrect_output_script_type(
|
||||||
|
client: Client, script_type: messages.OutputScriptType
|
||||||
|
):
|
||||||
address_n = parse_path("m/44h/1h/0h/0/0") # mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q
|
address_n = parse_path("m/44h/1h/0h/0/0") # mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q
|
||||||
attacker_multisig_public_key = bytes.fromhex(
|
attacker_multisig_public_key = bytes.fromhex(
|
||||||
"030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0"
|
"030e669acac1f280d1ddf441cd2ba5e97417bf2689e4bbec86df4f831bf9f7ffd0"
|
||||||
@ -1407,7 +1409,7 @@ def test_incorrect_output_script_type(client: Client, script_type):
|
|||||||
"lock_time, sequence",
|
"lock_time, sequence",
|
||||||
((499_999_999, 0xFFFFFFFE), (500_000_000, 0xFFFFFFFE), (1, 0xFFFFFFFF)),
|
((499_999_999, 0xFFFFFFFE), (500_000_000, 0xFFFFFFFE), (1, 0xFFFFFFFF)),
|
||||||
)
|
)
|
||||||
def test_lock_time(client: Client, lock_time, sequence):
|
def test_lock_time(client: Client, lock_time: int, sequence: int):
|
||||||
# input tx: 0dac366fd8a67b2a89fbb0d31086e7acded7a5bbf9ef9daa935bc873229ef5b5
|
# input tx: 0dac366fd8a67b2a89fbb0d31086e7acded7a5bbf9ef9daa935bc873229ef5b5
|
||||||
|
|
||||||
inp1 = messages.TxInputType(
|
inp1 = messages.TxInputType(
|
||||||
@ -1548,7 +1550,6 @@ def test_information(client: Client):
|
|||||||
with client:
|
with client:
|
||||||
IF = InputFlowSignTxInformation(client)
|
IF = InputFlowSignTxInformation(client)
|
||||||
client.set_input_flow(IF.get())
|
client.set_input_flow(IF.get())
|
||||||
client.watch_layout(True)
|
|
||||||
|
|
||||||
btc.sign_tx(
|
btc.sign_tx(
|
||||||
client,
|
client,
|
||||||
@ -1584,7 +1585,6 @@ def test_information_mixed(client: Client):
|
|||||||
with client:
|
with client:
|
||||||
IF = InputFlowSignTxInformationMixed(client)
|
IF = InputFlowSignTxInformationMixed(client)
|
||||||
client.set_input_flow(IF.get())
|
client.set_input_flow(IF.get())
|
||||||
client.watch_layout(True)
|
|
||||||
|
|
||||||
btc.sign_tx(
|
btc.sign_tx(
|
||||||
client,
|
client,
|
||||||
@ -1616,7 +1616,6 @@ def test_information_cancel(client: Client):
|
|||||||
with client, pytest.raises(Cancelled):
|
with client, pytest.raises(Cancelled):
|
||||||
IF = InputFlowSignTxInformationCancel(client)
|
IF = InputFlowSignTxInformationCancel(client)
|
||||||
client.set_input_flow(IF.get())
|
client.set_input_flow(IF.get())
|
||||||
client.watch_layout(True)
|
|
||||||
|
|
||||||
btc.sign_tx(
|
btc.sign_tx(
|
||||||
client,
|
client,
|
||||||
@ -1663,7 +1662,6 @@ def test_information_replacement(client: Client):
|
|||||||
with client:
|
with client:
|
||||||
IF = InputFlowSignTxInformationReplacement(client)
|
IF = InputFlowSignTxInformationReplacement(client)
|
||||||
client.set_input_flow(IF.get())
|
client.set_input_flow(IF.get())
|
||||||
client.watch_layout(True)
|
|
||||||
|
|
||||||
btc.sign_tx(
|
btc.sign_tx(
|
||||||
client,
|
client,
|
||||||
|
@ -63,7 +63,7 @@ def test_set_remove_wipe_code(client: Client):
|
|||||||
assert client.features.wipe_code_protection is False
|
assert client.features.wipe_code_protection is False
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
br_amount = 5 if client.debug.model == "T" else 7
|
br_amount = 5 if client.debug.model == "T" else 6
|
||||||
client.set_expected_responses(
|
client.set_expected_responses(
|
||||||
[messages.ButtonRequest()] * br_amount
|
[messages.ButtonRequest()] * br_amount
|
||||||
+ [messages.Success, messages.Features]
|
+ [messages.Success, messages.Features]
|
||||||
@ -118,7 +118,7 @@ def test_set_wipe_code_to_pin(client: Client):
|
|||||||
_ensure_unlocked(client, PIN4)
|
_ensure_unlocked(client, PIN4)
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
br_amount = 7 if client.debug.model == "T" else 9
|
br_amount = 7 if client.debug.model == "T" else 8
|
||||||
client.set_expected_responses(
|
client.set_expected_responses(
|
||||||
[messages.ButtonRequest()] * br_amount
|
[messages.ButtonRequest()] * br_amount
|
||||||
+ [messages.Success, messages.Features]
|
+ [messages.Success, messages.Features]
|
||||||
@ -134,7 +134,7 @@ def test_set_wipe_code_to_pin(client: Client):
|
|||||||
def test_set_pin_to_wipe_code(client: Client):
|
def test_set_pin_to_wipe_code(client: Client):
|
||||||
# Set wipe code.
|
# Set wipe code.
|
||||||
with client:
|
with client:
|
||||||
br_amount = 4 if client.debug.model == "T" else 6
|
br_amount = 4 if client.debug.model == "T" else 5
|
||||||
client.set_expected_responses(
|
client.set_expected_responses(
|
||||||
[messages.ButtonRequest()] * br_amount
|
[messages.ButtonRequest()] * br_amount
|
||||||
+ [messages.Success, messages.Features]
|
+ [messages.Success, messages.Features]
|
||||||
|
@ -53,7 +53,7 @@ def test_sd_protect_unlock(client: Client):
|
|||||||
|
|
||||||
def input_flow_enable_sd_protect():
|
def input_flow_enable_sd_protect():
|
||||||
yield # Enter PIN to unlock device
|
yield # Enter PIN to unlock device
|
||||||
assert layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout().all_components()
|
||||||
client.debug.input("1234")
|
client.debug.input("1234")
|
||||||
|
|
||||||
yield # do you really want to enable SD protection
|
yield # do you really want to enable SD protection
|
||||||
@ -61,7 +61,7 @@ def test_sd_protect_unlock(client: Client):
|
|||||||
client.debug.press_yes()
|
client.debug.press_yes()
|
||||||
|
|
||||||
yield # enter current PIN
|
yield # enter current PIN
|
||||||
assert layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout().all_components()
|
||||||
client.debug.input("1234")
|
client.debug.input("1234")
|
||||||
|
|
||||||
yield # you have successfully enabled SD protection
|
yield # you have successfully enabled SD protection
|
||||||
@ -79,15 +79,15 @@ def test_sd_protect_unlock(client: Client):
|
|||||||
client.debug.press_yes()
|
client.debug.press_yes()
|
||||||
|
|
||||||
yield # enter current PIN
|
yield # enter current PIN
|
||||||
assert layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout().all_components()
|
||||||
client.debug.input("1234")
|
client.debug.input("1234")
|
||||||
|
|
||||||
yield # enter new PIN
|
yield # enter new PIN
|
||||||
assert layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout().all_components()
|
||||||
client.debug.input("1234")
|
client.debug.input("1234")
|
||||||
|
|
||||||
yield # enter new PIN again
|
yield # enter new PIN again
|
||||||
assert layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout().all_components()
|
||||||
client.debug.input("1234")
|
client.debug.input("1234")
|
||||||
|
|
||||||
yield # Pin change successful
|
yield # Pin change successful
|
||||||
@ -107,7 +107,7 @@ def test_sd_protect_unlock(client: Client):
|
|||||||
client.debug.press_yes()
|
client.debug.press_yes()
|
||||||
|
|
||||||
yield # enter current PIN
|
yield # enter current PIN
|
||||||
assert layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in layout().all_components()
|
||||||
client.debug.input("1234")
|
client.debug.input("1234")
|
||||||
|
|
||||||
yield # SD card problem
|
yield # SD card problem
|
||||||
|
@ -1413,11 +1413,11 @@ def bip39_recovery_possible_pin(
|
|||||||
# PIN when requested
|
# PIN when requested
|
||||||
if pin is not None:
|
if pin is not None:
|
||||||
yield
|
yield
|
||||||
assert debug.wait_layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in debug.wait_layout().all_components()
|
||||||
debug.input(pin)
|
debug.input(pin)
|
||||||
|
|
||||||
yield
|
yield
|
||||||
assert debug.wait_layout().main_component() == "PinKeyboard"
|
assert "PinKeyboard" in debug.wait_layout().all_components()
|
||||||
debug.input(pin)
|
debug.input(pin)
|
||||||
|
|
||||||
yield
|
yield
|
||||||
@ -1464,7 +1464,7 @@ class InputFlowBip39RecoveryPIN(InputFlowBase):
|
|||||||
self.debug.input("654")
|
self.debug.input("654")
|
||||||
|
|
||||||
yield
|
yield
|
||||||
assert "re-enter to confirm" in self.layout().text_content()
|
assert "re-enter PIN" in self.layout().text_content()
|
||||||
self.debug.press_right()
|
self.debug.press_right()
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
Loading…
Reference in New Issue
Block a user