chore(core/eckhart): update device menu security settings
What changed, and why it matters
This commit wires up the on-device settings menu for a new Trezor hardware-wallet UI (the 'Eckhart' layout). It adds menu entries that let the user change or remove their PIN and wipe code, set auto-lock delay, and check their backup. The change is mostly UI plumbing: it connects existing security-management code to new menu items and adds translated labels. There is no obvious security bug in the diff, but it is a partial implementation (other TODOs remain) and the code now exposes sensitive actions through a new menu surface.
Treat as a normal feature commit. Reviewers should verify that the new menu actions invoke the same confirmation prompts and authentication checks as the host-triggered ChangePin/ChangeWipeCode/ApplySettings/RecoveryDevice flows, and that the dry-run backup check cannot be abused to alter seed state. No immediate security patch is indicated by the diff alone.
Security signals we found
New UI surface exposes PIN change, PIN removal, wipe-code change, wipe-code removal, auto-lock change, and dry-run backup check from the device menu
PIN/wipe-code removal items are styled with a warning theme, indicating destructive action
Auto-lock delay is now gated behind config.has_pin() and routed through apply_settings instead of direct storage write
Backup check uses RecoveryType.DryRun, which should not modify seed state
Translation key rename from pin__change to pin__change_question preserves the question form for confirmation dialogs while using a non-question form in menus
Evidence from the diff
The patch implements the device-menu security settings for the Eckhart layout. It adds submenus for PIN and wipe code, shows enabled/disabled state, and routes menu results to existing management flows: change_pin, change_wipe_code, apply_settings for auto-lock, and recovery_device dry-run for backup check. Translation keys are split so that ‘Change PIN’ and ‘Change PIN?’ are separate strings, and new strings for ‘Remove PIN’, ‘Remove wipe code’, ‘Enabled’, ‘Disabled’, etc. are added. The Rust device_menu_screen now conditionally builds a Security menu only when at least one security option is available, and auto-lock is hidden when no PIN is set. The Python handler replaces TODO stubs with calls to the existing management modules.
Changed components
core/src/apps/homescreen/device_menu.pycore/src/apps/management/change_pin.pycore/src/apps/management/change_wipe_code.pycore/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rscore/embed/rust/src/translations/generated/translated_string.rscore/embed/rust/librust_qstr.hcore/translations/en.json and other locale filestests/click_tests/test_pin.pyInspect captured patch +259 / −83
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index da1207d9..c76c7a73 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -472,6 +472,7 @@ static void _librust_qstrs(void) {
MP_QSTR_pin__cancel_info;
MP_QSTR_pin__cancel_setup;
MP_QSTR_pin__change;
+ MP_QSTR_pin__change_question;
MP_QSTR_pin__changed;
MP_QSTR_pin__cursor_will_change;
MP_QSTR_pin__diff_from_wipe_code;
@@ -489,8 +490,10 @@ static void _librust_qstrs(void) {
MP_QSTR_pin__reenter;
MP_QSTR_pin__reenter_new;
MP_QSTR_pin__reenter_to_confirm;
+ MP_QSTR_pin__remove;
MP_QSTR_pin__setup_completed;
MP_QSTR_pin__should_be_long;
+ MP_QSTR_pin__title;
MP_QSTR_pin__title_check_pin;
MP_QSTR_pin__title_settings;
MP_QSTR_pin__title_wrong_pin;
@@ -881,6 +884,7 @@ static void _librust_qstrs(void) {
MP_QSTR_wipe__want_to_wipe;
MP_QSTR_wipe_code;
MP_QSTR_wipe_code__change;
+ MP_QSTR_wipe_code__change_question;
MP_QSTR_wipe_code__changed;
MP_QSTR_wipe_code__diff_from_pin;
MP_QSTR_wipe_code__disabled;
@@ -891,6 +895,8 @@ static void _librust_qstrs(void) {
MP_QSTR_wipe_code__mismatch;
MP_QSTR_wipe_code__reenter;
MP_QSTR_wipe_code__reenter_to_confirm;
+ MP_QSTR_wipe_code__remove;
+ MP_QSTR_wipe_code__title;
MP_QSTR_wipe_code__title_check;
MP_QSTR_wipe_code__title_invalid;
MP_QSTR_wipe_code__title_settings;
@@ -922,7 +928,9 @@ static void _librust_qstrs(void) {
MP_QSTR_words__continue_anyway_question;
MP_QSTR_words__continue_with;
MP_QSTR_words__device;
+ MP_QSTR_words__disabled;
MP_QSTR_words__disconnect;
+ MP_QSTR_words__enabled;
MP_QSTR_words__error;
MP_QSTR_words__fee;
MP_QSTR_words__from;
diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs
index f81134ba..1877d88a 100644
--- a/core/embed/rust/src/translations/generated/translated_string.rs
+++ b/core/embed/rust/src/translations/generated/translated_string.rs
@@ -704,7 +704,7 @@ pub enum TranslatedString {
passphrase__title_source = 444, // "Passphrase source"
passphrase__turn_off = 445, // "Turn off passphrase protection?"
passphrase__turn_on = 446, // "Turn on passphrase protection?"
- pin__change = 447, // "Change PIN?"
+ pin__change = 447, // "Change PIN"
pin__changed = 448, // "PIN changed."
pin__cursor_will_change = 449, // "Position of the cursor will change between entries for enhanced security."
pin__diff_from_wipe_code = 450, // "The new PIN must be different from your wipe code."
@@ -1107,7 +1107,7 @@ pub enum TranslatedString {
wipe__info = 771, // "All data will be erased."
wipe__title = 772, // "Wipe device"
wipe__want_to_wipe = 773, // "Do you really want to wipe the device?\n"
- wipe_code__change = 774, // "Change wipe code?"
+ wipe_code__change = 774, // "Change wipe code"
wipe_code__changed = 775, // "Wipe code changed."
wipe_code__diff_from_pin = 776, // "The wipe code must be different from your PIN."
wipe_code__disabled = 777, // "Wipe code disabled."
@@ -1495,6 +1495,14 @@ pub enum TranslatedString {
words__on = 1104, // "ON"
words__review = 1105, // "Review"
words__security = 1106, // "Security"
+ pin__change_question = 1107, // "Change PIN?"
+ pin__remove = 1108, // "Remove PIN"
+ pin__title = 1109, // "PIN code"
+ wipe_code__change_question = 1110, // "Change wipe code?"
+ wipe_code__remove = 1111, // "Remove wipe code"
+ wipe_code__title = 1112, // "Wipe code"
+ words__disabled = 1113, // "Disabled"
+ words__enabled = 1114, // "Enabled"
}
impl TranslatedString {
@@ -2299,7 +2307,7 @@ impl TranslatedString {
(Self::passphrase__title_source, "Passphrase source"),
(Self::passphrase__turn_off, "Turn off passphrase protection?"),
(Self::passphrase__turn_on, "Turn on passphrase protection?"),
- (Self::pin__change, "Change PIN?"),
+ (Self::pin__change, "Change PIN"),
(Self::pin__changed, "PIN changed."),
(Self::pin__cursor_will_change, "Position of the cursor will change between entries for enhanced security."),
(Self::pin__diff_from_wipe_code, "The new PIN must be different from your wipe code."),
@@ -2814,7 +2822,7 @@ impl TranslatedString {
(Self::wipe__info, "All data will be erased."),
(Self::wipe__title, "Wipe device"),
(Self::wipe__want_to_wipe, "Do you really want to wipe the device?\n"),
- (Self::wipe_code__change, "Change wipe code?"),
+ (Self::wipe_code__change, "Change wipe code"),
(Self::wipe_code__changed, "Wipe code changed."),
(Self::wipe_code__diff_from_pin, "The wipe code must be different from your PIN."),
(Self::wipe_code__disabled, "Wipe code disabled."),
@@ -3309,6 +3317,14 @@ impl TranslatedString {
(Self::words__on, "ON"),
(Self::words__review, "Review"),
(Self::words__security, "Security"),
+ (Self::pin__change_question, "Change PIN?"),
+ (Self::pin__remove, "Remove PIN"),
+ (Self::pin__title, "PIN code"),
+ (Self::wipe_code__change_question, "Change wipe code?"),
+ (Self::wipe_code__remove, "Remove wipe code"),
+ (Self::wipe_code__title, "Wipe code"),
+ (Self::words__disabled, "Disabled"),
+ (Self::words__enabled, "Enabled"),
];
#[cfg(feature = "micropython")]
@@ -4173,6 +4189,7 @@ impl TranslatedString {
(Qstr::MP_QSTR_pin__cancel_info, Self::pin__cancel_info),
(Qstr::MP_QSTR_pin__cancel_setup, Self::pin__cancel_setup),
(Qstr::MP_QSTR_pin__change, Self::pin__change),
+ (Qstr::MP_QSTR_pin__change_question, Self::pin__change_question),
(Qstr::MP_QSTR_pin__changed, Self::pin__changed),
(Qstr::MP_QSTR_pin__cursor_will_change, Self::pin__cursor_will_change),
(Qstr::MP_QSTR_pin__diff_from_wipe_code, Self::pin__diff_from_wipe_code),
@@ -4190,8 +4207,10 @@ impl TranslatedString {
(Qstr::MP_QSTR_pin__reenter, Self::pin__reenter),
(Qstr::MP_QSTR_pin__reenter_new, Self::pin__reenter_new),
(Qstr::MP_QSTR_pin__reenter_to_confirm, Self::pin__reenter_to_confirm),
+ (Qstr::MP_QSTR_pin__remove, Self::pin__remove),
(Qstr::MP_QSTR_pin__setup_completed, Self::pin__setup_completed),
(Qstr::MP_QSTR_pin__should_be_long, Self::pin__should_be_long),
+ (Qstr::MP_QSTR_pin__title, Self::pin__title),
(Qstr::MP_QSTR_pin__title_check_pin, Self::pin__title_check_pin),
(Qstr::MP_QSTR_pin__title_settings, Self::pin__title_settings),
(Qstr::MP_QSTR_pin__title_wrong_pin, Self::pin__title_wrong_pin),
@@ -4703,6 +4722,7 @@ impl TranslatedString {
(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__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),
(Qstr::MP_QSTR_wipe_code__diff_from_pin, Self::wipe_code__diff_from_pin),
(Qstr::MP_QSTR_wipe_code__disabled, Self::wipe_code__disabled),
@@ -4713,6 +4733,8 @@ impl TranslatedString {
(Qstr::MP_QSTR_wipe_code__mismatch, Self::wipe_code__mismatch),
(Qstr::MP_QSTR_wipe_code__reenter, Self::wipe_code__reenter),
(Qstr::MP_QSTR_wipe_code__reenter_to_confirm, Self::wipe_code__reenter_to_confirm),
+ (Qstr::MP_QSTR_wipe_code__remove, Self::wipe_code__remove),
+ (Qstr::MP_QSTR_wipe_code__title, Self::wipe_code__title),
(Qstr::MP_QSTR_wipe_code__title_check, Self::wipe_code__title_check),
(Qstr::MP_QSTR_wipe_code__title_invalid, Self::wipe_code__title_invalid),
(Qstr::MP_QSTR_wipe_code__title_settings, Self::wipe_code__title_settings),
@@ -4743,7 +4765,9 @@ impl TranslatedString {
(Qstr::MP_QSTR_words__continue_anyway_question, Self::words__continue_anyway_question),
(Qstr::MP_QSTR_words__continue_with, Self::words__continue_with),
(Qstr::MP_QSTR_words__device, Self::words__device),
+ (Qstr::MP_QSTR_words__disabled, Self::words__disabled),
(Qstr::MP_QSTR_words__disconnect, Self::words__disconnect),
+ (Qstr::MP_QSTR_words__enabled, Self::words__enabled),
(Qstr::MP_QSTR_words__error, Self::words__error),
(Qstr::MP_QSTR_words__fee, Self::words__fee),
(Qstr::MP_QSTR_words__from, Self::words__from),
diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
index 3da77624..5d5087c5 100644
--- a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
@@ -197,10 +197,10 @@ impl DeviceMenuScreen {
paired_devices: Vec<TString<'static>, MAX_PAIRED_DEVICES>,
_connected_idx: Option<usize>,
_bluetooth: Option<bool>,
- _pin_code: Option<bool>,
- _auto_lock_delay: Option<TString<'static>>,
- _wipe_code: Option<bool>,
- _check_backup: bool,
+ pin_code: Option<bool>,
+ auto_lock_delay: Option<TString<'static>>,
+ wipe_code: Option<bool>,
+ check_backup: bool,
device_name: Option<TString<'static>>,
screen_brightness: Option<TString<'static>>,
haptic_feedback: Option<bool>,
@@ -219,7 +219,15 @@ impl DeviceMenuScreen {
let about = screen.add_subscreen(Subscreen::AboutScreen);
let regulatory = screen.add_subscreen(Subscreen::RegulatoryScreen);
- let security = screen.add_security_menu();
+ let security = if pin_code.is_none()
+ && auto_lock_delay.is_none()
+ && wipe_code.is_none()
+ && !check_backup
+ {
+ None
+ } else {
+ Some(screen.add_security_menu(pin_code, auto_lock_delay, wipe_code, check_backup))
+ };
let device = screen.add_device_menu(
device_name,
screen_brightness,
@@ -294,12 +302,14 @@ impl DeviceMenuScreen {
self.add_subscreen(Subscreen::Submenu(submenu_index))
}
- fn add_settings_menu(&mut self, security_index: usize, device_index: usize) -> usize {
+ fn add_settings_menu(&mut self, security_index: Option<usize>, device_index: usize) -> usize {
let mut items: Vec<MenuItem, MEDIUM_MENU_ITEMS> = Vec::new();
- unwrap!(items.push(MenuItem::new(
- TR::words__security.into(),
- Some(Action::GoTo(security_index))
- )));
+ if let Some(security_index) = security_index {
+ unwrap!(items.push(MenuItem::new(
+ TR::words__security.into(),
+ Some(Action::GoTo(security_index))
+ )));
+ }
unwrap!(items.push(MenuItem::new(
TR::words__device.into(),
Some(Action::GoTo(device_index))
@@ -309,16 +319,101 @@ impl DeviceMenuScreen {
self.add_subscreen(Subscreen::Submenu(submenu_index))
}
- fn add_security_menu(&mut self) -> usize {
+ fn add_code_menu(&mut self, wipe_code: bool) -> usize {
let mut items: Vec<MenuItem, MEDIUM_MENU_ITEMS> = Vec::new();
- unwrap!(items.push(MenuItem::new(
- TR::reset__check_backup_title.into(),
- Some(Action::Return(DeviceMenuMsg::CheckBackup)),
- )));
- unwrap!(items.push(MenuItem::new(
- TR::wipe__title.into(),
- Some(Action::Return(DeviceMenuMsg::WipeDevice))
- )));
+ let change_text = match wipe_code {
+ true => TR::wipe_code__change,
+ false => TR::pin__change,
+ }
+ .into();
+ let change_action = match wipe_code {
+ true => Action::Return(DeviceMenuMsg::WipeCode),
+ false => Action::Return(DeviceMenuMsg::PinCode),
+ };
+ let change_pin_item = MenuItem::new(change_text, Some(change_action));
+ unwrap!(items.push(change_pin_item));
+
+ let remove_text = match wipe_code {
+ true => TR::wipe_code__remove,
+ false => TR::pin__remove,
+ }
+ .into();
+ let remove_action = match wipe_code {
+ true => Action::Return(DeviceMenuMsg::WipeRemove),
+ false => Action::Return(DeviceMenuMsg::PinRemove),
+ };
+ let mut remove_pin_item = MenuItem::new(remove_text, Some(remove_action));
+ remove_pin_item.with_stylesheet(MENU_ITEM_WARNING);
+ unwrap!(items.push(remove_pin_item));
+
+ let submenu_index = self.add_submenu(Submenu::new(items));
+ self.add_subscreen(Subscreen::Submenu(submenu_index))
+ }
+
+ fn add_security_menu(
+ &mut self,
+ pin_code: Option<bool>,
+ auto_lock_delay: Option<TString<'static>>,
+ wipe_code: Option<bool>,
+ check_backup: bool,
+ ) -> usize {
+ let mut items: Vec<MenuItem, MEDIUM_MENU_ITEMS> = Vec::new();
+
+ if let Some(pin_code) = pin_code {
+ let (action, subtext) = if pin_code {
+ let pin_menu_idx = self.add_code_menu(false);
+ let action = Action::GoTo(pin_menu_idx);
+ let subtext = (
+ TR::words__enabled.into(),
+ Some(&theme::TEXT_MENU_ITEM_SUBTITLE_GREEN),
+ );
+ (action, subtext)
+ } else {
+ let action = Action::Return(DeviceMenuMsg::PinCode);
+ let subtext = (TR::words__disabled.into(), None);
+ (action, subtext)
+ };
+
+ let mut pin_code_item = MenuItem::new(TR::pin__title.into(), Some(action));
+ pin_code_item.with_subtext(Some(subtext));
+ unwrap!(items.push(pin_code_item));
+ }
+
+ if let Some(auto_lock_delay) = auto_lock_delay {
+ let mut auto_lock_delay_item = MenuItem::new(
+ TR::auto_lock__title.into(),
+ Some(Action::Return(DeviceMenuMsg::AutoLockDelay)),
+ );
+ auto_lock_delay_item.with_subtext(Some((auto_lock_delay, None)));
+ unwrap!(items.push(auto_lock_delay_item));
+ }
+
+ if let Some(wipe_code) = wipe_code {
+ let (action, subtext) = if wipe_code {
+ let wipe_menu_idx = self.add_code_menu(true);
+ let action = Action::GoTo(wipe_menu_idx);
+ let subtext = (
+ TR::words__enabled.into(),
+ Some(&theme::TEXT_MENU_ITEM_SUBTITLE_GREEN),
+ );
+ (action, subtext)
+ } else {
+ let action = Action::Return(DeviceMenuMsg::WipeCode);
+ let subtext = (TR::words__disabled.into(), None);
+ (action, subtext)
+ };
+
+ let mut wipe_code_item = MenuItem::new(TR::wipe_code__title.into(), Some(action));
+ wipe_code_item.with_subtext(Some(subtext));
+ unwrap!(items.push(wipe_code_item));
+ }
+
+ if check_backup {
+ unwrap!(items.push(MenuItem::new(
+ TR::reset__check_backup_title.into(),
+ Some(Action::Return(DeviceMenuMsg::CheckBackup)),
+ )));
+ }
let submenu_index = self.add_submenu(Submenu::new(items));
self.add_subscreen(Subscreen::Submenu(submenu_index))
diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi
index 37ad0a2c..28da1e6c 100644
--- a/core/mocks/trezortranslate_keys.pyi
+++ b/core/mocks/trezortranslate_keys.pyi
@@ -555,7 +555,8 @@ class TR:
pin__cancel_description: str = "Continue without PIN"
pin__cancel_info: str = "Without a PIN, anyone can access this device."
pin__cancel_setup: str = "Cancel PIN setup"
- pin__change: str = "Change PIN?"
+ pin__change: str = "Change PIN"
+ pin__change_question: str = "Change PIN?"
pin__changed: str = "PIN changed."
pin__cursor_will_change: str = "Position of the cursor will change between entries for enhanced security."
pin__diff_from_wipe_code: str = "The new PIN must be different from your wipe code."
@@ -573,8 +574,10 @@ class TR:
pin__reenter: str = "Re-enter PIN"
pin__reenter_new: str = "Re-enter new PIN"
pin__reenter_to_confirm: str = "Please re-enter PIN to confirm."
+ pin__remove: str = "Remove PIN"
pin__setup_completed: str = "PIN setup completed."
pin__should_be_long: str = "PIN should be 4-50 digits long."
+ pin__title: str = "PIN code"
pin__title_check_pin: str = "Check PIN"
pin__title_settings: str = "PIN settings"
pin__title_wrong_pin: str = "Wrong PIN"
@@ -973,7 +976,8 @@ class TR:
wipe__info: str = "All data will be erased."
wipe__title: str = "Wipe device"
wipe__want_to_wipe: str = "Do you really want to wipe the device?\n"
- wipe_code__change: str = "Change wipe code?"
+ wipe_code__change: str = "Change wipe code"
+ wipe_code__change_question: str = "Change wipe code?"
wipe_code__changed: str = "Wipe code changed."
wipe_code__diff_from_pin: str = "The wipe code must be different from your PIN."
wipe_code__disabled: str = "Wipe code disabled."
@@ -984,6 +988,8 @@ class TR:
wipe_code__mismatch: str = "The wipe codes you entered do not match."
wipe_code__reenter: str = "Re-enter wipe code"
wipe_code__reenter_to_confirm: str = "Please re-enter wipe code to confirm."
+ wipe_code__remove: str = "Remove wipe code"
+ wipe_code__title: str = "Wipe code"
wipe_code__title_check: str = "Check wipe code"
wipe_code__title_invalid: str = "Invalid wipe code"
wipe_code__title_settings: str = "Wipe code settings"
@@ -1014,7 +1020,9 @@ class TR:
words__continue_anyway_question: str = "Continue anyway?"
words__continue_with: str = "Continue with"
words__device: str = "Device"
+ words__disabled: str = "Disabled"
words__disconnect: str = "Disconnect"
+ words__enabled: str = "Enabled"
words__error: str = "Error"
words__fee: str = "Fee"
words__from: str = "from"
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index abea118e..5f7a639a 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -3,31 +3,9 @@ import trezorble as ble
import trezorui_api
from trezor import TR, config, log, utils
from trezor.ui.layouts import interact
-from trezor.wire import ActionCancelled
from trezorui_api import DeviceMenuResult
-async def _prompt_auto_lock_delay() -> int:
- auto_lock_delay_ms = await interact(
- trezorui_api.request_duration(
- title=TR.auto_lock__title,
- duration_ms=storage_device.get_autolock_delay_ms(),
- min_ms=storage_device.AUTOLOCK_DELAY_MINIMUM,
- max_ms=storage_device.AUTOLOCK_DELAY_MAXIMUM,
- description=TR.auto_lock__description,
- ),
- br_name=None,
- )
-
- if auto_lock_delay_ms is not trezorui_api.CANCELLED:
- assert isinstance(auto_lock_delay_ms, int)
- assert auto_lock_delay_ms >= storage_device.AUTOLOCK_DELAY_MINIMUM
- assert auto_lock_delay_ms <= storage_device.AUTOLOCK_DELAY_MAXIMUM
- return auto_lock_delay_ms
- else:
- raise ActionCancelled # user cancelled request number prompt
-
-
async def handle_device_menu() -> None:
from trezor import strings
@@ -42,8 +20,11 @@ async def handle_device_menu() -> None:
firmware_version = ".".join(map(str, utils.VERSION))
firmware_type = "Bitcoin-only" if utils.BITCOIN_ONLY else "Universal"
- auto_lock_ms = storage_device.get_autolock_delay_ms()
- auto_lock_delay = strings.format_autolock_duration(auto_lock_ms)
+ auto_lock_delay = (
+ strings.format_autolock_duration(storage_device.get_autolock_delay_ms())
+ if config.has_pin()
+ else None
+ )
if __debug__:
log.debug(
@@ -55,12 +36,14 @@ async def handle_device_menu() -> None:
trezorui_api.show_device_menu(
failed_backup=failed_backup,
paired_devices=paired_devices,
- connected_idx=None, # TODO implement
- bluetooth=None, # TODO implement
- pin_code=None, # TODO implement
+ connected_idx=None,
+ bluetooth=None,
+ pin_code=config.has_pin() if storage_device.is_initialized() else None,
auto_lock_delay=auto_lock_delay,
- wipe_code=None, # TODO implement
- check_backup=False, # TODO implement
+ wipe_code=(
+ config.has_wipe_code() if storage_device.is_initialized() else None
+ ),
+ check_backup=storage_device.is_initialized(),
device_name=(
(storage_device.get_label() or "Trezor")
if storage_device.is_initialized()
@@ -113,20 +96,62 @@ async def handle_device_menu() -> None:
pass # TODO implement bluetooth handling
# Security settings
elif menu_result is DeviceMenuResult.PinCode:
- pass # TODO implement pin code handling
+ from trezor.messages import ChangePin
+
+ from apps.management.change_pin import change_pin
+
+ await change_pin(ChangePin())
elif menu_result is DeviceMenuResult.PinRemove:
- pass # TODO implement pin remove handling
+ from trezor.messages import ChangePin
+
+ from apps.management.change_pin import change_pin
+
+ await change_pin(ChangePin(remove=True))
elif menu_result is DeviceMenuResult.AutoLockDelay:
- if config.has_pin():
+ from trezor.messages import ApplySettings
+
+ from apps.management.apply_settings import apply_settings
- auto_lock_delay_ms = await _prompt_auto_lock_delay()
- storage_device.set_autolock_delay_ms(auto_lock_delay_ms)
+ assert config.has_pin()
+ auto_lock_delay_ms = await interact(
+ trezorui_api.request_duration(
+ title=TR.auto_lock__title,
+ duration_ms=storage_device.get_autolock_delay_ms(),
+ min_ms=storage_device.AUTOLOCK_DELAY_MINIMUM,
+ max_ms=storage_device.AUTOLOCK_DELAY_MAXIMUM,
+ description=TR.auto_lock__description,
+ ),
+ br_name=None,
+ )
+ assert isinstance(auto_lock_delay_ms, int)
+ await apply_settings(
+ ApplySettings(
+ auto_lock_delay_ms=auto_lock_delay_ms,
+ )
+ )
elif menu_result is DeviceMenuResult.WipeCode:
- pass # TODO implement wipe code handling
+ from trezor.messages import ChangeWipeCode
+
+ from apps.management.change_wipe_code import change_wipe_code
+
+ await change_wipe_code(ChangeWipeCode())
elif menu_result is DeviceMenuResult.WipeRemove:
- pass # TODO implement wipe remove handling
+ from trezor.messages import ChangeWipeCode
+
+ from apps.management.change_wipe_code import change_wipe_code
+
+ await change_wipe_code(ChangeWipeCode(remove=True))
elif menu_result is DeviceMenuResult.CheckBackup:
- pass # TODO implement check backup handling
+ from trezor.enums import RecoveryType
+ from trezor.messages import RecoveryDevice
+
+ from apps.management.recovery_device import recovery_device
+
+ await recovery_device(
+ RecoveryDevice(
+ type=RecoveryType.DryRun,
+ )
+ )
# Device settings
elif menu_result is DeviceMenuResult.DeviceName:
from trezor.messages import ApplySettings
diff --git a/core/src/apps/management/change_pin.py b/core/src/apps/management/change_pin.py
index 418ac43e..ff26fbaf 100644
--- a/core/src/apps/management/change_pin.py
+++ b/core/src/apps/management/change_pin.py
@@ -79,7 +79,7 @@ def _require_confirm_change_pin(msg: ChangePin) -> Awaitable[None]:
return confirm_change_pin(
"change_pin",
TR.pin__title_settings,
- description=TR.pin__change,
+ description=TR.pin__change_question,
)
if not msg.remove and not has_pin: # setting new pin
diff --git a/core/src/apps/management/change_wipe_code.py b/core/src/apps/management/change_wipe_code.py
index b5256fd4..d4f136a9 100644
--- a/core/src/apps/management/change_wipe_code.py
+++ b/core/src/apps/management/change_wipe_code.py
@@ -75,7 +75,7 @@ def _require_confirm_action(
return confirm_action(
"change_wipe_code",
TR.wipe_code__title_settings,
- description=TR.wipe_code__change,
+ description=TR.wipe_code__change_question,
verb=TR.buttons__change,
)
diff --git a/core/translations/cs.json b/core/translations/cs.json
index 39dc6384..4ce0e2f7 100644
--- a/core/translations/cs.json
+++ b/core/translations/cs.json
@@ -556,7 +556,7 @@
"pin__cancel_description": "Pokračovat bez PIN kódu",
"pin__cancel_info": "Bez PIN kódu může k tomuto zařízení přistupovat kdokoli.",
"pin__cancel_setup": "Zrušit nastavení PIN kódu",
- "pin__change": "Změnit PIN?",
+ "pin__change_question": "Změnit PIN?",
"pin__changed": "PIN byl změněn.",
"pin__cursor_will_change": "Poloha kurzoru se mezi jednotlivými položkami změní pro zvýšení bezpečnosti.",
"pin__diff_from_wipe_code": "Nový PIN se musí lišit od kódu pro vymazání.",
@@ -949,7 +949,7 @@
"wipe__info": "Všechna data budou smazána.",
"wipe__title": "Vymazat zařízení",
"wipe__want_to_wipe": "Opravdu chcete vymazat zařízení?\n",
- "wipe_code__change": "Změnit kód pro vymazání?",
+ "wipe_code__change_question": "Změnit kód pro vymazání?",
"wipe_code__changed": "Kód pro vymazání změněn.",
"wipe_code__diff_from_pin": "Kód pro vymazání se musí lišit od PIN kódu.",
"wipe_code__disabled": "Kód pro vymazání zakázán.",
diff --git a/core/translations/de.json b/core/translations/de.json
index aa60f8cb..f6dbb445 100644
--- a/core/translations/de.json
+++ b/core/translations/de.json
@@ -556,7 +556,7 @@
"pin__cancel_description": "Ohne PIN fortfahren",
"pin__cancel_info": "Ohne PIN kann jeder auf dieses Gerät zugreifen.",
"pin__cancel_setup": "PIN-Einrichtung abbrechen",
- "pin__change": "PIN ändern?",
+ "pin__change_question": "PIN ändern?",
"pin__changed": "PIN geändert.",
"pin__cursor_will_change": "Die Cursorposition ändert sich für mehr Sicherheit zwischen den Einträgen.",
"pin__diff_from_wipe_code": "Die neue PIN muss sich von deinem Löschcode unterscheiden.",
@@ -949,7 +949,7 @@
"wipe__info": "Alle Daten werden gelöscht.",
"wipe__title": "Gerät löschen",
"wipe__want_to_wipe": "Möchtest du die Gerätedaten wirklich löschen?\n",
- "wipe_code__change": "Löschcode ändern?",
+ "wipe_code__change_question": "Löschcode ändern?",
"wipe_code__changed": "Löschcode geändert.",
"wipe_code__diff_from_pin": "Der Löschcode muss sich von deiner PIN unterscheiden.",
"wipe_code__disabled": "Löschcode deaktiviert.",
diff --git a/core/translations/en.json b/core/translations/en.json
index edc0a649..4d90c033 100644
--- a/core/translations/en.json
+++ b/core/translations/en.json
@@ -672,7 +672,8 @@
"Delizia": "Cancel PIN setup",
"Eckhart": "Cancel PIN setup?"
},
- "pin__change": "Change PIN?",
+ "pin__change": "Change PIN",
+ "pin__change_question": "Change PIN?",
"pin__changed": "PIN changed.",
"pin__cursor_will_change": "Position of the cursor will change between entries for enhanced security.",
"pin__diff_from_wipe_code": "The new PIN must be different from your wipe code.",
@@ -695,8 +696,10 @@
"pin__reenter": "Re-enter PIN",
"pin__reenter_new": "Re-enter new PIN",
"pin__reenter_to_confirm": "Please re-enter PIN to confirm.",
+ "pin__remove": "Remove PIN",
"pin__setup_completed": "PIN setup completed.",
"pin__should_be_long": "PIN should be 4-50 digits long.",
+ "pin__title": "PIN code",
"pin__title_check_pin": "Check PIN",
"pin__title_settings": "PIN settings",
"pin__title_wrong_pin": "Wrong PIN",
@@ -1190,7 +1193,8 @@
"wipe__info": "All data will be erased.",
"wipe__title": "Wipe device",
"wipe__want_to_wipe": "Do you really want to wipe the device?\n",
- "wipe_code__change": "Change wipe code?",
+ "wipe_code__change": "Change wipe code",
+ "wipe_code__change_question": "Change wipe code?",
"wipe_code__changed": "Wipe code changed.",
"wipe_code__diff_from_pin": "The wipe code must be different from your PIN.",
"wipe_code__disabled": "Wipe code disabled.",
@@ -1201,6 +1205,8 @@
"wipe_code__mismatch": "The wipe codes you entered do not match.",
"wipe_code__reenter": "Re-enter wipe code",
"wipe_code__reenter_to_confirm": "Please re-enter wipe code to confirm.",
+ "wipe_code__remove": "Remove wipe code",
+ "wipe_code__title": "Wipe code",
"wipe_code__title_check": "Check wipe code",
"wipe_code__title_invalid": "Invalid wipe code",
"wipe_code__title_settings": "Wipe code settings",
@@ -1231,7 +1237,9 @@
"words__continue_anyway_question": "Continue anyway?",
"words__continue_with": "Continue with",
"words__device": "Device",
+ "words__disabled": "Disabled",
"words__disconnect": "Disconnect",
+ "words__enabled": "Enabled",
"words__error": "Error",
"words__fee": "Fee",
"words__from": "from",
diff --git a/core/translations/es.json b/core/translations/es.json
index 464748f0..071d15ed 100644
--- a/core/translations/es.json
+++ b/core/translations/es.json
@@ -556,7 +556,7 @@
"pin__cancel_description": "Continuar sin PIN",
"pin__cancel_info": "Sin un PIN, cualquiera puede acceder al dispositivo.",
"pin__cancel_setup": "Cancelar config. de PIN",
- "pin__change": "¿Cambiar PIN?",
+ "pin__change_question": "¿Cambiar PIN?",
"pin__changed": "PIN cambiado.",
"pin__cursor_will_change": "La posición del cursor irá cambiando para mejorar la seguridad.",
"pin__diff_from_wipe_code": "El nuevo PIN no debe ser el código de borrar.",
@@ -954,7 +954,7 @@
"wipe__info": "Se borrarán todos los datos.",
"wipe__title": "Borrar dispositivo",
"wipe__want_to_wipe": "¿Quieres borrar el dispositivo?\n",
- "wipe_code__change": "¿Cambiar el código de borrar?",
+ "wipe_code__change_question": "¿Cambiar el código de borrar?",
"wipe_code__changed": "El código de borrar se ha cambiado.",
"wipe_code__diff_from_pin": "El código de borrar debe ser diferente del PIN.",
"wipe_code__disabled": "El código de borrar se ha desactivado.",
diff --git a/core/translations/fr.json b/core/translations/fr.json
index a17bd3eb..2cab621b 100644
--- a/core/translations/fr.json
+++ b/core/translations/fr.json
@@ -556,7 +556,7 @@
"pin__cancel_description": "Continuer sans PIN",
"pin__cancel_info": "Sans PIN, tout le monde peut accéder à ce dispositif.",
"pin__cancel_setup": "Annuler la config. du PIN",
- "pin__change": "Modifier le PIN ?",
+ "pin__change_question": "Modifier le PIN ?",
"pin__changed": "PIN modifié.",
"pin__cursor_will_change": "La position du curseur change entre les entrées pour plus de sécurité.",
"pin__diff_from_wipe_code": "Le nouveau PIN doit être différent de votre code d'eff.",
@@ -949,7 +949,7 @@
"wipe__info": "Toutes les données seront effacées.",
"wipe__title": "Effacer disp.",
"wipe__want_to_wipe": "Voulez-vous vraiment effacer le disp. ?\n",
- "wipe_code__change": "Modifier le code d'eff. ?",
+ "wipe_code__change_question": "Modifier le code d'eff. ?",
"wipe_code__changed": "Code d'eff. modifié.",
"wipe_code__diff_from_pin": "Le code d'eff. doit être différent de votre PIN.",
"wipe_code__disabled": "Code d'eff. désactivé.",
diff --git a/core/translations/it.json b/core/translations/it.json
index 1170f6fd..298fc8ac 100644
--- a/core/translations/it.json
+++ b/core/translations/it.json
@@ -549,7 +549,7 @@
"pin__cancel_description": "Continua senza PIN",
"pin__cancel_info": "Senza PIN, tutti possono accedere al dispositivo.",
"pin__cancel_setup": "Annulla impostazione PIN",
- "pin__change": "Modificare PIN?",
+ "pin__change_question": "Modificare PIN?",
"pin__changed": "PIN modificato.",
"pin__cursor_will_change": "La posizione del cursore cambia tra le voci per maggiore sicurezza.",
"pin__diff_from_wipe_code": "Il nuovo PIN deve essere diverso dal codice di eliminaz.",
@@ -923,7 +923,7 @@
"wipe__info": "Tutti i dati verranno cancellati.",
"wipe__title": "Elimina dati disp.",
"wipe__want_to_wipe": "Vuoi eliminare i dati dal dispositivo?\n",
- "wipe_code__change": "Modificare il codice di eliminazione?",
+ "wipe_code__change_question": "Modificare il codice di eliminazione?",
"wipe_code__changed": "Codice di eliminazione modificato.",
"wipe_code__diff_from_pin": "Il codice di eliminazione deve essere diverso dal PIN.",
"wipe_code__disabled": "Codice di eliminazione disabilitato.",
diff --git a/core/translations/order.json b/core/translations/order.json
index 9f95bfff..53251477 100644
--- a/core/translations/order.json
+++ b/core/translations/order.json
@@ -1105,5 +1105,13 @@
"1103": "words__off",
"1104": "words__on",
"1105": "words__review",
- "1106": "words__security"
+ "1106": "words__security",
+ "1107": "pin__change_question",
+ "1108": "pin__remove",
+ "1109": "pin__title",
+ "1110": "wipe_code__change_question",
+ "1111": "wipe_code__remove",
+ "1112": "wipe_code__title",
+ "1113": "words__disabled",
+ "1114": "words__enabled"
}
diff --git a/core/translations/pt.json b/core/translations/pt.json
index 3bc79dae..832a8653 100644
--- a/core/translations/pt.json
+++ b/core/translations/pt.json
@@ -557,7 +557,7 @@
"pin__cancel_description": "Continuar sem PIN",
"pin__cancel_info": "Sem um PIN qualquer pessoa pode acessar este dispositivo.",
"pin__cancel_setup": "Cancelar configuração PIN",
- "pin__change": "Alterar PIN?",
+ "pin__change_question": "Alterar PIN?",
"pin__changed": "PIN alterado.",
"pin__cursor_will_change": "A posição do cursor mudará entre as entradas para maior segurança.",
"pin__diff_from_wipe_code": "O novo PIN deve ser diferente do seu código de limpeza.",
@@ -953,7 +953,7 @@
"wipe__info": "Todos os dados serão apagados.",
"wipe__title": "Limpar dispositivo",
"wipe__want_to_wipe": "Deseja mesmo limpar o dispositivo?\n",
- "wipe_code__change": "Alterar código de limpeza?",
+ "wipe_code__change_question": "Alterar código de limpeza?",
"wipe_code__changed": "O código de limpeza foi alterado.",
"wipe_code__diff_from_pin": "O código de limpeza deve ser diferente do seu PIN.",
"wipe_code__disabled": "Código de limpeza desativado.",
diff --git a/core/translations/signatures.json b/core/translations/signatures.json
index 8978651d..2a6cbeb5 100644
--- a/core/translations/signatures.json
+++ b/core/translations/signatures.json
@@ -1,8 +1,8 @@
{
"current": {
- "merkle_root": "8831f35419234e4ff06e269d941c33cd364118e9ca935ee4587611172bd8612a",
- "datetime": "2025-08-25T10:11:10.597216+00:00",
- "commit": "5bc7576aabff225f66984c24d9d343a46f4a64fe"
+ "merkle_root": "0fc289ede58a215cc208d7fa364371c68494c4ca6de6e247dd312fa130134a4f",
+ "datetime": "2025-08-26T12:39:15.483270+00:00",
+ "commit": "19f5faca506cc28166289f45029bd3b4ac5b93fe"
},
"history": [
{
diff --git a/tests/click_tests/test_pin.py b/tests/click_tests/test_pin.py
index 2786aaa4..b9a7f3da 100644
--- a/tests/click_tests/test_pin.py
+++ b/tests/click_tests/test_pin.py
@@ -120,7 +120,7 @@ def prepare(
device_handler.run_with_provided_session(device_handler.client.get_seedless_session(), device.change_pin) # type: ignore
_assert_pin_entry(debug)
_input_see_confirm(debug, old_pin)
- debug.synchronize_at(TR.pin__change)
+ debug.synchronize_at(TR.pin__change_question)
go_next(debug)
_input_see_confirm(debug, old_pin)
elif situation == Situation.WIPE_CODE_SETUP:
Why this scored 30/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.