diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 5fc2d0da5..3640e9ef8 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -502,6 +502,7 @@ static void _librust_qstrs(void) { MP_QSTR_reset__only_one_share_will_be_created; MP_QSTR_reset__recovery_share_title_template; MP_QSTR_reset__recovery_wallet_backup_title; + MP_QSTR_reset__repeat_for_all_shares; MP_QSTR_reset__required_number_of_groups; MP_QSTR_reset__select_correct_word; MP_QSTR_reset__select_threshold; @@ -541,6 +542,7 @@ static void _librust_qstrs(void) { MP_QSTR_reset__tos_link; MP_QSTR_reset__total_number_of_shares_in_group_template; MP_QSTR_reset__use_your_backup; + MP_QSTR_reset__words_may_repeat; MP_QSTR_reset__words_written_down_template; MP_QSTR_reset__write_down_words_template; MP_QSTR_reset__wrong_word_selected; diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs index e037ae623..476d249a5 100644 --- a/core/embed/rust/src/translations/generated/translated_string.rs +++ b/core/embed/rust/src/translations/generated/translated_string.rs @@ -1334,6 +1334,8 @@ pub enum TranslatedString { recovery__unlock_repeated_backup = 934, // "Create additional backup?" recovery__unlock_repeated_backup_verb = 935, // "Unlock backup" homescreen__set_default = 936, // "Do you really want to set default homescreen image?" + reset__words_may_repeat = 937, // "Words may repeat." + reset__repeat_for_all_shares = 938, // "Repeat for all shares." } impl TranslatedString { @@ -2663,6 +2665,8 @@ impl TranslatedString { Self::recovery__unlock_repeated_backup => "Create additional backup?", Self::recovery__unlock_repeated_backup_verb => "Unlock backup", Self::homescreen__set_default => "Do you really want to set default homescreen image?", + Self::reset__words_may_repeat => "Words may repeat.", + Self::reset__repeat_for_all_shares => "Repeat for all shares.", } } @@ -3993,6 +3997,8 @@ impl TranslatedString { Qstr::MP_QSTR_recovery__unlock_repeated_backup => Some(Self::recovery__unlock_repeated_backup), Qstr::MP_QSTR_recovery__unlock_repeated_backup_verb => Some(Self::recovery__unlock_repeated_backup_verb), Qstr::MP_QSTR_homescreen__set_default => Some(Self::homescreen__set_default), + Qstr::MP_QSTR_reset__words_may_repeat => Some(Self::reset__words_may_repeat), + Qstr::MP_QSTR_reset__repeat_for_all_shares => Some(Self::reset__repeat_for_all_shares), _ => None, } } diff --git a/core/embed/rust/src/ui/model_mercury/flow/show_share_words.rs b/core/embed/rust/src/ui/model_mercury/flow/show_share_words.rs index e016aec4e..96e0bd317 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/show_share_words.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/show_share_words.rs @@ -1,13 +1,13 @@ use crate::{ error, - micropython::{map::Map, obj::Obj, qstr::Qstr, util}, + micropython::{iter::IterBuf, map::Map, obj::Obj, qstr::Qstr, util}, strutil::TString, translations::TR, ui::{ button_request::ButtonRequestCode, component::{ swipe_detect::SwipeSettings, - text::paragraphs::{Paragraph, Paragraphs}, + text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, ButtonRequestExt, ComponentExt, SwipeDirection, }, flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, @@ -24,7 +24,6 @@ use super::super::{ #[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] pub enum ShowShareWords { - // TODO: potentially also add there the 'never put anywhere digital' warning? Instruction, Words, Confirm, @@ -80,16 +79,23 @@ impl ShowShareWords { let subtitle: TString = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into()?; let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_words)?; let share_words_vec: Vec = util::iter_into_vec(share_words_obj)?; - let text_info: TString = kwargs.get(Qstr::MP_QSTR_text_info)?.try_into()?; + let text_info: Obj = kwargs.get(Qstr::MP_QSTR_text_info)?; let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?; let nwords = share_words_vec.len(); + let mut instructions_paragraphs = ParagraphVecShort::new(); + for item in IterBuf::new().try_iterate(text_info)? { + let text: TString = item.try_into()?; + instructions_paragraphs.add(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, text)); + } + let paragraphs_spacing = 8; let content_instruction = Frame::left_aligned( title, - SwipeContent::new(Paragraphs::new(Paragraph::new( - &theme::TEXT_MAIN_GREY_LIGHT, - text_info, - ))), + SwipeContent::new( + instructions_paragraphs + .into_paragraphs() + .with_spacing(paragraphs_spacing), + ), ) .with_subtitle(TR::words__instructions.into()) .with_footer(TR::instructions__swipe_up.into(), None) diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index fb1581e3f..48a22a656 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -1688,7 +1688,7 @@ pub static mp_module_trezorui2: Module = obj_module! { /// title: str, /// subtitle: str, /// words: Iterable[str], - /// text_info: str, + /// text_info: Iterable[str], /// text_confirm: str, /// ) -> LayoutObj[UiResult]: /// """Show wallet backup words preceded by an instruction screen and followed by diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index caf2c398e..8318286ab 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -402,7 +402,7 @@ def flow_show_share_words( title: str, subtitle: str, words: Iterable[str], - text_info: str, + text_info: Iterable[str], text_confirm: str, ) -> LayoutObj[UiResult]: """Show wallet backup words preceded by an instruction screen and followed by diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi index 701cb3be4..74bd4bf26 100644 --- a/core/mocks/trezortranslate_keys.pyi +++ b/core/mocks/trezortranslate_keys.pyi @@ -638,6 +638,7 @@ class TR: reset__only_one_share_will_be_created: str = "Only one share will be created." reset__recovery_share_title_template: str = "Recovery share #{0}" reset__recovery_wallet_backup_title: str = "Wallet backup" + reset__repeat_for_all_shares: str = "Repeat for all shares." reset__required_number_of_groups: str = "The required number of groups for recovery." reset__select_correct_word: str = "Select the correct word for each position." reset__select_threshold: str = "Select the minimum shares required to recover your wallet." @@ -676,6 +677,7 @@ class TR: reset__tos_link: str = "trezor.io/tos" reset__total_number_of_shares_in_group_template: str = "Set the total number of shares in Group {0}." reset__use_your_backup: str = "Use your backup when you need to recover your wallet." + reset__words_may_repeat: str = "Words may repeat." reset__words_written_down_template: str = "I wrote down all {0} words in order." reset__write_down_words_template: str = "Write the following {0} words in order on your wallet backup card." reset__wrong_word_selected: str = "Wrong word selected!" diff --git a/core/src/trezor/ui/layouts/mercury/reset.py b/core/src/trezor/ui/layouts/mercury/reset.py index dcfd45688..ef5de17cd 100644 --- a/core/src/trezor/ui/layouts/mercury/reset.py +++ b/core/src/trezor/ui/layouts/mercury/reset.py @@ -32,7 +32,13 @@ async def show_share_words( group_index + 1, share_index + 1 ) words_count = len(share_words) - text_info = TR.reset__write_down_words_template.format(words_count) + text_info = [TR.reset__write_down_words_template.format(words_count)] + if words_count == 20 and share_index is None: + # 1-of-1 SLIP39: inform the user about repeated words + text_info.append(TR.reset__words_may_repeat) + if share_index == 0: + # regular SLIP39, 1st share + text_info.append(TR.reset__repeat_for_all_shares) text_confirm = TR.reset__words_written_down_template.format(words_count) result = await RustLayout( diff --git a/core/translations/en.json b/core/translations/en.json index 841f6920d..f04fe0045 100644 --- a/core/translations/en.json +++ b/core/translations/en.json @@ -642,6 +642,7 @@ "reset__only_one_share_will_be_created": "Only one share will be created.", "reset__recovery_share_title_template": "Recovery share #{0}", "reset__recovery_wallet_backup_title": "Wallet backup", + "reset__repeat_for_all_shares": "Repeat for all shares.", "reset__required_number_of_groups": "The required number of groups for recovery.", "reset__select_correct_word": "Select the correct word for each position.", "reset__select_threshold": "Select the minimum shares required to recover your wallet.", @@ -680,6 +681,7 @@ "reset__tos_link": "trezor.io/tos", "reset__total_number_of_shares_in_group_template": "Set the total number of shares in Group {0}.", "reset__use_your_backup": "Use your backup when you need to recover your wallet.", + "reset__words_may_repeat": "Words may repeat.", "reset__words_written_down_template": "I wrote down all {0} words in order.", "reset__write_down_words_template": "Write the following {0} words in order on your wallet backup card.", "reset__wrong_word_selected": "Wrong word selected!", @@ -942,4 +944,4 @@ "words__writable": "Writable", "words__yes": "Yes" } -} \ No newline at end of file +} diff --git a/core/translations/order.json b/core/translations/order.json index 54dd2a1b6..b42c337a0 100644 --- a/core/translations/order.json +++ b/core/translations/order.json @@ -935,5 +935,7 @@ "933": "recovery__title_unlock_repeated_backup", "934": "recovery__unlock_repeated_backup", "935": "recovery__unlock_repeated_backup_verb", - "936": "homescreen__set_default" + "936": "homescreen__set_default", + "937": "reset__words_may_repeat", + "938": "reset__repeat_for_all_shares" } diff --git a/core/translations/signatures.json b/core/translations/signatures.json index 32d271486..eb05fe000 100644 --- a/core/translations/signatures.json +++ b/core/translations/signatures.json @@ -1,8 +1,8 @@ { "current": { - "merkle_root": "7a115e582a5f5f09b1850946030762360f55e516a60cf960dffc9ba174c2e4d2", - "datetime": "2024-06-02T11:07:12.183601", - "commit": "66496206ccbe9583203fbfebf8c9222e8c6379b8" + "merkle_root": "405ce2be8dcb492a1d4b25ec8aaeaa9914ff88594151beaceea52315172b11d0", + "datetime": "2024-06-03T12:40:07.498873", + "commit": "2ac4967ef190dac1fe238cc00878ff2d08e17bb5" }, "history": [ { diff --git a/tests/input_flows.py b/tests/input_flows.py index aba5d4d08..f2b7babb1 100644 --- a/tests/input_flows.py +++ b/tests/input_flows.py @@ -1554,7 +1554,9 @@ class InputFlowSlip39BasicResetRecovery(InputFlowBase): # 6. threshold info # 7. Set & confirm threshold value # 8. Confirm show seeds - yield from click_through(self.debug, screens=9, code=B.ResetDevice) + # 9. Warning + # 10. Instructions + yield from click_through(self.debug, screens=10, code=B.ResetDevice) # Mnemonic phrases self.mnemonics = yield from load_N_shares(self.debug, 5)