mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-04 13:52:35 +00:00
fix(core/mercury): show remaining shares
This commit enables showing "Remaining shares" from the context menu during recovery process. This is only shown during super-shamir.
This commit is contained in:
parent
708b0274f5
commit
c5f9fadbd1
1
core/.changelog.d/4142.fixed
Normal file
1
core/.changelog.d/4142.fixed
Normal file
@ -0,0 +1 @@
|
|||||||
|
[T3T1] Added missing info about remaining shares in super-shamir recovery.
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
error,
|
error,
|
||||||
micropython::{map::Map, obj::Obj, qstr::Qstr, util},
|
micropython::{iter::IterBuf, map::Map, obj::Obj, qstr::Qstr, util},
|
||||||
strutil::TString,
|
strutil::TString,
|
||||||
translations::TR,
|
translations::TR,
|
||||||
ui::{
|
ui::{
|
||||||
@ -8,12 +8,14 @@ use crate::{
|
|||||||
component::{
|
component::{
|
||||||
button_request::ButtonRequestExt,
|
button_request::ButtonRequestExt,
|
||||||
swipe_detect::SwipeSettings,
|
swipe_detect::SwipeSettings,
|
||||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
|
text::paragraphs::{
|
||||||
|
Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, VecExt,
|
||||||
|
},
|
||||||
ComponentExt, SwipeDirection,
|
ComponentExt, SwipeDirection,
|
||||||
},
|
},
|
||||||
flow::{
|
flow::{
|
||||||
base::{DecisionBuilder as _, StateChange},
|
base::{DecisionBuilder as _, StateChange},
|
||||||
FlowMsg, FlowState, SwipeFlow,
|
FlowMsg, FlowState, SwipeFlow, SwipePage,
|
||||||
},
|
},
|
||||||
layout::{obj::LayoutObj, util::RecoveryType},
|
layout::{obj::LayoutObj, util::RecoveryType},
|
||||||
},
|
},
|
||||||
@ -40,6 +42,15 @@ pub enum ContinueRecoveryBetweenShares {
|
|||||||
CancelConfirm,
|
CancelConfirm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ContinueRecoveryBetweenSharesAdvanced {
|
||||||
|
Main,
|
||||||
|
Menu,
|
||||||
|
CancelIntro,
|
||||||
|
CancelConfirm,
|
||||||
|
RemainingShares,
|
||||||
|
}
|
||||||
|
|
||||||
impl FlowState for ContinueRecoveryBeforeShares {
|
impl FlowState for ContinueRecoveryBeforeShares {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn index(&'static self) -> usize {
|
fn index(&'static self) -> usize {
|
||||||
@ -96,56 +107,99 @@ impl FlowState for ContinueRecoveryBetweenShares {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
impl FlowState for ContinueRecoveryBetweenSharesAdvanced {
|
||||||
pub extern "C" fn new_continue_recovery(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
#[inline]
|
||||||
unsafe {
|
fn index(&'static self) -> usize {
|
||||||
util::try_with_args_and_kwargs(n_args, args, kwargs, ContinueRecoveryBeforeShares::new_obj)
|
*self as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
|
||||||
|
match (self, direction) {
|
||||||
|
(Self::Main, SwipeDirection::Left) => Self::Menu.swipe(direction),
|
||||||
|
(Self::Menu, SwipeDirection::Right) => Self::Main.swipe(direction),
|
||||||
|
(Self::Main, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
|
||||||
|
(Self::CancelIntro, SwipeDirection::Up) => Self::CancelConfirm.swipe(direction),
|
||||||
|
(Self::CancelIntro, SwipeDirection::Right) => Self::Menu.swipe(direction),
|
||||||
|
(Self::CancelConfirm, SwipeDirection::Down) => Self::CancelIntro.swipe(direction),
|
||||||
|
(Self::RemainingShares, SwipeDirection::Right) => Self::Menu.swipe(direction),
|
||||||
|
_ => self.do_nothing(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
|
||||||
|
match (self, msg) {
|
||||||
|
(Self::Main, FlowMsg::Info) => Self::Menu.transit(),
|
||||||
|
(Self::Menu, FlowMsg::Choice(0)) => Self::RemainingShares.transit(),
|
||||||
|
(Self::Menu, FlowMsg::Choice(1)) => Self::CancelIntro.swipe_left(),
|
||||||
|
(Self::Menu, FlowMsg::Cancelled) => Self::Main.swipe_right(),
|
||||||
|
(Self::CancelIntro, FlowMsg::Cancelled) => Self::Menu.transit(),
|
||||||
|
(Self::CancelConfirm, FlowMsg::Cancelled) => Self::CancelIntro.swipe_right(),
|
||||||
|
(Self::CancelConfirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
|
||||||
|
(Self::RemainingShares, FlowMsg::Cancelled) => Self::Menu.transit(),
|
||||||
|
_ => self.do_nothing(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContinueRecoveryBeforeShares {
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||||
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
|
pub extern "C" fn new_continue_recovery(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
let first_screen: bool = kwargs.get(Qstr::MP_QSTR_first_screen)?.try_into()?;
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, new_obj) }
|
||||||
let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?;
|
}
|
||||||
let text: TString = kwargs.get(Qstr::MP_QSTR_text)?.try_into()?; // #shares entered
|
|
||||||
let subtext: Option<TString> = kwargs.get(Qstr::MP_QSTR_subtext)?.try_into_option()?; // #shares remaining
|
|
||||||
|
|
||||||
let (title, cancel_btn, cancel_title, cancel_intro) = match recovery_type {
|
fn new_obj(_args: &[Obj], kwargs: &Map) -> Result<Obj, error::Error> {
|
||||||
RecoveryType::Normal => (
|
let first_screen: bool = kwargs.get(Qstr::MP_QSTR_first_screen)?.try_into()?;
|
||||||
TR::recovery__title,
|
let recovery_type: RecoveryType = kwargs.get(Qstr::MP_QSTR_recovery_type)?.try_into()?;
|
||||||
TR::recovery__title_cancel_recovery,
|
let text: TString = kwargs.get(Qstr::MP_QSTR_text)?.try_into()?; // #shares entered
|
||||||
TR::recovery__title_cancel_recovery,
|
let subtext: Option<TString> = kwargs.get(Qstr::MP_QSTR_subtext)?.try_into_option()?; // #shares remaining
|
||||||
TR::recovery__wanna_cancel_recovery,
|
let pages: Option<Obj> = kwargs.get(Qstr::MP_QSTR_pages)?.try_into_option()?; // info about remaining shares
|
||||||
),
|
|
||||||
_ => (
|
|
||||||
TR::recovery__title_dry_run,
|
|
||||||
TR::recovery__cancel_dry_run,
|
|
||||||
TR::recovery__title_cancel_dry_run,
|
|
||||||
TR::recovery__wanna_cancel_dry_run,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut pars = ParagraphVecShort::new();
|
let mut pars_show_shares = ParagraphVecLong::new();
|
||||||
let footer_instruction;
|
if let Some(pages) = pages {
|
||||||
let footer_description;
|
let pages_iterable: Obj = pages;
|
||||||
if first_screen {
|
for page in IterBuf::new().try_iterate(pages_iterable)? {
|
||||||
pars.add(Paragraph::new(
|
let [title, description]: [TString; 2] = util::iter_into_array(page)?;
|
||||||
&theme::TEXT_MAIN_GREY_EXTRA_LIGHT,
|
pars_show_shares
|
||||||
TR::recovery__enter_each_word,
|
.add(Paragraph::new(&theme::TEXT_SUB_GREY, title))
|
||||||
));
|
.add(Paragraph::new(&theme::TEXT_MONO_GREY_LIGHT, description).break_after());
|
||||||
footer_instruction = TR::instructions__swipe_up.into();
|
|
||||||
footer_description = None;
|
|
||||||
} else {
|
|
||||||
pars.add(Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, text));
|
|
||||||
if let Some(sub) = subtext {
|
|
||||||
pars.add(Paragraph::new(&theme::TEXT_SUB_GREY, sub));
|
|
||||||
}
|
|
||||||
footer_instruction = TR::instructions__swipe_up.into();
|
|
||||||
footer_description = Some(TR::instructions__enter_next_share.into());
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let paragraphs_main = Paragraphs::new(pars);
|
let (title, cancel_btn, cancel_title, cancel_intro) = match recovery_type {
|
||||||
let content_main = Frame::left_aligned(title.into(), SwipeContent::new(paragraphs_main))
|
RecoveryType::Normal => (
|
||||||
|
TR::recovery__title,
|
||||||
|
TR::recovery__title_cancel_recovery,
|
||||||
|
TR::recovery__title_cancel_recovery,
|
||||||
|
TR::recovery__wanna_cancel_recovery,
|
||||||
|
),
|
||||||
|
_ => (
|
||||||
|
TR::recovery__title_dry_run,
|
||||||
|
TR::recovery__cancel_dry_run,
|
||||||
|
TR::recovery__title_cancel_dry_run,
|
||||||
|
TR::recovery__wanna_cancel_dry_run,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pars_main = ParagraphVecShort::new();
|
||||||
|
let footer_instruction;
|
||||||
|
let footer_description;
|
||||||
|
if first_screen {
|
||||||
|
pars_main.add(Paragraph::new(
|
||||||
|
&theme::TEXT_MAIN_GREY_EXTRA_LIGHT,
|
||||||
|
TR::recovery__enter_each_word,
|
||||||
|
));
|
||||||
|
footer_instruction = TR::instructions__swipe_up.into();
|
||||||
|
footer_description = None;
|
||||||
|
} else {
|
||||||
|
pars_main.add(Paragraph::new(&theme::TEXT_MAIN_GREY_EXTRA_LIGHT, text));
|
||||||
|
if let Some(sub) = subtext {
|
||||||
|
pars_main.add(Paragraph::new(&theme::TEXT_SUB_GREY, sub));
|
||||||
|
}
|
||||||
|
footer_instruction = TR::instructions__swipe_up.into();
|
||||||
|
footer_description = Some(TR::instructions__enter_next_share.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let content_main =
|
||||||
|
Frame::left_aligned(title.into(), SwipeContent::new(pars_main.into_paragraphs()))
|
||||||
.with_subtitle(TR::words__instructions.into())
|
.with_subtitle(TR::words__instructions.into())
|
||||||
.with_menu_button()
|
.with_menu_button()
|
||||||
.with_footer(footer_instruction, footer_description)
|
.with_footer(footer_instruction, footer_description)
|
||||||
@ -155,8 +209,47 @@ impl ContinueRecoveryBeforeShares {
|
|||||||
.repeated_button_request(ButtonRequest::new(
|
.repeated_button_request(ButtonRequest::new(
|
||||||
ButtonRequestCode::RecoveryHomepage,
|
ButtonRequestCode::RecoveryHomepage,
|
||||||
"recovery".into(),
|
"recovery".into(),
|
||||||
|
))
|
||||||
|
.with_pages(|_| 1);
|
||||||
|
|
||||||
|
let paragraphs_cancel = ParagraphVecShort::from_iter([
|
||||||
|
Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, cancel_intro).with_bottom_padding(17),
|
||||||
|
Paragraph::new(&theme::TEXT_WARNING, TR::recovery__progress_will_be_lost),
|
||||||
|
])
|
||||||
|
.into_paragraphs();
|
||||||
|
let content_cancel_intro =
|
||||||
|
Frame::left_aligned(cancel_title.into(), SwipeContent::new(paragraphs_cancel))
|
||||||
|
.with_cancel_button()
|
||||||
|
.with_footer(
|
||||||
|
TR::instructions__swipe_up.into(),
|
||||||
|
Some(TR::words__continue_anyway.into()),
|
||||||
|
)
|
||||||
|
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||||
|
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||||
|
.map(|msg| match msg {
|
||||||
|
FrameMsg::Button(FlowMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.repeated_button_request(ButtonRequest::new(
|
||||||
|
ButtonRequestCode::ProtectCall,
|
||||||
|
"abort_recovery".into(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let content_cancel_confirm = Frame::left_aligned(
|
||||||
|
cancel_title.into(),
|
||||||
|
SwipeContent::new(PromptScreen::new_tap_to_cancel()),
|
||||||
|
)
|
||||||
|
.with_cancel_button()
|
||||||
|
.with_footer(TR::instructions__tap_to_confirm.into(), None)
|
||||||
|
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
||||||
|
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||||
|
.map(|msg| match msg {
|
||||||
|
FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed),
|
||||||
|
FrameMsg::Button(FlowMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = if first_screen {
|
||||||
let content_menu = Frame::left_aligned(
|
let content_menu = Frame::left_aligned(
|
||||||
TString::empty(),
|
TString::empty(),
|
||||||
VerticalMenu::empty().danger(theme::ICON_CANCEL, cancel_btn.into()),
|
VerticalMenu::empty().danger(theme::ICON_CANCEL, cancel_btn.into()),
|
||||||
@ -168,60 +261,81 @@ impl ContinueRecoveryBeforeShares {
|
|||||||
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||||
});
|
});
|
||||||
|
|
||||||
let paragraphs_cancel = ParagraphVecShort::from_iter([
|
SwipeFlow::new(&ContinueRecoveryBeforeShares::Main)?
|
||||||
Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, cancel_intro).with_bottom_padding(17),
|
.with_page(&ContinueRecoveryBeforeShares::Main, content_main)?
|
||||||
Paragraph::new(&theme::TEXT_WARNING, TR::recovery__progress_will_be_lost),
|
.with_page(&ContinueRecoveryBeforeShares::Menu, content_menu)?
|
||||||
])
|
} else if pars_show_shares.is_empty() {
|
||||||
.into_paragraphs();
|
let content_menu = Frame::left_aligned(
|
||||||
let content_cancel_intro =
|
TString::empty(),
|
||||||
Frame::left_aligned(cancel_title.into(), SwipeContent::new(paragraphs_cancel))
|
VerticalMenu::empty().danger(theme::ICON_CANCEL, cancel_btn.into()),
|
||||||
.with_cancel_button()
|
|
||||||
.with_footer(
|
|
||||||
TR::instructions__swipe_up.into(),
|
|
||||||
Some(TR::words__continue_anyway.into()),
|
|
||||||
)
|
|
||||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
|
||||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
|
||||||
.map(|msg| match msg {
|
|
||||||
FrameMsg::Button(FlowMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.repeated_button_request(ButtonRequest::new(
|
|
||||||
ButtonRequestCode::ProtectCall,
|
|
||||||
"abort_recovery".into(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let content_cancel_confirm = Frame::left_aligned(
|
|
||||||
cancel_title.into(),
|
|
||||||
SwipeContent::new(PromptScreen::new_tap_to_cancel()),
|
|
||||||
)
|
)
|
||||||
.with_cancel_button()
|
.with_cancel_button()
|
||||||
.with_footer(TR::instructions__tap_to_confirm.into(), None)
|
|
||||||
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
|
|
||||||
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||||
.map(|msg| match msg {
|
.map(|msg| match msg {
|
||||||
FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed),
|
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||||
FrameMsg::Button(FlowMsg::Cancelled) => Some(FlowMsg::Cancelled),
|
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||||
_ => None,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let res = if first_screen {
|
SwipeFlow::new(&ContinueRecoveryBetweenShares::Main)?
|
||||||
SwipeFlow::new(&ContinueRecoveryBeforeShares::Main)?
|
.with_page(&ContinueRecoveryBetweenShares::Main, content_main)?
|
||||||
.with_page(&ContinueRecoveryBeforeShares::Main, content_main)?
|
.with_page(&ContinueRecoveryBetweenShares::Menu, content_menu)?
|
||||||
.with_page(&ContinueRecoveryBeforeShares::Menu, content_menu)?
|
.with_page(
|
||||||
} else {
|
&ContinueRecoveryBetweenShares::CancelIntro,
|
||||||
SwipeFlow::new(&ContinueRecoveryBetweenShares::Main)?
|
content_cancel_intro,
|
||||||
.with_page(&ContinueRecoveryBetweenShares::Main, content_main)?
|
)?
|
||||||
.with_page(&ContinueRecoveryBetweenShares::Menu, content_menu)?
|
.with_page(
|
||||||
.with_page(
|
&ContinueRecoveryBetweenShares::CancelConfirm,
|
||||||
&ContinueRecoveryBetweenShares::CancelIntro,
|
content_cancel_confirm,
|
||||||
content_cancel_intro,
|
)?
|
||||||
)?
|
} else {
|
||||||
.with_page(
|
let content_menu = Frame::left_aligned(
|
||||||
&ContinueRecoveryBetweenShares::CancelConfirm,
|
TString::empty(),
|
||||||
content_cancel_confirm,
|
VerticalMenu::empty()
|
||||||
)?
|
.item(
|
||||||
};
|
theme::ICON_CHEVRON_RIGHT,
|
||||||
Ok(LayoutObj::new(res)?.into())
|
TR::recovery__title_remaining_shares.into(),
|
||||||
}
|
)
|
||||||
|
.danger(theme::ICON_CANCEL, cancel_btn.into()),
|
||||||
|
)
|
||||||
|
.with_cancel_button()
|
||||||
|
.with_swipe(SwipeDirection::Right, SwipeSettings::immediate())
|
||||||
|
.map(|msg| match msg {
|
||||||
|
FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)),
|
||||||
|
FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
|
||||||
|
});
|
||||||
|
|
||||||
|
let n_remaining_shares = pars_show_shares.len();
|
||||||
|
let content_remaining_shares = Frame::left_aligned(
|
||||||
|
TR::recovery__title_remaining_shares.into(),
|
||||||
|
SwipeContent::new(SwipePage::vertical(pars_show_shares.into_paragraphs())),
|
||||||
|
)
|
||||||
|
.with_cancel_button()
|
||||||
|
// .with_footer(TR::instructions__swipe_up.into(), None)
|
||||||
|
.with_swipe(SwipeDirection::Up, SwipeSettings::default())
|
||||||
|
.with_swipe(SwipeDirection::Left, SwipeSettings::default())
|
||||||
|
.with_vertical_pages()
|
||||||
|
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled))
|
||||||
|
.repeated_button_request(ButtonRequest::new(
|
||||||
|
ButtonRequestCode::Other,
|
||||||
|
"show_shares".into(),
|
||||||
|
))
|
||||||
|
.with_pages(move |_| n_remaining_shares);
|
||||||
|
|
||||||
|
SwipeFlow::new(&ContinueRecoveryBetweenSharesAdvanced::Main)?
|
||||||
|
.with_page(&ContinueRecoveryBetweenSharesAdvanced::Main, content_main)?
|
||||||
|
.with_page(&ContinueRecoveryBetweenSharesAdvanced::Menu, content_menu)?
|
||||||
|
.with_page(
|
||||||
|
&ContinueRecoveryBetweenSharesAdvanced::CancelIntro,
|
||||||
|
content_cancel_intro,
|
||||||
|
)?
|
||||||
|
.with_page(
|
||||||
|
&ContinueRecoveryBetweenSharesAdvanced::CancelConfirm,
|
||||||
|
content_cancel_confirm,
|
||||||
|
)?
|
||||||
|
.with_page(
|
||||||
|
&ContinueRecoveryBetweenSharesAdvanced::RemainingShares,
|
||||||
|
content_remaining_shares,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
Ok(LayoutObj::new(res)?.into())
|
||||||
}
|
}
|
||||||
|
@ -1013,31 +1013,6 @@ extern "C" fn new_show_group_share_success(
|
|||||||
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_remaining_shares(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
|
||||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
|
||||||
let pages_iterable: Obj = kwargs.get(Qstr::MP_QSTR_pages)?;
|
|
||||||
|
|
||||||
let mut paragraphs = ParagraphVecLong::new();
|
|
||||||
for page in IterBuf::new().try_iterate(pages_iterable)? {
|
|
||||||
let [title, description]: [TString; 2] = util::iter_into_array(page)?;
|
|
||||||
paragraphs
|
|
||||||
.add(Paragraph::new(&theme::TEXT_DEMIBOLD, title))
|
|
||||||
.add(Paragraph::new(&theme::TEXT_NORMAL, description).break_after());
|
|
||||||
}
|
|
||||||
|
|
||||||
let obj = LayoutObj::new(SwipeUpScreen::new(
|
|
||||||
Frame::left_aligned(
|
|
||||||
TR::recovery__title_remaining_shares.into(),
|
|
||||||
SwipeContent::new(paragraphs.into_paragraphs()),
|
|
||||||
)
|
|
||||||
.with_footer(TR::instructions__swipe_up.into(), None)
|
|
||||||
.with_swipe(SwipeDirection::Up, SwipeSettings::default()),
|
|
||||||
))?;
|
|
||||||
Ok(obj.into())
|
|
||||||
};
|
|
||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_show_progress(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 description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||||
@ -1594,6 +1569,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// recovery_type: RecoveryType,
|
/// recovery_type: RecoveryType,
|
||||||
/// text: str,
|
/// text: str,
|
||||||
/// subtext: str | None = None,
|
/// subtext: str | None = None,
|
||||||
|
/// pages: Iterable[tuple[str, str]] | None = None,
|
||||||
/// ) -> LayoutObj[UiResult]:
|
/// ) -> LayoutObj[UiResult]:
|
||||||
/// """Device recovery homescreen."""
|
/// """Device recovery homescreen."""
|
||||||
Qstr::MP_QSTR_flow_continue_recovery => obj_fn_kw!(0, flow::continue_recovery::new_continue_recovery).as_obj(),
|
Qstr::MP_QSTR_flow_continue_recovery => obj_fn_kw!(0, flow::continue_recovery::new_continue_recovery).as_obj(),
|
||||||
@ -1613,13 +1589,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// """Shown after successfully finishing a group."""
|
/// """Shown after successfully finishing a group."""
|
||||||
Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(),
|
Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(),
|
||||||
|
|
||||||
/// def show_remaining_shares(
|
|
||||||
/// *,
|
|
||||||
/// pages: Iterable[tuple[str, str]],
|
|
||||||
/// ) -> LayoutObj[UiResult]:
|
|
||||||
/// """Shows SLIP39 state after info button is pressed on `confirm_recovery`."""
|
|
||||||
Qstr::MP_QSTR_show_remaining_shares => obj_fn_kw!(0, new_show_remaining_shares).as_obj(),
|
|
||||||
|
|
||||||
/// def show_progress(
|
/// def show_progress(
|
||||||
/// *,
|
/// *,
|
||||||
/// title: str,
|
/// title: str,
|
||||||
|
@ -452,6 +452,7 @@ def flow_continue_recovery(
|
|||||||
recovery_type: RecoveryType,
|
recovery_type: RecoveryType,
|
||||||
text: str,
|
text: str,
|
||||||
subtext: str | None = None,
|
subtext: str | None = None,
|
||||||
|
pages: Iterable[tuple[str, str]] | None = None,
|
||||||
) -> LayoutObj[UiResult]:
|
) -> LayoutObj[UiResult]:
|
||||||
"""Device recovery homescreen."""
|
"""Device recovery homescreen."""
|
||||||
|
|
||||||
@ -473,14 +474,6 @@ def show_group_share_success(
|
|||||||
"""Shown after successfully finishing a group."""
|
"""Shown after successfully finishing a group."""
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_mercury/layout.rs
|
|
||||||
def show_remaining_shares(
|
|
||||||
*,
|
|
||||||
pages: Iterable[tuple[str, str]],
|
|
||||||
) -> LayoutObj[UiResult]:
|
|
||||||
"""Shows SLIP39 state after info button is pressed on `confirm_recovery`."""
|
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_mercury/layout.rs
|
# rust/src/ui/model_mercury/layout.rs
|
||||||
def show_progress(
|
def show_progress(
|
||||||
*,
|
*,
|
||||||
|
@ -13,6 +13,8 @@ from . import layout, recover
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from trezor.enums import BackupType, RecoveryType
|
from trezor.enums import BackupType, RecoveryType
|
||||||
|
|
||||||
|
from .layout import RemainingSharesInfo
|
||||||
|
|
||||||
|
|
||||||
async def recovery_homescreen() -> None:
|
async def recovery_homescreen() -> None:
|
||||||
from trezor import workflow
|
from trezor import workflow
|
||||||
@ -297,7 +299,7 @@ async def _request_share_next_screen() -> None:
|
|||||||
await layout.homescreen_dialog(
|
await layout.homescreen_dialog(
|
||||||
TR.buttons__enter,
|
TR.buttons__enter,
|
||||||
TR.recovery__more_shares_needed,
|
TR.recovery__more_shares_needed,
|
||||||
info_func=_show_remaining_groups_and_shares,
|
remaining_shares_info=_get_remaining_groups_and_shares(),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
still_needed_shares = remaining[0]
|
still_needed_shares = remaining[0]
|
||||||
@ -315,21 +317,21 @@ async def _request_share_next_screen() -> None:
|
|||||||
await layout.homescreen_dialog(TR.buttons__enter_share, entered, needed)
|
await layout.homescreen_dialog(TR.buttons__enter_share, entered, needed)
|
||||||
|
|
||||||
|
|
||||||
async def _show_remaining_groups_and_shares() -> None:
|
def _get_remaining_groups_and_shares() -> "RemainingSharesInfo":
|
||||||
"""
|
"""
|
||||||
Show info dialog for Slip39 Advanced - what shares are to be entered.
|
Prepare data for Slip39 Advanced - what shares are to be entered.
|
||||||
"""
|
"""
|
||||||
from trezor.crypto import slip39
|
from trezor.crypto import slip39
|
||||||
|
|
||||||
shares_remaining = storage_recovery.fetch_slip39_remaining_shares()
|
shares_remaining = storage_recovery.fetch_slip39_remaining_shares()
|
||||||
# should be stored at this point
|
assert shares_remaining # should be stored at this point
|
||||||
assert shares_remaining
|
|
||||||
|
|
||||||
groups = set()
|
groups = set()
|
||||||
first_entered_index = -1
|
first_entered_index = -1
|
||||||
for i, group_count in enumerate(shares_remaining):
|
for i, group_count in enumerate(shares_remaining):
|
||||||
if group_count < slip39.MAX_SHARE_COUNT:
|
if group_count < slip39.MAX_SHARE_COUNT:
|
||||||
first_entered_index = i
|
first_entered_index = i
|
||||||
|
break
|
||||||
|
|
||||||
share = None
|
share = None
|
||||||
for index, remaining in enumerate(shares_remaining):
|
for index, remaining in enumerate(shares_remaining):
|
||||||
@ -337,15 +339,15 @@ async def _show_remaining_groups_and_shares() -> None:
|
|||||||
m = storage_recovery_shares.fetch_group(index)[0]
|
m = storage_recovery_shares.fetch_group(index)[0]
|
||||||
if not share:
|
if not share:
|
||||||
share = slip39.decode_mnemonic(m)
|
share = slip39.decode_mnemonic(m)
|
||||||
identifier = m.split(" ")[0:3]
|
identifier = tuple(m.split(" ")[0:3])
|
||||||
groups.add((remaining, tuple(identifier)))
|
groups.add(identifier)
|
||||||
elif remaining == slip39.MAX_SHARE_COUNT: # no shares yet
|
elif remaining == slip39.MAX_SHARE_COUNT: # no shares yet
|
||||||
identifier = storage_recovery_shares.fetch_group(first_entered_index)[
|
identifier = tuple(
|
||||||
0
|
storage_recovery_shares.fetch_group(first_entered_index)[0].split(" ")[
|
||||||
].split(" ")[0:2]
|
0:2
|
||||||
groups.add((remaining, tuple(identifier)))
|
]
|
||||||
|
)
|
||||||
|
groups.add(identifier)
|
||||||
|
|
||||||
assert share # share needs to be set
|
assert share # share needs to be set
|
||||||
return await layout.show_remaining_shares(
|
return groups, shares_remaining, share.group_threshold
|
||||||
groups, shares_remaining, share.group_threshold
|
|
||||||
)
|
|
||||||
|
@ -6,16 +6,19 @@ from trezor.ui.layouts.recovery import ( # noqa: F401
|
|||||||
request_word_count,
|
request_word_count,
|
||||||
show_group_share_success,
|
show_group_share_success,
|
||||||
show_recovery_warning,
|
show_recovery_warning,
|
||||||
show_remaining_shares,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from apps.common import backup_types
|
from apps.common import backup_types
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from trezor.enums import BackupType
|
from trezor.enums import BackupType
|
||||||
|
|
||||||
|
# RemainingSharesInfo represents the data structure for remaining shares in SLIP-39 recovery:
|
||||||
|
# - Set of tuples, each containing 2 or 3 words identifying a group
|
||||||
|
# - List of remaining share counts for each group
|
||||||
|
# - Group threshold (minimum number of groups required)
|
||||||
|
RemainingSharesInfo = tuple[set[tuple[str, ...]], list[int], int]
|
||||||
|
|
||||||
|
|
||||||
async def request_mnemonic(
|
async def request_mnemonic(
|
||||||
word_count: int, backup_type: BackupType | None
|
word_count: int, backup_type: BackupType | None
|
||||||
@ -123,8 +126,8 @@ async def homescreen_dialog(
|
|||||||
button_label: str,
|
button_label: str,
|
||||||
text: str,
|
text: str,
|
||||||
subtext: str | None = None,
|
subtext: str | None = None,
|
||||||
info_func: Callable | None = None,
|
|
||||||
show_info: bool = False,
|
show_info: bool = False,
|
||||||
|
remaining_shares_info: "RemainingSharesInfo | None" = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
import storage.recovery as storage_recovery
|
import storage.recovery as storage_recovery
|
||||||
from trezor.ui.layouts.recovery import continue_recovery
|
from trezor.ui.layouts.recovery import continue_recovery
|
||||||
@ -133,6 +136,11 @@ async def homescreen_dialog(
|
|||||||
|
|
||||||
recovery_type = storage_recovery.get_type()
|
recovery_type = storage_recovery.get_type()
|
||||||
if not await continue_recovery(
|
if not await continue_recovery(
|
||||||
button_label, text, subtext, info_func, recovery_type, show_info
|
button_label,
|
||||||
|
text,
|
||||||
|
subtext,
|
||||||
|
recovery_type,
|
||||||
|
show_info,
|
||||||
|
remaining_shares_info,
|
||||||
):
|
):
|
||||||
raise RecoveryAborted
|
raise RecoveryAborted
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Callable, Iterable
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import trezorui2
|
import trezorui2
|
||||||
from trezor import TR
|
from trezor import TR
|
||||||
@ -11,6 +11,9 @@ CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
|||||||
CANCELLED = trezorui2.CANCELLED # global_import_cache
|
CANCELLED = trezorui2.CANCELLED # global_import_cache
|
||||||
INFO = trezorui2.INFO # global_import_cache
|
INFO = trezorui2.INFO # global_import_cache
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from apps.management.recovery_device.layout import RemainingSharesInfo
|
||||||
|
|
||||||
|
|
||||||
async def request_word_count(recovery_type: RecoveryType) -> int:
|
async def request_word_count(recovery_type: RecoveryType) -> int:
|
||||||
selector = RustLayout(trezorui2.select_word_count(recovery_type=recovery_type))
|
selector = RustLayout(trezorui2.select_word_count(recovery_type=recovery_type))
|
||||||
@ -40,16 +43,18 @@ async def request_word(
|
|||||||
return word
|
return word
|
||||||
|
|
||||||
|
|
||||||
async def show_remaining_shares(
|
def format_remaining_shares_info(
|
||||||
groups: Iterable[tuple[int, tuple[str, ...]]], # remaining + list 3 words
|
remaining_shares_info: "RemainingSharesInfo",
|
||||||
shares_remaining: list[int],
|
) -> list[tuple[str, str]]:
|
||||||
group_threshold: int,
|
|
||||||
) -> None:
|
|
||||||
from trezor import strings
|
from trezor import strings
|
||||||
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
||||||
|
|
||||||
|
groups, shares_remaining, group_threshold = remaining_shares_info
|
||||||
|
|
||||||
pages: list[tuple[str, str]] = []
|
pages: list[tuple[str, str]] = []
|
||||||
for remaining, group in groups:
|
completed_groups = shares_remaining.count(0)
|
||||||
|
|
||||||
|
for group, remaining in zip(groups, shares_remaining):
|
||||||
if 0 < remaining < MAX_SHARE_COUNT:
|
if 0 < remaining < MAX_SHARE_COUNT:
|
||||||
title = strings.format_plural(
|
title = strings.format_plural(
|
||||||
TR.recovery__x_more_items_starting_template_plural,
|
TR.recovery__x_more_items_starting_template_plural,
|
||||||
@ -58,10 +63,8 @@ async def show_remaining_shares(
|
|||||||
)
|
)
|
||||||
words = "\n".join(group)
|
words = "\n".join(group)
|
||||||
pages.append((title, words))
|
pages.append((title, words))
|
||||||
elif (
|
elif remaining == MAX_SHARE_COUNT and completed_groups < group_threshold:
|
||||||
remaining == MAX_SHARE_COUNT and shares_remaining.count(0) < group_threshold
|
groups_remaining = group_threshold - completed_groups
|
||||||
):
|
|
||||||
groups_remaining = group_threshold - shares_remaining.count(0)
|
|
||||||
title = strings.format_plural(
|
title = strings.format_plural(
|
||||||
TR.recovery__x_more_items_starting_template_plural,
|
TR.recovery__x_more_items_starting_template_plural,
|
||||||
groups_remaining,
|
groups_remaining,
|
||||||
@ -70,13 +73,7 @@ async def show_remaining_shares(
|
|||||||
words = "\n".join(group)
|
words = "\n".join(group)
|
||||||
pages.append((title, words))
|
pages.append((title, words))
|
||||||
|
|
||||||
await raise_if_not_confirmed(
|
return pages
|
||||||
interact(
|
|
||||||
RustLayout(trezorui2.show_remaining_shares(pages=pages)),
|
|
||||||
"show_shares",
|
|
||||||
ButtonRequestType.Other,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def show_group_share_success(share_index: int, group_index: int) -> None:
|
async def show_group_share_success(share_index: int, group_index: int) -> None:
|
||||||
@ -102,26 +99,25 @@ async def continue_recovery(
|
|||||||
button_label: str, # unused on mercury
|
button_label: str, # unused on mercury
|
||||||
text: str,
|
text: str,
|
||||||
subtext: str | None,
|
subtext: str | None,
|
||||||
info_func: Callable | None, # TODO: see below
|
|
||||||
recovery_type: RecoveryType,
|
recovery_type: RecoveryType,
|
||||||
show_info: bool = False,
|
show_info: bool = False,
|
||||||
|
remaining_shares_info: "RemainingSharesInfo | None" = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
# TODO: info_func should be changed to return data to be shown (and not show
|
|
||||||
# them) so that individual models can implement showing logic on their own.
|
|
||||||
# T3T1 should move the data to `flow_continue_recovery` and hide them
|
|
||||||
# in the context menu
|
|
||||||
|
|
||||||
# NOTE: show_info can be understood as first screen before any shares
|
# NOTE: show_info can be understood as first screen before any shares
|
||||||
# NOTE: button request sent from the flow
|
# NOTE: button request sent from the flow
|
||||||
homepage = RustLayout(
|
result = await RustLayout(
|
||||||
trezorui2.flow_continue_recovery(
|
trezorui2.flow_continue_recovery(
|
||||||
first_screen=show_info,
|
first_screen=show_info,
|
||||||
recovery_type=recovery_type,
|
recovery_type=recovery_type,
|
||||||
text=text,
|
text=text,
|
||||||
subtext=subtext,
|
subtext=subtext,
|
||||||
|
pages=(
|
||||||
|
format_remaining_shares_info(remaining_shares_info)
|
||||||
|
if remaining_shares_info
|
||||||
|
else None
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
result = await homepage
|
|
||||||
return result is CONFIRMED
|
return result is CONFIRMED
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Callable, Iterable
|
from typing import TYPE_CHECKING, Iterable
|
||||||
|
|
||||||
import trezorui2
|
import trezorui2
|
||||||
from trezor import TR
|
from trezor import TR
|
||||||
@ -7,6 +7,9 @@ from trezor.enums import ButtonRequestType, RecoveryType
|
|||||||
from ..common import interact
|
from ..common import interact
|
||||||
from . import RustLayout, raise_if_not_confirmed, show_warning
|
from . import RustLayout, raise_if_not_confirmed, show_warning
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from apps.management.recovery_device.layout import RemainingSharesInfo
|
||||||
|
|
||||||
|
|
||||||
async def request_word_count(recovery_type: RecoveryType) -> int:
|
async def request_word_count(recovery_type: RecoveryType) -> int:
|
||||||
count = await interact(
|
count = await interact(
|
||||||
@ -97,9 +100,9 @@ async def continue_recovery(
|
|||||||
button_label: str,
|
button_label: str,
|
||||||
text: str,
|
text: str,
|
||||||
subtext: str | None,
|
subtext: str | None,
|
||||||
info_func: Callable | None,
|
|
||||||
recovery_type: RecoveryType,
|
recovery_type: RecoveryType,
|
||||||
show_info: bool = False,
|
show_info: bool = False,
|
||||||
|
remaining_shares_info: "RemainingSharesInfo | None" = None, # unused on TR
|
||||||
) -> bool:
|
) -> bool:
|
||||||
# TODO: implement info_func?
|
# TODO: implement info_func?
|
||||||
# There is very limited space on the screen
|
# There is very limited space on the screen
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Callable, Iterable
|
from typing import TYPE_CHECKING, Callable
|
||||||
|
|
||||||
import trezorui2
|
import trezorui2
|
||||||
from trezor import TR
|
from trezor import TR
|
||||||
@ -10,6 +10,9 @@ from . import RustLayout, raise_if_not_confirmed
|
|||||||
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
CONFIRMED = trezorui2.CONFIRMED # global_import_cache
|
||||||
INFO = trezorui2.INFO # global_import_cache
|
INFO = trezorui2.INFO # global_import_cache
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from apps.management.recovery_device.layout import RemainingSharesInfo
|
||||||
|
|
||||||
|
|
||||||
async def _homepage_with_info(
|
async def _homepage_with_info(
|
||||||
dialog: RustLayout,
|
dialog: RustLayout,
|
||||||
@ -54,7 +57,7 @@ async def request_word(
|
|||||||
|
|
||||||
|
|
||||||
async def show_remaining_shares(
|
async def show_remaining_shares(
|
||||||
groups: Iterable[tuple[int, tuple[str, ...]]], # remaining + list 3 words
|
groups: set[tuple[str, ...]],
|
||||||
shares_remaining: list[int],
|
shares_remaining: list[int],
|
||||||
group_threshold: int,
|
group_threshold: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -62,7 +65,9 @@ async def show_remaining_shares(
|
|||||||
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
from trezor.crypto.slip39 import MAX_SHARE_COUNT
|
||||||
|
|
||||||
pages: list[tuple[str, str]] = []
|
pages: list[tuple[str, str]] = []
|
||||||
for remaining, group in groups:
|
completed_groups = shares_remaining.count(0)
|
||||||
|
|
||||||
|
for group, remaining in zip(groups, shares_remaining):
|
||||||
if 0 < remaining < MAX_SHARE_COUNT:
|
if 0 < remaining < MAX_SHARE_COUNT:
|
||||||
title = strings.format_plural(
|
title = strings.format_plural(
|
||||||
TR.recovery__x_more_items_starting_template_plural,
|
TR.recovery__x_more_items_starting_template_plural,
|
||||||
@ -71,10 +76,8 @@ async def show_remaining_shares(
|
|||||||
)
|
)
|
||||||
words = "\n".join(group)
|
words = "\n".join(group)
|
||||||
pages.append((title, words))
|
pages.append((title, words))
|
||||||
elif (
|
elif remaining == MAX_SHARE_COUNT and completed_groups < group_threshold:
|
||||||
remaining == MAX_SHARE_COUNT and shares_remaining.count(0) < group_threshold
|
groups_remaining = group_threshold - completed_groups
|
||||||
):
|
|
||||||
groups_remaining = group_threshold - shares_remaining.count(0)
|
|
||||||
title = strings.format_plural(
|
title = strings.format_plural(
|
||||||
TR.recovery__x_more_items_starting_template_plural,
|
TR.recovery__x_more_items_starting_template_plural,
|
||||||
groups_remaining,
|
groups_remaining,
|
||||||
@ -139,9 +142,9 @@ async def continue_recovery(
|
|||||||
button_label: str,
|
button_label: str,
|
||||||
text: str,
|
text: str,
|
||||||
subtext: str | None,
|
subtext: str | None,
|
||||||
info_func: Callable | None,
|
|
||||||
recovery_type: RecoveryType,
|
recovery_type: RecoveryType,
|
||||||
show_info: bool = False,
|
show_info: bool = False,
|
||||||
|
remaining_shares_info: "RemainingSharesInfo | None" = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
from trezor.wire import ActionCancelled
|
from trezor.wire import ActionCancelled
|
||||||
|
|
||||||
@ -160,19 +163,26 @@ async def continue_recovery(
|
|||||||
description=description,
|
description=description,
|
||||||
button=button_label,
|
button=button_label,
|
||||||
recovery_type=recovery_type,
|
recovery_type=recovery_type,
|
||||||
info_button=info_func is not None,
|
info_button=remaining_shares_info is not None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
await button_request("recovery", ButtonRequestType.RecoveryHomepage)
|
await button_request("recovery", ButtonRequestType.RecoveryHomepage)
|
||||||
|
|
||||||
result = (
|
if remaining_shares_info is None:
|
||||||
await homepage
|
result = await homepage
|
||||||
if info_func is None
|
else:
|
||||||
else await _homepage_with_info(homepage, info_func)
|
groups, shares_remaining, group_threshold = remaining_shares_info
|
||||||
)
|
result = await _homepage_with_info(
|
||||||
|
homepage,
|
||||||
|
lambda: show_remaining_shares(
|
||||||
|
groups, shares_remaining, group_threshold
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if result is CONFIRMED:
|
if result is CONFIRMED:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await _confirm_abort(recovery_type != RecoveryType.NormalRecovery)
|
await _confirm_abort(recovery_type != RecoveryType.NormalRecovery)
|
||||||
except ActionCancelled:
|
except ActionCancelled:
|
||||||
|
@ -73,7 +73,6 @@ def test_secret(client: Client, shares: list[str], secret: str):
|
|||||||
_test_secret(client, shares, secret)
|
_test_secret(client, shares, secret)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip_t3t1(reason="currently broken on T3T1")
|
|
||||||
@pytest.mark.parametrize("shares, secret", VECTORS)
|
@pytest.mark.parametrize("shares, secret", VECTORS)
|
||||||
@pytest.mark.setup_client(uninitialized=True)
|
@pytest.mark.setup_client(uninitialized=True)
|
||||||
def test_secret_click_info_button(client: Client, shares: list[str], secret: str):
|
def test_secret_click_info_button(client: Client, shares: list[str], secret: str):
|
||||||
|
@ -304,7 +304,7 @@ class RecoveryFlow:
|
|||||||
if self.client.model is models.T2T1:
|
if self.client.model is models.T2T1:
|
||||||
yield from self.tt_click_info()
|
yield from self.tt_click_info()
|
||||||
elif self.client.model is models.T3T1:
|
elif self.client.model is models.T3T1:
|
||||||
self.mercury_click_info()
|
yield from self.mercury_click_info()
|
||||||
yield from self.success_more_shares_needed()
|
yield from self.success_more_shares_needed()
|
||||||
|
|
||||||
def tt_click_info(
|
def tt_click_info(
|
||||||
@ -316,10 +316,19 @@ class RecoveryFlow:
|
|||||||
self.debug.swipe_up()
|
self.debug.swipe_up()
|
||||||
self.debug.press_yes()
|
self.debug.press_yes()
|
||||||
|
|
||||||
def mercury_click_info(self):
|
def mercury_click_info(self) -> BRGeneratorType:
|
||||||
|
# Starting on the homepage, handle the repeated button request
|
||||||
|
br = yield
|
||||||
|
assert br.name == "recovery"
|
||||||
|
assert br.code == B.RecoveryHomepage
|
||||||
|
# Moving through the menu into the show_shares screen
|
||||||
self.debug.click(buttons.CORNER_BUTTON, wait=True)
|
self.debug.click(buttons.CORNER_BUTTON, wait=True)
|
||||||
self.debug.synchronize_at("VerticalMenu")
|
self.debug.synchronize_at("VerticalMenu")
|
||||||
self.debug.click(buttons.VERTICAL_MENU[0], wait=True)
|
self.debug.click(buttons.VERTICAL_MENU[0], wait=True)
|
||||||
|
br = yield
|
||||||
|
assert br.name == "show_shares"
|
||||||
|
assert br.code == B.Other
|
||||||
|
# Getting back to the homepage
|
||||||
self.debug.click(buttons.CORNER_BUTTON, wait=True)
|
self.debug.click(buttons.CORNER_BUTTON, wait=True)
|
||||||
self.debug.click(buttons.CORNER_BUTTON, wait=True)
|
self.debug.click(buttons.CORNER_BUTTON, wait=True)
|
||||||
|
|
||||||
|
@ -16511,6 +16511,8 @@
|
|||||||
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "f207194fc64f068391f521c2069ef1fcf55c581c1c241c49c620f9462557fcf1",
|
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "f207194fc64f068391f521c2069ef1fcf55c581c1c241c49c620f9462557fcf1",
|
||||||
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "ad415d9e066ae572abc1e81df83758f1eb884aa75d33464ad5cf393edd17c2ac",
|
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "ad415d9e066ae572abc1e81df83758f1eb884aa75d33464ad5cf393edd17c2ac",
|
||||||
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "e8a1825029d6cd6fccf2d9433df5153cc7494c00350c0cab09300fc05bff8a87",
|
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "e8a1825029d6cd6fccf2d9433df5153cc7494c00350c0cab09300fc05bff8a87",
|
||||||
|
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "987214495bb12d72ef099fcf6a5e3e671eda37ed5905e552256c300a7934d919",
|
||||||
|
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "2a1e35ba924989ed4d0aa87b9ddfd61ff91084d142bb517a255c517e82f8463f",
|
||||||
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "06b9b90d151e8eb32f824b25c379e38dccd2732592a9d54cbe963b75b88cef24",
|
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "06b9b90d151e8eb32f824b25c379e38dccd2732592a9d54cbe963b75b88cef24",
|
||||||
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "908dfde70e1269935c7add3c2edaea223724fc78bc8e030e5aeb08dd4fd0ec9f",
|
"T3T1_cs_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "908dfde70e1269935c7add3c2edaea223724fc78bc8e030e5aeb08dd4fd0ec9f",
|
||||||
"T3T1_cs_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "65a86c715390ceca75c9eb2f56a54bf85e2b915489bc080ca45f15a69c5dc55d",
|
"T3T1_cs_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "65a86c715390ceca75c9eb2f56a54bf85e2b915489bc080ca45f15a69c5dc55d",
|
||||||
@ -17853,6 +17855,8 @@
|
|||||||
"T3T1_de_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "d585bf013c0c6526bd9dc17f33bb21c0de19e2d5e653159d5821921f32da2705",
|
"T3T1_de_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "d585bf013c0c6526bd9dc17f33bb21c0de19e2d5e653159d5821921f32da2705",
|
||||||
"T3T1_de_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "03437b32a72c828dbb6365be52090ca3bdabdc969cd8e238b88b8fff868b0417",
|
"T3T1_de_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "03437b32a72c828dbb6365be52090ca3bdabdc969cd8e238b88b8fff868b0417",
|
||||||
"T3T1_de_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "aca43fcd50b60741f3dda0e06adb01d74c41cf92853368759c476e6fe55326a9",
|
"T3T1_de_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "aca43fcd50b60741f3dda0e06adb01d74c41cf92853368759c476e6fe55326a9",
|
||||||
|
"T3T1_de_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "a8b0d2f295238aa40db569320fd715a017f98244887d3366d0a9b7a4ac5460cd",
|
||||||
|
"T3T1_de_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "379fc0b4041a4085e2cb41dfd922c1dcddaca4a08da9afe6e72dd0fce01137e4",
|
||||||
"T3T1_de_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "af836476467f13d07ca7689fef5e9703e85f0ec489c0f3528b5fa9ccc9531041",
|
"T3T1_de_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "af836476467f13d07ca7689fef5e9703e85f0ec489c0f3528b5fa9ccc9531041",
|
||||||
"T3T1_de_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "f238d4b9f4f6d13fb082a2b048a7aed6a3f25281ce637d973f3a3cdfedbb5909",
|
"T3T1_de_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "f238d4b9f4f6d13fb082a2b048a7aed6a3f25281ce637d973f3a3cdfedbb5909",
|
||||||
"T3T1_de_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "2fc4b3a3ab644a339238a7291a162b1e1fbed9714cae4de49b7e1e10814af2bf",
|
"T3T1_de_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "2fc4b3a3ab644a339238a7291a162b1e1fbed9714cae4de49b7e1e10814af2bf",
|
||||||
@ -19195,6 +19199,8 @@
|
|||||||
"T3T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "fb29b0dcf08f0ac969197d48ab9547850846959d09ac215aa5a831b2432e3d3b",
|
"T3T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "fb29b0dcf08f0ac969197d48ab9547850846959d09ac215aa5a831b2432e3d3b",
|
||||||
"T3T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "e7748f6864c05f4a81677d0c70baf12c16eb3463f1f6f1f6caf99b89752318ca",
|
"T3T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "e7748f6864c05f4a81677d0c70baf12c16eb3463f1f6f1f6caf99b89752318ca",
|
||||||
"T3T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "f26e7ad1434f037331503f436e82780c7f3e4c9d6389b064680f1098d82fb2fc",
|
"T3T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "f26e7ad1434f037331503f436e82780c7f3e4c9d6389b064680f1098d82fb2fc",
|
||||||
|
"T3T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "6b1dab1a11ae8853e1dbfbdea8362843f9b31b069824f6484d6e8cb00eeaa1f0",
|
||||||
|
"T3T1_en_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "b9a12d0387b2f74769366176b53fe405e557f4af329fb4dcd3b2eecccf8ad07a",
|
||||||
"T3T1_en_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "ff9fd97ab6ca805596bba50ff532a5decded71da4888456d6bc4f6aa6992cc4a",
|
"T3T1_en_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "ff9fd97ab6ca805596bba50ff532a5decded71da4888456d6bc4f6aa6992cc4a",
|
||||||
"T3T1_en_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "49149d6d43bbf9fd59f92685569e541fc01a8765e60580518a9b75063bad89ba",
|
"T3T1_en_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "49149d6d43bbf9fd59f92685569e541fc01a8765e60580518a9b75063bad89ba",
|
||||||
"T3T1_en_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "a23cd874215a2944ef6282f9e396715d3f629b9afa3da0dd534049ce6cc323cd",
|
"T3T1_en_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "a23cd874215a2944ef6282f9e396715d3f629b9afa3da0dd534049ce6cc323cd",
|
||||||
@ -20537,6 +20543,8 @@
|
|||||||
"T3T1_es_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "bfcc90188c0e704f2b2ee4afd7c616e116493bf0f9ef08aeb006c8eae23e3130",
|
"T3T1_es_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "bfcc90188c0e704f2b2ee4afd7c616e116493bf0f9ef08aeb006c8eae23e3130",
|
||||||
"T3T1_es_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "c86b3f84b601aa162ebfa59e49f7a83ece8b3d03f780ca6cffba01e257f9e631",
|
"T3T1_es_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "c86b3f84b601aa162ebfa59e49f7a83ece8b3d03f780ca6cffba01e257f9e631",
|
||||||
"T3T1_es_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "739e3359b4c0d22da0ce39c1949d36e50942fa6855941c19bb5ef8a33b10e63a",
|
"T3T1_es_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "739e3359b4c0d22da0ce39c1949d36e50942fa6855941c19bb5ef8a33b10e63a",
|
||||||
|
"T3T1_es_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "d8b4392d7b9c05436504d714e5b5be17229afe0020db212d5bad30618570ae52",
|
||||||
|
"T3T1_es_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "e223d9a2b11fb83b76dc6266c285d9a853db04fbeeecd5d794206042a06159e6",
|
||||||
"T3T1_es_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "95dc3c3561517b60adb1c2021ea23b9ec11e6b63851e11b4586fbf1762bea32b",
|
"T3T1_es_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "95dc3c3561517b60adb1c2021ea23b9ec11e6b63851e11b4586fbf1762bea32b",
|
||||||
"T3T1_es_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "83bbf55f0a13974b9f7832554aa3019ae16d56555f017ecb5130c7594637e775",
|
"T3T1_es_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "83bbf55f0a13974b9f7832554aa3019ae16d56555f017ecb5130c7594637e775",
|
||||||
"T3T1_es_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "e046d946894f3308666456016642ae88826ddcfb4253e7d90f29e410b3dc14fe",
|
"T3T1_es_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "e046d946894f3308666456016642ae88826ddcfb4253e7d90f29e410b3dc14fe",
|
||||||
@ -21879,6 +21887,8 @@
|
|||||||
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "c2a537f28eac80fcf674c4f2c1f00c82dd618b06d5b030ad86cf5a7f54178c27",
|
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced.py::test_same_share": "c2a537f28eac80fcf674c4f2c1f00c82dd618b06d5b030ad86cf5a7f54178c27",
|
||||||
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "fc949c187da623bf33b5bfdb034d9933d3edd0551b4355f70c6347420989939d",
|
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f1-afc2dad5": "fc949c187da623bf33b5bfdb034d9933d3edd0551b4355f70c6347420989939d",
|
||||||
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "e8a0a40d53e0f359d1b4178bbf14d7a372f4f56918819fdd9ed5806d515e1b7a",
|
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0-eb47093e": "e8a0a40d53e0f359d1b4178bbf14d7a372f4f56918819fdd9ed5806d515e1b7a",
|
||||||
|
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares0-c2d2-850ffa77": "804b8a27ecdde765cf636c8358f30cb51d1cbfa9020c2ed1690bb881d9e6d649",
|
||||||
|
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced.py::test_secret_click_info_button[shares1-c41d-ca9ddec8": "0eee15f6168a094643bd299c9d7a9d5e5942340ee46a27556b0efb081110ab73",
|
||||||
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "3c43ed6e7e58eabe8b7db49124c2ec0202fcfc3f50cb15449e24ffeb716c090d",
|
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_dryrun": "3c43ed6e7e58eabe8b7db49124c2ec0202fcfc3f50cb15449e24ffeb716c090d",
|
||||||
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "08306b13088a2fcca5beaeb6b97c4071a149b399fb607b82e1c57352d4ba78e0",
|
"T3T1_fr_reset_recovery-test_recovery_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "08306b13088a2fcca5beaeb6b97c4071a149b399fb607b82e1c57352d4ba78e0",
|
||||||
"T3T1_fr_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "7eca9e95d1798c4762fd052d976eb080374b7e8cf96ce1fc01b7f9ad25717bcf",
|
"T3T1_fr_reset_recovery-test_recovery_slip39_basic.py::test_1of1": "7eca9e95d1798c4762fd052d976eb080374b7e8cf96ce1fc01b7f9ad25717bcf",
|
||||||
|
Loading…
Reference in New Issue
Block a user