fix(core): differentiate wipe code from pin set up
What changed, and why it matters
This commit fixes a user-interface labeling bug where setting up a 'wipe code' (a special code that erases the device) was incorrectly shown using the same on-screen text and flow as setting up a regular PIN. The change renames the confirmation flow, adds a flag to distinguish wipe-code setup from PIN setup, and shows wipe-code-specific cancellation text on newer Trezor layouts. It is a UI clarity fix, not a cryptographic or access-control flaw.
Treat as a low-severity UI/UX fix. No urgent security response is indicated, but verify that the new `is_wipe_code` flag is passed correctly on all supported device variants and that translation signatures are valid for production builds.
Security signals we found
UI text mismatch between wipe-code setup and PIN setup is corrected
New `is_wipe_code` parameter added to Rust UI API and Python layouts
Wipe-code-specific cancellation string introduced in translations
No changes to PIN/wipe-code validation, storage, or cryptography
No privilege escalation, buffer overflow, or memory-safety issues visible
Evidence from the diff
The patch generalizes the flow_confirm_set_new_pin UI flow into flow_confirm_set_new_code and threads an is_wipe_code boolean through the Rust UI API, the trait FirmwareUI, and the Python management apps (change_pin.py, change_wipe_code.py). For the Delizia and Eckhart layouts it replaces PIN-only cancel text with a new wipe_code__cancel_setup translation when is_wipe_code is true. Bolt and Caesar receive the parameter but do not change behavior (Bolt raises NotImplementedError; Caesar already used a string check). The commit also updates translation files and their signatures.
Changed components
core/src/apps/management/change_pin.pycore/src/apps/management/change_wipe_code.pycore/src/trezor/ui/layouts/bolt/__init__.pycore/src/trezor/ui/layouts/caesar/__init__.pycore/src/trezor/ui/layouts/delizia/__init__.pycore/src/trezor/ui/layouts/eckhart/__init__.pycore/embed/rust/src/ui/api/firmware_micropython.rscore/embed/rust/src/ui/layout_delizia/flow/confirm_set_new_code.rscore/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rscore/embed/rust/src/ui/ui_firmware.rscore/translations/en.jsonInspect captured patch +303 / −259
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index 5fe9100b..fb7d5ab6 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -338,7 +338,7 @@ static void _librust_qstrs(void) {
MP_QSTR_firmware_update__title;
MP_QSTR_firmware_update__title_fingerprint;
MP_QSTR_flow_confirm_output;
- MP_QSTR_flow_confirm_set_new_pin;
+ MP_QSTR_flow_confirm_set_new_code;
MP_QSTR_flow_get_address;
MP_QSTR_flow_get_pubkey;
MP_QSTR_get;
@@ -415,6 +415,7 @@ static void _librust_qstrs(void) {
MP_QSTR_is_pairing_requested;
MP_QSTR_is_started;
MP_QSTR_is_type_of;
+ MP_QSTR_is_wipe_code;
MP_QSTR_items;
MP_QSTR_joint__title;
MP_QSTR_joint__to_the_total_amount;
@@ -928,6 +929,7 @@ static void _librust_qstrs(void) {
MP_QSTR_wipe__title;
MP_QSTR_wipe__want_to_wipe;
MP_QSTR_wipe_code;
+ MP_QSTR_wipe_code__cancel_setup;
MP_QSTR_wipe_code__change;
MP_QSTR_wipe_code__change_question;
MP_QSTR_wipe_code__changed;
diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs
index 1bb74e2c..e4f141f0 100644
--- a/core/embed/rust/src/translations/generated/translated_string.rs
+++ b/core/embed/rust/src/translations/generated/translated_string.rs
@@ -1548,6 +1548,7 @@ pub enum TranslatedString {
pin__wipe_code_exists_description = 1156, // "Wipe code must be turned off before turning off PIN protection."
pin__wipe_code_exists_title = 1157, // "Wipe code set"
wipe_code__pin_not_set_description = 1158, // "PIN must be set before enabling wipe code."
+ wipe_code__cancel_setup = 1159, // {"Bolt": "Cancel wipe code setup", "Caesar": "Cancel wipe code setup", "Delizia": "Cancel wipe code setup", "Eckhart": "Cancel wipe code setup?"}
}
impl TranslatedString {
@@ -3533,6 +3534,14 @@ impl TranslatedString {
(Self::pin__wipe_code_exists_description, "Wipe code must be turned off before turning off PIN protection."),
(Self::pin__wipe_code_exists_title, "Wipe code set"),
(Self::wipe_code__pin_not_set_description, "PIN must be set before enabling wipe code."),
+ #[cfg(feature = "layout_bolt")]
+ (Self::wipe_code__cancel_setup, "Cancel wipe code setup"),
+ #[cfg(feature = "layout_caesar")]
+ (Self::wipe_code__cancel_setup, "Cancel wipe code setup"),
+ #[cfg(feature = "layout_delizia")]
+ (Self::wipe_code__cancel_setup, "Cancel wipe code setup"),
+ #[cfg(feature = "layout_eckhart")]
+ (Self::wipe_code__cancel_setup, "Cancel wipe code setup?"),
];
#[cfg(feature = "micropython")]
@@ -4965,6 +4974,7 @@ impl TranslatedString {
(Qstr::MP_QSTR_wipe__start_again, Self::wipe__start_again),
(Qstr::MP_QSTR_wipe__title, Self::wipe__title),
(Qstr::MP_QSTR_wipe__want_to_wipe, Self::wipe__want_to_wipe),
+ (Qstr::MP_QSTR_wipe_code__cancel_setup, Self::wipe_code__cancel_setup),
(Qstr::MP_QSTR_wipe_code__change, Self::wipe_code__change),
(Qstr::MP_QSTR_wipe_code__change_question, Self::wipe_code__change_question),
(Qstr::MP_QSTR_wipe_code__changed, Self::wipe_code__changed),
diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs
index 122d42b8..49226909 100644
--- a/core/embed/rust/src/ui/api/firmware_micropython.rs
+++ b/core/embed/rust/src/ui/api/firmware_micropython.rs
@@ -571,7 +571,7 @@ extern "C" fn new_flow_confirm_output(n_args: usize, args: *const Obj, kwargs: *
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
-extern "C" fn new_flow_confirm_set_new_pin(
+extern "C" fn new_flow_confirm_set_new_code(
n_args: usize,
args: *const Obj,
kwargs: *mut Map,
@@ -579,8 +579,9 @@ extern "C" fn new_flow_confirm_set_new_pin(
let block = move |_args: &[Obj], kwargs: &Map| {
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
+ let is_wipe_code: bool = kwargs.get(Qstr::MP_QSTR_is_wipe_code)?.try_into()?;
- let layout = ModelUI::flow_confirm_set_new_pin(title, description)?;
+ let layout = ModelUI::flow_confirm_set_new_code(title, description, is_wipe_code)?;
Ok(LayoutObj::new_root(layout)?.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@@ -1688,15 +1689,14 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// """Confirm the recipient, (optionally) confirm the amount and (optionally) confirm the summary and present a Hold to Sign page."""
Qstr::MP_QSTR_flow_confirm_output => obj_fn_kw!(0, new_flow_confirm_output).as_obj(),
- // TODO: supply more arguments for Wipe code setting (delizia)
- ///
- /// def flow_confirm_set_new_pin(
+ /// def flow_confirm_set_new_code(
/// *,
/// title: str,
/// description: str,
+ /// is_wipe_code: bool,
/// ) -> LayoutObj[UiResult]:
- /// """Confirm new PIN setup with an option to cancel action."""
- Qstr::MP_QSTR_flow_confirm_set_new_pin => obj_fn_kw!(0, new_flow_confirm_set_new_pin).as_obj(),
+ /// """Confirm new PIN/wipe code setup with an option to cancel action."""
+ Qstr::MP_QSTR_flow_confirm_set_new_code => obj_fn_kw!(0, new_flow_confirm_set_new_code).as_obj(),
/// def flow_get_address(
/// *,
diff --git a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
index 5cf1e05d..e23305a8 100644
--- a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
@@ -601,9 +601,10 @@ impl FirmwareUI for UIBolt {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::NotImplementedError)
}
- fn flow_confirm_set_new_pin(
+ fn flow_confirm_set_new_code(
_title: TString<'static>,
_description: TString<'static>,
+ _is_wipe_code: bool,
) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::NotImplementedError)
}
diff --git a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
index 28c5caaf..7ef29516 100644
--- a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
@@ -730,9 +730,10 @@ impl FirmwareUI for UICaesar {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::NotImplementedError)
}
- fn flow_confirm_set_new_pin(
+ fn flow_confirm_set_new_code(
_title: TString<'static>,
_description: TString<'static>,
+ _is_wipe_code: bool,
) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::NotImplementedError)
}
diff --git a/core/embed/rust/src/ui/layout_delizia/flow/confirm_set_new_code.rs b/core/embed/rust/src/ui/layout_delizia/flow/confirm_set_new_code.rs
new file mode 100644
index 00000000..73d6869e
--- /dev/null
+++ b/core/embed/rust/src/ui/layout_delizia/flow/confirm_set_new_code.rs
@@ -0,0 +1,105 @@
+use crate::{
+ error,
+ strutil::TString,
+ translations::TR,
+ ui::{
+ component::{
+ swipe_detect::SwipeSettings,
+ text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs},
+ },
+ flow::{
+ base::{Decision, DecisionBuilder as _},
+ FlowController, FlowMsg, SwipeFlow,
+ },
+ geometry::Direction,
+ },
+};
+
+use super::super::{
+ component::{Frame, PromptScreen, SwipeContent, VerticalMenu},
+ theme,
+};
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum SetNewCode {
+ Intro,
+ Menu,
+ CancelIntro,
+ CancelConfirm,
+}
+
+impl FlowController for SetNewCode {
+ #[inline]
+ fn index(&'static self) -> usize {
+ *self as usize
+ }
+
+ fn handle_swipe(&'static self, direction: Direction) -> Decision {
+ match (self, direction) {
+ (Self::Intro, Direction::Up) => self.return_msg(FlowMsg::Confirmed),
+ (Self::CancelIntro, Direction::Up) => Self::CancelConfirm.swipe(direction),
+ (Self::CancelConfirm, Direction::Down) => Self::CancelIntro.swipe(direction),
+ _ => self.do_nothing(),
+ }
+ }
+
+ fn handle_event(&'static self, msg: FlowMsg) -> Decision {
+ match (self, msg) {
+ (Self::Intro, FlowMsg::Info) => Self::Menu.goto(),
+ (Self::Menu, FlowMsg::Choice(0)) => Self::CancelIntro.swipe_left(),
+ (Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
+ (Self::CancelIntro, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
+ (Self::CancelConfirm, FlowMsg::Cancelled) => Self::CancelIntro.swipe_right(),
+ (Self::CancelConfirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
+ _ => self.do_nothing(),
+ }
+ }
+}
+
+pub fn new_set_new_code(
+ title: TString<'static>,
+ description: TString<'static>,
+ _is_wipe_code: bool,
+) -> Result<SwipeFlow, error::Error> {
+ let paragraphs = Paragraphs::new(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description));
+ let content_intro = Frame::left_aligned(title, SwipeContent::new(paragraphs))
+ .with_menu_button()
+ .with_swipeup_footer(None)
+ .map_to_button_msg();
+
+ let content_menu = Frame::left_aligned(
+ "".into(),
+ VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::pin__cancel_setup.into()),
+ )
+ .with_cancel_button()
+ .map(super::util::map_to_choice);
+
+ let paragraphs_cancel_intro = ParagraphVecShort::from_iter([
+ Paragraph::new(&theme::TEXT_WARNING, TR::words__not_recommended),
+ Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, TR::pin__cancel_info),
+ ])
+ .into_paragraphs();
+ let content_cancel_intro = Frame::left_aligned(
+ TR::pin__cancel_setup.into(),
+ SwipeContent::new(paragraphs_cancel_intro),
+ )
+ .with_cancel_button()
+ .with_swipeup_footer(Some(TR::pin__cancel_description.into()))
+ .map_to_button_msg();
+
+ let content_cancel_confirm = Frame::left_aligned(
+ TR::pin__cancel_setup.into(),
+ SwipeContent::new(PromptScreen::new_tap_to_cancel()),
+ )
+ .with_cancel_button()
+ .with_footer(TR::instructions__tap_to_confirm.into(), None)
+ .with_swipe(Direction::Down, SwipeSettings::Default)
+ .map(super::util::map_to_confirm);
+
+ let mut res = SwipeFlow::new(&SetNewCode::Intro)?;
+ res.add_page(&SetNewCode::Intro, content_intro)?
+ .add_page(&SetNewCode::Menu, content_menu)?
+ .add_page(&SetNewCode::CancelIntro, content_cancel_intro)?
+ .add_page(&SetNewCode::CancelConfirm, content_cancel_confirm)?;
+ Ok(res)
+}
diff --git a/core/embed/rust/src/ui/layout_delizia/flow/confirm_set_new_pin.rs b/core/embed/rust/src/ui/layout_delizia/flow/confirm_set_new_pin.rs
deleted file mode 100644
index b7c5202d..00000000
--- a/core/embed/rust/src/ui/layout_delizia/flow/confirm_set_new_pin.rs
+++ /dev/null
@@ -1,105 +0,0 @@
-use crate::{
- error,
- strutil::TString,
- translations::TR,
- ui::{
- component::{
- swipe_detect::SwipeSettings,
- text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs},
- },
- flow::{
- base::{Decision, DecisionBuilder as _},
- FlowController, FlowMsg, SwipeFlow,
- },
- geometry::Direction,
- },
-};
-
-use super::super::{
- component::{Frame, PromptScreen, SwipeContent, VerticalMenu},
- theme,
-};
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum SetNewPin {
- Intro,
- Menu,
- CancelPinIntro,
- CancelPinConfirm,
-}
-
-impl FlowController for SetNewPin {
- #[inline]
- fn index(&'static self) -> usize {
- *self as usize
- }
-
- fn handle_swipe(&'static self, direction: Direction) -> Decision {
- match (self, direction) {
- (Self::Intro, Direction::Up) => self.return_msg(FlowMsg::Confirmed),
- (Self::CancelPinIntro, Direction::Up) => Self::CancelPinConfirm.swipe(direction),
- (Self::CancelPinConfirm, Direction::Down) => Self::CancelPinIntro.swipe(direction),
- _ => self.do_nothing(),
- }
- }
-
- fn handle_event(&'static self, msg: FlowMsg) -> Decision {
- match (self, msg) {
- (Self::Intro, FlowMsg::Info) => Self::Menu.goto(),
- (Self::Menu, FlowMsg::Choice(0)) => Self::CancelPinIntro.swipe_left(),
- (Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
- (Self::CancelPinIntro, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
- (Self::CancelPinConfirm, FlowMsg::Cancelled) => Self::CancelPinIntro.swipe_right(),
- (Self::CancelPinConfirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
- _ => self.do_nothing(),
- }
- }
-}
-
-pub fn new_set_new_pin(
- title: TString<'static>,
- description: TString<'static>,
-) -> Result<SwipeFlow, error::Error> {
- // TODO: supply more arguments for Wipe code setting when figma done
- let paragraphs = Paragraphs::new(Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, description));
- let content_intro = Frame::left_aligned(title, SwipeContent::new(paragraphs))
- .with_menu_button()
- .with_swipeup_footer(None)
- .map_to_button_msg();
-
- let content_menu = Frame::left_aligned(
- "".into(),
- VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::pin__cancel_setup.into()),
- )
- .with_cancel_button()
- .map(super::util::map_to_choice);
-
- let paragraphs_cancel_intro = ParagraphVecShort::from_iter([
- Paragraph::new(&theme::TEXT_WARNING, TR::words__not_recommended),
- Paragraph::new(&theme::TEXT_MAIN_GREY_LIGHT, TR::pin__cancel_info),
- ])
- .into_paragraphs();
- let content_cancel_intro = Frame::left_aligned(
- TR::pin__cancel_setup.into(),
- SwipeContent::new(paragraphs_cancel_intro),
- )
- .with_cancel_button()
- .with_swipeup_footer(Some(TR::pin__cancel_description.into()))
- .map_to_button_msg();
-
- let content_cancel_confirm = Frame::left_aligned(
- TR::pin__cancel_setup.into(),
- SwipeContent::new(PromptScreen::new_tap_to_cancel()),
- )
- .with_cancel_button()
- .with_footer(TR::instructions__tap_to_confirm.into(), None)
- .with_swipe(Direction::Down, SwipeSettings::Default)
- .map(super::util::map_to_confirm);
-
- let mut res = SwipeFlow::new(&SetNewPin::Intro)?;
- res.add_page(&SetNewPin::Intro, content_intro)?
- .add_page(&SetNewPin::Menu, content_menu)?
- .add_page(&SetNewPin::CancelPinIntro, content_cancel_intro)?
- .add_page(&SetNewPin::CancelPinConfirm, content_cancel_confirm)?;
- Ok(res)
-}
diff --git a/core/embed/rust/src/ui/layout_delizia/flow/mod.rs b/core/embed/rust/src/ui/layout_delizia/flow/mod.rs
index 94807ff9..32e66e4f 100644
--- a/core/embed/rust/src/ui/layout_delizia/flow/mod.rs
+++ b/core/embed/rust/src/ui/layout_delizia/flow/mod.rs
@@ -5,7 +5,7 @@ pub mod confirm_firmware_update;
pub mod confirm_homescreen;
pub mod confirm_output;
pub mod confirm_reset;
-pub mod confirm_set_new_pin;
+pub mod confirm_set_new_code;
pub mod confirm_summary;
pub mod continue_recovery_homepage;
pub mod prompt_backup;
@@ -28,7 +28,7 @@ pub use confirm_firmware_update::new_confirm_firmware_update;
pub use confirm_homescreen::new_confirm_homescreen;
pub use confirm_output::new_confirm_output;
pub use confirm_reset::new_confirm_reset;
-pub use confirm_set_new_pin::SetNewPin;
+pub use confirm_set_new_code::SetNewCode;
pub use confirm_summary::new_confirm_summary;
pub use continue_recovery_homepage::new_continue_recovery_homepage;
pub use prompt_backup::PromptBackup;
diff --git a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
index c2414c0a..82b23078 100644
--- a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
@@ -652,11 +652,12 @@ impl FirmwareUI for UIDelizia {
Ok(flow)
}
- fn flow_confirm_set_new_pin(
+ fn flow_confirm_set_new_code(
title: TString<'static>,
description: TString<'static>,
+ is_wipe_code: bool,
) -> Result<impl LayoutMaybeTrace, Error> {
- let flow = flow::confirm_set_new_pin::new_set_new_pin(title, description)?;
+ let flow = flow::confirm_set_new_code::new_set_new_code(title, description, is_wipe_code)?;
Ok(flow)
}
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rs
new file mode 100644
index 00000000..eb1ea47c
--- /dev/null
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_code.rs
@@ -0,0 +1,125 @@
+use crate::{
+ error,
+ strutil::TString,
+ translations::TR,
+ ui::{
+ component::{
+ text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs},
+ ComponentExt as _,
+ },
+ flow::{
+ base::{Decision, DecisionBuilder as _},
+ FlowController, FlowMsg, SwipeFlow,
+ },
+ geometry::{Direction, LinearPlacement},
+ },
+};
+
+use super::super::{
+ component::Button,
+ firmware::{
+ ActionBar, Header, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
+ VerticalMenuScreen, VerticalMenuScreenMsg,
+ },
+ theme::{self, gradient::Gradient},
+};
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum SetNewCode {
+ Intro,
+ Menu,
+ Cancel,
+}
+
+impl FlowController for SetNewCode {
+ #[inline]
+ fn index(&'static self) -> usize {
+ *self as usize
+ }
+
+ fn handle_swipe(&'static self, _direction: Direction) -> Decision {
+ self.do_nothing()
+ }
+
+ fn handle_event(&'static self, msg: FlowMsg) -> Decision {
+ match (self, msg) {
+ (Self::Intro, FlowMsg::Info) => Self::Menu.goto(),
+ (Self::Intro, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
+ (Self::Menu, FlowMsg::Choice(0)) => Self::Cancel.goto(),
+ (Self::Menu, FlowMsg::Cancelled) => Self::Intro.goto(),
+ (Self::Cancel, FlowMsg::Cancelled) => Self::Intro.goto(),
+ (Self::Cancel, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
+ _ => self.do_nothing(),
+ }
+ }
+}
+
+pub fn new_set_new_code(
+ title: TString<'static>,
+ description: TString<'static>,
+ is_wipe_code: bool,
+) -> Result<SwipeFlow, error::Error> {
+ let paragraphs = Paragraphs::new(Paragraph::new(&theme::firmware::TEXT_REGULAR, description))
+ .with_placement(LinearPlacement::vertical());
+ let content_intro = TextScreen::new(paragraphs)
+ .with_header(Header::new(title).with_menu_button())
+ .with_action_bar(ActionBar::new_single(Button::with_text(
+ TR::buttons__continue.into(),
+ )))
+ .with_page_limit(1)
+ .map(|msg| match msg {
+ TextScreenMsg::Menu => Some(FlowMsg::Info),
+ TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
+ _ => None,
+ });
+
+ let content_menu = VerticalMenuScreen::new(VerticalMenu::<ShortMenuVec>::empty().with_item(
+ Button::new_menu_item(TR::buttons__cancel.into(), theme::menu_item_title_orange()),
+ ))
+ .with_header(Header::new(title).with_close_button())
+ .map(|msg| match msg {
+ VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
+ VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
+ _ => None,
+ });
+
+ let paragraphs_cancel_intro = ParagraphVecShort::from_iter(if is_wipe_code {
+ [
+ Paragraph::new(&theme::firmware::TEXT_REGULAR, TR::wipe_code__cancel_setup),
+ Paragraph::new(&theme::firmware::TEXT_REGULAR, TString::empty()),
+ ]
+ } else {
+ [
+ Paragraph::new(&theme::firmware::TEXT_REGULAR, TR::pin__cancel_setup),
+ Paragraph::new(&theme::firmware::TEXT_REGULAR, TR::pin__cancel_info),
+ ]
+ })
+ .into_paragraphs()
+ .with_placement(LinearPlacement::vertical())
+ .with_spacing(theme::TEXT_VERTICAL_SPACING);
+
+ let content_cancel = TextScreen::new(paragraphs_cancel_intro)
+ .with_header(
+ Header::new(TR::words__important.into())
+ .with_text_style(theme::label_title_danger())
+ .with_icon(theme::ICON_WARNING, theme::ORANGE),
+ )
+ .with_action_bar(ActionBar::new_double(
+ Button::with_icon(theme::ICON_CHEVRON_LEFT),
+ Button::with_text(TR::buttons__cancel.into())
+ .styled(theme::button_actionbar_danger())
+ .with_gradient(Gradient::Alert),
+ ))
+ .with_page_limit(1)
+ .map(|msg| match msg {
+ TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
+ TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
+ _ => None,
+ });
+
+ let mut res = SwipeFlow::new(&SetNewCode::Intro)?;
+ res.add_page(&SetNewCode::Intro, content_intro)?
+ .add_page(&SetNewCode::Menu, content_menu)?
+ .add_page(&SetNewCode::Cancel, content_cancel)?;
+ Ok(res)
+}
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_pin.rs b/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_pin.rs
deleted file mode 100644
index 2e015820..00000000
--- a/core/embed/rust/src/ui/layout_eckhart/flow/confirm_set_new_pin.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-use crate::{
- error,
- strutil::TString,
- translations::TR,
- ui::{
- component::{
- text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs},
- ComponentExt as _,
- },
- flow::{
- base::{Decision, DecisionBuilder as _},
- FlowController, FlowMsg, SwipeFlow,
- },
- geometry::{Direction, LinearPlacement},
- },
-};
-
-use super::super::{
- component::Button,
- firmware::{
- ActionBar, Header, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
- VerticalMenuScreen, VerticalMenuScreenMsg,
- },
- theme::{self, gradient::Gradient},
-};
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum SetNewPin {
- Intro,
- Menu,
- Cancel,
-}
-
-impl FlowController for SetNewPin {
- #[inline]
- fn index(&'static self) -> usize {
- *self as usize
- }
-
- fn handle_swipe(&'static self, _direction: Direction) -> Decision {
- self.do_nothing()
- }
-
- fn handle_event(&'static self, msg: FlowMsg) -> Decision {
- match (self, msg) {
- (Self::Intro, FlowMsg::Info) => Self::Menu.goto(),
- (Self::Intro, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
- (Self::Menu, FlowMsg::Choice(0)) => Self::Cancel.goto(),
- (Self::Menu, FlowMsg::Cancelled) => Self::Intro.goto(),
- (Self::Cancel, FlowMsg::Cancelled) => Self::Intro.goto(),
- (Self::Cancel, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
- _ => self.do_nothing(),
- }
- }
-}
-
-pub fn new_set_new_pin(
- title: TString<'static>,
- description: TString<'static>,
-) -> Result<SwipeFlow, error::Error> {
- let paragraphs = Paragraphs::new(Paragraph::new(&theme::firmware::TEXT_REGULAR, description))
- .with_placement(LinearPlacement::vertical());
- let content_intro = TextScreen::new(paragraphs)
- .with_header(Header::new(title).with_menu_button())
- .with_action_bar(ActionBar::new_single(Button::with_text(
- TR::buttons__continue.into(),
- )))
- .with_page_limit(1)
- .map(|msg| match msg {
- TextScreenMsg::Menu => Some(FlowMsg::Info),
- TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
- _ => None,
- });
-
- let content_menu = VerticalMenuScreen::new(VerticalMenu::<ShortMenuVec>::empty().with_item(
- Button::new_menu_item(TR::buttons__cancel.into(), theme::menu_item_title_orange()),
- ))
- .with_header(Header::new(title).with_close_button())
- .map(|msg| match msg {
- VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
- VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
- _ => None,
- });
-
- let paragraphs_cancel_intro = ParagraphVecShort::from_iter([
- Paragraph::new(&theme::firmware::TEXT_REGULAR, TR::pin__cancel_setup),
- Paragraph::new(&theme::firmware::TEXT_REGULAR, TR::pin__cancel_info),
- ])
- .into_paragraphs()
- .with_placement(LinearPlacement::vertical())
- .with_spacing(theme::TEXT_VERTICAL_SPACING);
-
- let content_cancel = TextScreen::new(paragraphs_cancel_intro)
- .with_header(
- Header::new(TR::words__important.into())
- .with_text_style(theme::label_title_danger())
- .with_icon(theme::ICON_WARNING, theme::ORANGE),
- )
- .with_action_bar(ActionBar::new_double(
- Button::with_icon(theme::ICON_CHEVRON_LEFT),
- Button::with_text(TR::buttons__cancel.into())
- .styled(theme::button_actionbar_danger())
- .with_gradient(Gradient::Alert),
- ))
- .with_page_limit(1)
- .map(|msg| match msg {
- TextScreenMsg::Cancelled => Some(FlowMsg::Cancelled),
- TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
- _ => None,
- });
-
- let mut res = SwipeFlow::new(&SetNewPin::Intro)?;
- res.add_page(&SetNewPin::Intro, content_intro)?
- .add_page(&SetNewPin::Menu, content_menu)?
- .add_page(&SetNewPin::Cancel, content_cancel)?;
- Ok(res)
-}
diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs b/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs
index cc289543..2c77ecac 100644
--- a/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs
@@ -3,7 +3,7 @@ pub mod confirm_fido;
pub mod confirm_firmware_update;
pub mod confirm_output;
pub mod confirm_reset;
-pub mod confirm_set_new_pin;
+pub mod confirm_set_new_code;
pub mod confirm_summary;
pub mod confirm_value_intro;
pub mod confirm_with_menu;
@@ -23,7 +23,7 @@ pub use confirm_fido::new_confirm_fido;
pub use confirm_firmware_update::new_confirm_firmware_update;
pub use confirm_output::new_confirm_output;
pub use confirm_reset::new_confirm_reset;
-pub use confirm_set_new_pin::new_set_new_pin;
+pub use confirm_set_new_code::new_set_new_code;
pub use confirm_summary::new_confirm_summary;
pub use confirm_value_intro::new_confirm_value_intro;
pub use confirm_with_menu::new_confirm_with_menu;
diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
index 0d20ce89..28b037dc 100644
--- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
@@ -790,11 +790,12 @@ impl FirmwareUI for UIEckhart {
Ok(flow)
}
- fn flow_confirm_set_new_pin(
+ fn flow_confirm_set_new_code(
title: TString<'static>,
description: TString<'static>,
+ is_wipe_code: bool,
) -> Result<impl LayoutMaybeTrace, Error> {
- let flow = flow::confirm_set_new_pin::new_set_new_pin(title, description)?;
+ let flow = flow::confirm_set_new_code::new_set_new_code(title, description, is_wipe_code)?;
Ok(flow)
}
diff --git a/core/embed/rust/src/ui/ui_firmware.rs b/core/embed/rust/src/ui/ui_firmware.rs
index c684a626..11a34e65 100644
--- a/core/embed/rust/src/ui/ui_firmware.rs
+++ b/core/embed/rust/src/ui/ui_firmware.rs
@@ -205,9 +205,10 @@ pub trait FirmwareUI {
cancel_text: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error>;
- fn flow_confirm_set_new_pin(
+ fn flow_confirm_set_new_code(
title: TString<'static>,
description: TString<'static>,
+ is_wipe_code: bool,
) -> Result<impl LayoutMaybeTrace, Error>;
#[allow(clippy::too_many_arguments)]
@@ -240,7 +241,7 @@ pub trait FirmwareUI {
br_name: TString<'static>,
) -> Result<impl LayoutMaybeTrace, Error>;
- // TODO: this is TR specific and used only in confirm_set_new_pin
+ // TODO: this is TR specific and used only in confirm_set_new_code
fn multiple_pages_texts(
title: TString<'static>,
verb: TString<'static>,
diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi
index aa05f455..1c39dd60 100644
--- a/core/mocks/generated/trezorui_api.pyi
+++ b/core/mocks/generated/trezorui_api.pyi
@@ -375,12 +375,13 @@ def flow_confirm_output(
# rust/src/ui/api/firmware_micropython.rs
-def flow_confirm_set_new_pin(
+def flow_confirm_set_new_code(
*,
title: str,
description: str,
+ is_wipe_code: bool,
) -> LayoutObj[UiResult]:
- """Confirm new PIN setup with an option to cancel action."""
+ """Confirm new PIN/wipe code setup with an option to cancel action."""
# rust/src/ui/api/firmware_micropython.rs
diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi
index 8f1e97b1..9d0f8297 100644
--- a/core/mocks/trezortranslate_keys.pyi
+++ b/core/mocks/trezortranslate_keys.pyi
@@ -1012,6 +1012,7 @@ class TR:
wipe__start_again: str = "Wipe your Trezor and start the setup process again."
wipe__title: str = "Wipe device"
wipe__want_to_wipe: str = "Do you really want to wipe the device?\n"
+ wipe_code__cancel_setup: str = "Cancel wipe code setup"
wipe_code__change: str = "Change wipe code"
wipe_code__change_question: str = "Change wipe code?"
wipe_code__changed: str = "Wipe code changed."
diff --git a/core/src/apps/management/change_pin.py b/core/src/apps/management/change_pin.py
index 8e1eaa46..c3d9ac0e 100644
--- a/core/src/apps/management/change_pin.py
+++ b/core/src/apps/management/change_pin.py
@@ -72,7 +72,7 @@ def _require_confirm_change_pin(msg: ChangePin) -> Awaitable[None]:
from trezor.ui.layouts import (
confirm_change_pin,
confirm_remove_pin,
- confirm_set_new_pin,
+ confirm_set_new_code,
)
has_pin = config.has_pin()
@@ -92,11 +92,12 @@ def _require_confirm_change_pin(msg: ChangePin) -> Awaitable[None]:
)
if not msg.remove and not has_pin: # setting new pin
- return confirm_set_new_pin(
+ return confirm_set_new_code(
"set_pin",
TR.pin__title_settings,
TR.pin__turn_on,
TR.pin__info,
+ is_wipe_code=False,
)
# removing non-existing PIN
diff --git a/core/src/apps/management/change_wipe_code.py b/core/src/apps/management/change_wipe_code.py
index c158226f..f6ff8f62 100644
--- a/core/src/apps/management/change_wipe_code.py
+++ b/core/src/apps/management/change_wipe_code.py
@@ -68,7 +68,7 @@ async def change_wipe_code(msg: ChangeWipeCode) -> Success:
def _require_confirm_action(
msg: ChangeWipeCode, has_wipe_code: bool
) -> Awaitable[None]:
- from trezor.ui.layouts import confirm_action, confirm_set_new_pin
+ from trezor.ui.layouts import confirm_action, confirm_set_new_code
from trezor.wire import ProcessError
if msg.remove and has_wipe_code: # removing wipe code
@@ -89,11 +89,12 @@ def _require_confirm_action(
)
if not msg.remove and not has_wipe_code: # setting new wipe code
- return confirm_set_new_pin(
+ return confirm_set_new_code(
"set_wipe_code",
TR.wipe_code__title_settings,
TR.wipe_code__turn_on,
TR.wipe_code__info,
+ is_wipe_code=True,
)
# Removing non-existing wipe code.
diff --git a/core/src/trezor/ui/layouts/bolt/__init__.py b/core/src/trezor/ui/layouts/bolt/__init__.py
index e422a1a4..e4c416ef 100644
--- a/core/src/trezor/ui/layouts/bolt/__init__.py
+++ b/core/src/trezor/ui/layouts/bolt/__init__.py
@@ -1814,11 +1814,12 @@ async def pin_wipe_code_exists_popup(
)
-def confirm_set_new_pin(
+def confirm_set_new_code(
br_name: str,
title: str,
description: str,
information: str,
+ is_wipe_code: bool,
br_code: ButtonRequestType = BR_CODE_OTHER,
) -> Awaitable[None]:
return raise_if_cancelled(
diff --git a/core/src/trezor/ui/layouts/caesar/__init__.py b/core/src/trezor/ui/layouts/caesar/__init__.py
index 1ddde996..6643cfb7 100644
--- a/core/src/trezor/ui/layouts/caesar/__init__.py
+++ b/core/src/trezor/ui/layouts/caesar/__init__.py
@@ -1841,11 +1841,12 @@ async def pin_wipe_code_exists_popup(
)
-async def confirm_set_new_pin(
+async def confirm_set_new_code(
br_name: str,
title: str,
description: str,
information: str,
+ is_wipe_code: bool,
br_code: ButtonRequestType = BR_CODE_OTHER,
) -> None:
await _confirm_multiple_pages_texts(
@@ -1857,7 +1858,7 @@ async def confirm_set_new_pin(
)
# Not showing extra info for wipe code
- if "wipe_code" in br_name:
+ if is_wipe_code:
return
# Additional information for the user to know about PIN
diff --git a/core/src/trezor/ui/layouts/delizia/__init__.py b/core/src/trezor/ui/layouts/delizia/__init__.py
index e7c87781..db468c58 100644
--- a/core/src/trezor/ui/layouts/delizia/__init__.py
+++ b/core/src/trezor/ui/layouts/delizia/__init__.py
@@ -1734,15 +1734,18 @@ async def pin_wipe_code_exists_popup(
)
-def confirm_set_new_pin(
+def confirm_set_new_code(
br_name: str,
title: str,
description: str,
information: str,
+ is_wipe_code: bool,
br_code: ButtonRequestType = BR_CODE_OTHER,
) -> Awaitable[None]:
return raise_if_cancelled(
- trezorui_api.flow_confirm_set_new_pin(title=title, description=description),
+ trezorui_api.flow_confirm_set_new_code(
+ title=title, description=description, is_wipe_code=is_wipe_code
+ ),
br_name,
br_code,
)
diff --git a/core/src/trezor/ui/layouts/eckhart/__init__.py b/core/src/trezor/ui/layouts/eckhart/__init__.py
index be65f07c..874e600e 100644
--- a/core/src/trezor/ui/layouts/eckhart/__init__.py
+++ b/core/src/trezor/ui/layouts/eckhart/__init__.py
@@ -1798,15 +1798,18 @@ async def pin_wipe_code_exists_popup(
)
-def confirm_set_new_pin(
+def confirm_set_new_code(
br_name: str,
title: str,
description: str,
information: str,
+ is_wipe_code: bool,
br_code: ButtonRequestType = BR_CODE_OTHER,
) -> Awaitable[None]:
return raise_if_cancelled(
- trezorui_api.flow_confirm_set_new_pin(title=title, description=information),
+ trezorui_api.flow_confirm_set_new_code(
+ title=title, description=information, is_wipe_code=is_wipe_code
+ ),
br_name,
br_code,
)
diff --git a/core/translations/en.json b/core/translations/en.json
index 4b83fda5..a3ebe97c 100644
--- a/core/translations/en.json
+++ b/core/translations/en.json
@@ -1314,6 +1314,12 @@
"wipe__start_again": "Wipe your Trezor and start the setup process again.",
"wipe__title": "Wipe device",
"wipe__want_to_wipe": "Do you really want to wipe the device?\n",
+ "wipe_code__cancel_setup": {
+ "Bolt": "Cancel wipe code setup",
+ "Caesar": "Cancel wipe code setup",
+ "Delizia": "Cancel wipe code setup",
+ "Eckhart": "Cancel wipe code setup?"
+ },
"wipe_code__change": "Change wipe code",
"wipe_code__change_question": "Change wipe code?",
"wipe_code__changed": "Wipe code changed.",
diff --git a/core/translations/order.json b/core/translations/order.json
index 181df4c2..37551665 100644
--- a/core/translations/order.json
+++ b/core/translations/order.json
@@ -1157,5 +1157,6 @@
"1155": "auto_lock__on_usb",
"1156": "pin__wipe_code_exists_description",
"1157": "pin__wipe_code_exists_title",
- "1158": "wipe_code__pin_not_set_description"
+ "1158": "wipe_code__pin_not_set_description",
+ "1159": "wipe_code__cancel_setup"
}
diff --git a/core/translations/signatures.json b/core/translations/signatures.json
index ad5287f0..68171c2a 100644
--- a/core/translations/signatures.json
+++ b/core/translations/signatures.json
@@ -1,8 +1,8 @@
{
"current": {
- "merkle_root": "0a1eac9964d2f43bb9796ed12b86a4b89e17ae3dee5453a7cd3e12e496b5b104",
- "datetime": "2025-09-17T10:32:46.627902+00:00",
- "commit": "16f3d7c06d6bdcef4e6e44c0635fa2f03486c3a0"
+ "merkle_root": "055c40c88ad7df0fe3f804766abc30438ee65fb31bf3869c18e830285b061e70",
+ "datetime": "2025-09-17T11:58:56.566416+00:00",
+ "commit": "375ee785f73dc92b858931914212fcf48bcea6c4"
},
"history": [
{
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.