feat(eckhart): back button on confirm_value
What changed, and why it matters
This commit adds a new optional 'back button' to a Trezor screen type called confirm_value. It is a user-interface feature for the upcoming Eckhart hardware model and does not fix or introduce any security vulnerability. The change simply lets callers request a back arrow instead of a cancel cross, and wires that through the Rust UI layer for all supported device layouts.
No security action required. Treat as a normal feature commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch extends the confirm_value UI API with a new boolean parameter back_button. The trait FirmwareUI::confirm_value and all four layout implementations (bolt, caesar, delizia, eckhart) gain the parameter. Bolt and Caesar ignore it; Delizia accepts but ignores it; Eckhart implements it by mapping the left action-bar button to a chevron-up icon and returning FlowMsg::Back when pressed. The Python stub and micropython binding are updated accordingly. The return type of confirm_value is changed from Gc<LayoutObj> to impl LayoutMaybeTrace, and the top-level wrapper now wraps the result in LayoutObj::new_root.
Changed components
core/embed/rust/src/ui/api/firmware_micropython.rscore/embed/rust/src/ui/layout_bolt/ui_firmware.rscore/embed/rust/src/ui/layout_caesar/ui_firmware.rscore/embed/rust/src/ui/layout_delizia/ui_firmware.rscore/embed/rust/src/ui/layout_eckhart/ui_firmware.rscore/embed/rust/src/ui/ui_firmware.rscore/mocks/generated/trezorui_api.pyiInspect captured patch +45 / −16
diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs
index cf465c46..13ccfb7a 100644
--- a/core/embed/rust/src/ui/api/firmware_micropython.rs
+++ b/core/embed/rust/src/ui/api/firmware_micropython.rs
@@ -165,13 +165,14 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
let page_counter: bool = kwargs.get_or(Qstr::MP_QSTR_page_counter, false)?;
let prompt_screen: bool = kwargs.get_or(Qstr::MP_QSTR_prompt_screen, false)?;
let cancel: bool = kwargs.get_or(Qstr::MP_QSTR_cancel, false)?;
+ let back_button: bool = kwargs.get_or(Qstr::MP_QSTR_back_button, false)?;
let warning_footer: Option<TString> = kwargs
.get(Qstr::MP_QSTR_warning_footer)
.unwrap_or_else(|_| Obj::const_none())
.try_into_option()?;
let external_menu: bool = kwargs.get_or(Qstr::MP_QSTR_external_menu, false)?;
- let layout_obj = ModelUI::confirm_value(
+ let layout = ModelUI::confirm_value(
title,
value,
description,
@@ -186,10 +187,12 @@ extern "C" fn new_confirm_value(n_args: usize, args: *const Obj, kwargs: *mut Ma
page_counter,
prompt_screen,
cancel,
+ back_button,
warning_footer,
external_menu,
)?;
- Ok(layout_obj.into())
+
+ Ok(LayoutObj::new_root(layout)?.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
@@ -1546,6 +1549,7 @@ pub static mp_module_trezorui_api: Module = obj_module! {
/// page_counter: bool = False,
/// prompt_screen: bool = False,
/// cancel: bool = False,
+ /// back_button: bool = False,
/// warning_footer: str | None = None,
/// external_menu: bool = False,
/// ) -> LayoutObj[UiResult]:
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 d1805aa8..d5952b3e 100644
--- a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs
@@ -128,16 +128,19 @@ impl FirmwareUI for UIBolt {
_page_counter: bool,
_prompt_screen: bool,
_cancel: bool,
+ _back_button: bool,
_warning_footer: Option<TString<'static>>,
_external_menu: bool,
- ) -> Result<Gc<LayoutObj>, Error> {
- ConfirmValue::new(title, value, description, verb, verb_cancel, hold)
+ ) -> Result<impl LayoutMaybeTrace, Error> {
+ let frame = ConfirmValue::new(title, value, description, verb, verb_cancel, hold)
.with_text_mono(is_data)
.with_subtitle(subtitle)
.with_extra(extra)
.with_chunkify(chunkify)
.with_info_button(info)
- .into_layout()
+ .into_frame()?;
+ let layout = RootComponent::new(frame);
+ Ok(layout)
}
fn confirm_value_intro(
@@ -1435,7 +1438,7 @@ impl ConfirmValue {
self
}
- fn into_layout(self) -> Result<Gc<LayoutObj>, Error> {
+ fn into_frame(self) -> Result<Frame<ButtonPage<Paragraphs<ConfirmValueParams>>>, Error> {
let description = self.description.unwrap_or("".into());
let extra = self.extra.unwrap_or("".into());
let paragraphs = ConfirmValueParams {
@@ -1474,7 +1477,11 @@ impl ConfirmValue {
if self.info_button {
frame = frame.with_info_button();
}
- LayoutObj::new(frame)
+ Ok(frame)
+ }
+
+ fn into_layout(self) -> Result<Gc<LayoutObj>, Error> {
+ LayoutObj::new(self.into_frame()?)
}
}
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 8c6ade02..94a803cf 100644
--- a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs
@@ -159,9 +159,10 @@ impl FirmwareUI for UICaesar {
_page_counter: bool,
_prompt_screen: bool,
_cancel: bool,
+ _back_button: bool,
_warning_footer: Option<TString<'static>>,
_external_menu: bool,
- ) -> Result<Gc<LayoutObj>, Error> {
+ ) -> Result<impl LayoutMaybeTrace, Error> {
let paragraphs = ConfirmValueParams {
description: description.unwrap_or("".into()),
extra: extra.unwrap_or("".into()),
@@ -179,15 +180,14 @@ impl FirmwareUI for UICaesar {
}
.into_paragraphs();
- let layout = content_in_button_page(
+ content_in_button_page(
title,
paragraphs,
verb.unwrap_or(TR::buttons__confirm.into()),
verb_cancel,
hold,
false,
- )?;
- LayoutObj::new_root(layout)
+ )
}
fn confirm_value_intro(
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 c12b1b37..66f3ad1d 100644
--- a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs
@@ -123,9 +123,10 @@ impl FirmwareUI for UIDelizia {
page_counter: bool,
prompt_screen: bool,
cancel: bool,
+ _back_button: bool,
_warning_footer: Option<TString<'static>>,
external_menu: bool,
- ) -> Result<Gc<LayoutObj>, Error> {
+ ) -> Result<impl LayoutMaybeTrace, Error> {
if info && external_menu {
return Err(Error::NotImplementedError);
}
@@ -149,7 +150,6 @@ impl FirmwareUI for UIDelizia {
.with_external_menu(external_menu)
.with_hold(hold)
.into_flow()
- .and_then(LayoutObj::new_root)
}
fn confirm_value_intro(
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 c84fc79b..c5933165 100644
--- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
@@ -453,12 +453,16 @@ impl FirmwareUI for UIEckhart {
page_counter: bool,
_prompt_screen: bool,
cancel: bool,
+ back_button: bool,
warning_footer: Option<TString<'static>>,
external_menu: bool,
- ) -> Result<Gc<LayoutObj>, Error> {
+ ) -> Result<impl LayoutMaybeTrace, Error> {
if info && external_menu {
return Err(Error::NotImplementedError);
}
+ if cancel && back_button {
+ return Err(Error::NotImplementedError);
+ }
let paragraphs = ConfirmValueParams {
description: description.unwrap_or("".into()),
@@ -509,6 +513,8 @@ impl FirmwareUI for UIEckhart {
let action_bar = if cancel {
ActionBar::new_double(Button::with_icon(theme::ICON_CROSS), right_button)
+ } else if back_button {
+ ActionBar::new_double(Button::with_icon(theme::ICON_CHEVRON_UP), right_button)
} else {
ActionBar::new_single(right_button)
};
@@ -523,7 +529,17 @@ impl FirmwareUI for UIEckhart {
} else if let Some(warning_footer) = warning_footer {
screen = screen.with_hint(Hint::new_warning_caution(warning_footer));
}
- LayoutObj::new(screen)
+ let screen = screen.map(move |msg| match msg {
+ TextScreenMsg::Cancelled => Some(if back_button {
+ FlowMsg::Back
+ } else {
+ FlowMsg::Cancelled
+ }),
+ TextScreenMsg::Menu => Some(FlowMsg::Info),
+ TextScreenMsg::Confirmed => Some(FlowMsg::Confirmed),
+ });
+
+ flow::util::single_page(screen)
}
fn confirm_value_intro(
diff --git a/core/embed/rust/src/ui/ui_firmware.rs b/core/embed/rust/src/ui/ui_firmware.rs
index ef1d1271..24dc96ff 100644
--- a/core/embed/rust/src/ui/ui_firmware.rs
+++ b/core/embed/rust/src/ui/ui_firmware.rs
@@ -68,9 +68,10 @@ pub trait FirmwareUI {
page_counter: bool,
prompt_screen: bool,
cancel: bool,
+ back_button: bool,
warning_footer: Option<TString<'static>>,
external_menu: bool,
- ) -> Result<Gc<LayoutObj>, Error>; // TODO: return LayoutMaybeTrace
+ ) -> Result<impl LayoutMaybeTrace, Error>;
fn confirm_value_intro(
title: TString<'static>,
diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi
index 10d407af..2dd6f080 100644
--- a/core/mocks/generated/trezorui_api.pyi
+++ b/core/mocks/generated/trezorui_api.pyi
@@ -167,6 +167,7 @@ def confirm_value(
page_counter: bool = False,
prompt_screen: bool = False,
cancel: bool = False,
+ back_button: bool = False,
warning_footer: str | None = None,
external_menu: bool = False,
) -> LayoutObj[UiResult]:
Why this scored 15/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.