feat(core): show pairing code in big font in bld
What changed, and why it matters
This commit is a user-interface improvement for the Trezor hardware wallet bootloader. It makes the Bluetooth pairing code display much larger and easier to read, and spaces out the digits. There is no security vulnerability here; it is purely a usability and accessibility change.
No security action required. This is a normal feature/usability commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change updates format_pairing_code() to insert spaces between digits and an extra space in the middle (e.g., ‘000123’ becomes ‘0 0 0 1 2 3’). It adds a numeral-only variant of the 72 px Satoshi ExtraLight font and uses it in the bootloader’s ConfirmPairingScreen instead of the previous 38 px font. The font is gated to the bootloader feature only. Unit tests are updated to match the new spaced format.
Changed components
core/embed/rust/src/strutil.rscore/embed/rust/src/ui/layout_eckhart/bootloader/confirm_pairing.rscore/embed/rust/src/ui/layout_eckhart/fonts/font_ttsatoshi_extralight_72.rscore/embed/rust/src/ui/layout_eckhart/fonts/mod.rsInspect captured patch +163 / −40
diff --git a/core/embed/rust/src/strutil.rs b/core/embed/rust/src/strutil.rs
index 0ca2e1a2..fe7a2cc8 100644
--- a/core/embed/rust/src/strutil.rs
+++ b/core/embed/rust/src/strutil.rs
@@ -55,19 +55,32 @@ pub fn format_i64(num: i64, buffer: &mut [u8]) -> Option<&str> {
}
}
-/// Format the BLE pairing code with zero-padding so that it always has a
-/// minimum width.
+/// Formats a BLE pairing code as a zero-padded string with spaces between
+/// digits. Adds an extra space after the 3rd digit for improved readability.
+/// Example: code=123, width=6 produces "0 0 0 1 2 3"
pub fn format_pairing_code(code: u32, width: usize) -> ShortString {
let mut buf = [0; 20];
let code_str = unwrap!(format_i64(code as _, &mut buf));
let mut formatted_code = ShortString::new();
- // Add leading zeros
- for _ in 0..width.saturating_sub(code_str.len()) {
- unwrap!(formatted_code.push('0'));
+ let padding = width.saturating_sub(code_str.len());
+
+ for i in 0..width {
+ let c = if i < padding {
+ '0'
+ } else {
+ unwrap!(code_str.chars().nth(i - padding))
+ };
+ unwrap!(formatted_code.push(c));
+ if i < width - 1 {
+ unwrap!(formatted_code.push(' '));
+ if i == (width / 2 - 1) {
+ // extra space in the middle
+ unwrap!(formatted_code.push(' '));
+ }
+ }
}
- // Add the actual digits
- unwrap!(formatted_code.push_str(code_str));
+
formatted_code
}
@@ -303,34 +316,28 @@ mod tests {
use super::format_pairing_code;
let width = 6;
// Test normal cases with different digit counts
- assert_eq!(format_pairing_code(123, width).as_str(), "000123");
- assert_eq!(format_pairing_code(7, width).as_str(), "000007");
- assert_eq!(format_pairing_code(123456, width).as_str(), "123456");
+ assert_eq!(format_pairing_code(123, width).as_str(), "0 0 0 1 2 3");
+ assert_eq!(format_pairing_code(7, width).as_str(), "0 0 0 0 0 7");
+ assert_eq!(format_pairing_code(123456, width).as_str(), "1 2 3 4 5 6");
// Test boundary cases
- assert_eq!(format_pairing_code(0, width).as_str(), "000000");
- assert_eq!(format_pairing_code(999999, width).as_str(), "999999");
- assert_eq!(format_pairing_code(1000000, width).as_str(), "1000000"); // Exceeds 6 digits
-
- // Test with maximum u32 value
- assert_eq!(format_pairing_code(u32::MAX, width).as_str(), "4294967295");
+ assert_eq!(format_pairing_code(0, width).as_str(), "0 0 0 0 0 0");
+ assert_eq!(format_pairing_code(999999, width).as_str(), "9 9 9 9 9 9");
// Test with values having exactly 6 digits
- assert_eq!(format_pairing_code(100000, width).as_str(), "100000");
- assert_eq!(format_pairing_code(999999, width).as_str(), "999999");
+ assert_eq!(format_pairing_code(100000, width).as_str(), "1 0 0 0 0 0");
// Verify behavior with sequential values around boundaries
- assert_eq!(format_pairing_code(9999, width).as_str(), "009999");
- assert_eq!(format_pairing_code(10000, width).as_str(), "010000");
- assert_eq!(format_pairing_code(99999, width).as_str(), "099999");
- assert_eq!(format_pairing_code(100000, width).as_str(), "100000");
-
- // Test different width
- let width = 3;
- assert_eq!(format_pairing_code(1, width).as_str(), "001");
- assert_eq!(format_pairing_code(12, width).as_str(), "012");
- assert_eq!(format_pairing_code(123, width).as_str(), "123");
- assert_eq!(format_pairing_code(1234, width).as_str(), "1234");
+ assert_eq!(format_pairing_code(9999, width).as_str(), "0 0 9 9 9 9");
+ assert_eq!(format_pairing_code(10000, width).as_str(), "0 1 0 0 0 0");
+ assert_eq!(format_pairing_code(99999, width).as_str(), "0 9 9 9 9 9");
+
+ // Test different even width (width=4, middle after position 1)
+ let width = 4;
+ assert_eq!(format_pairing_code(1, width).as_str(), "0 0 0 1");
+ assert_eq!(format_pairing_code(12, width).as_str(), "0 0 1 2");
+ assert_eq!(format_pairing_code(123, width).as_str(), "0 1 2 3");
+ assert_eq!(format_pairing_code(1234, width).as_str(), "1 2 3 4");
}
#[test]
diff --git a/core/embed/rust/src/ui/layout_eckhart/bootloader/confirm_pairing.rs b/core/embed/rust/src/ui/layout_eckhart/bootloader/confirm_pairing.rs
index 27b1ebf0..58b0de69 100644
--- a/core/embed/rust/src/ui/layout_eckhart/bootloader/confirm_pairing.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/bootloader/confirm_pairing.rs
@@ -4,8 +4,9 @@ use crate::{
ui::{
component::{Component, Event, EventCtx, Label},
constant::SCREEN,
+ display::Font,
event::BLEEvent,
- geometry::{Alignment, Rect},
+ geometry::{Alignment, Offset, Point, Rect},
shape::{self, Renderer},
},
};
@@ -91,15 +92,13 @@ impl<'a> Component for ConfirmPairingScreen<'a> {
screen_border.render(u8::MAX, target);
}
- // TODO: font size 72 is requested but it seems pricy for bootloader
- shape::Text::new(
- SCREEN.center(),
- &self.code_formatted,
- fonts::FONT_SATOSHI_REGULAR_38,
- )
- .with_fg(theme::GREY_EXTRA_LIGHT)
- .with_align(Alignment::Center)
- .render(target);
+ const FONT_CODE: Font = fonts::FONT_SATOSHI_EXTRALIGHT_72_NUMS_ONLY;
+ const BASE_POS: Point = SCREEN.center().ofs(Offset::y(FONT_CODE.height));
+
+ shape::Text::new(BASE_POS, &self.code_formatted, FONT_CODE)
+ .with_fg(theme::GREY_EXTRA_LIGHT)
+ .with_align(Alignment::Center)
+ .render(target);
}
}
diff --git a/core/embed/rust/src/ui/layout_eckhart/fonts/font_ttsatoshi_extralight_72.rs b/core/embed/rust/src/ui/layout_eckhart/fonts/font_ttsatoshi_extralight_72.rs
index e33c3c0d..36953944 100644
--- a/core/embed/rust/src/ui/layout_eckhart/fonts/font_ttsatoshi_extralight_72.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/fonts/font_ttsatoshi_extralight_72.rs
@@ -8,6 +8,8 @@
use crate::ui::display::font::FontInfo;
+// NOTE: manually added Font_TTSatoshi_ExtraLight_72_nums and Font_TTSatoshi_ExtraLight_72_NumsOnly_info for the usage in Eckhart Bootloader, see https://github.com/trezor/trezor-firmware/issues/5522
+
/// ' ' (ASCII 32)
const Font_TTSatoshi_ExtraLight_72_glyph_32: [u8; 5] = [ 0, 0, 16, 0, 0 ];
@@ -395,6 +397,105 @@ const Font_TTSatoshi_ExtraLight_72: [&[u8]; 95] = [
&Font_TTSatoshi_ExtraLight_72_glyph_126,
];
+/// Array of references for 'TTSatoshi_ExtraLight_72' numeral-only ASCII glyphs
+const Font_TTSatoshi_ExtraLight_72_nums: [&[u8]; 95] = [
+ &Font_TTSatoshi_ExtraLight_72_glyph_32,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_48,
+ &Font_TTSatoshi_ExtraLight_72_glyph_49,
+ &Font_TTSatoshi_ExtraLight_72_glyph_50,
+ &Font_TTSatoshi_ExtraLight_72_glyph_51,
+ &Font_TTSatoshi_ExtraLight_72_glyph_52,
+ &Font_TTSatoshi_ExtraLight_72_glyph_53,
+ &Font_TTSatoshi_ExtraLight_72_glyph_54,
+ &Font_TTSatoshi_ExtraLight_72_glyph_55,
+ &Font_TTSatoshi_ExtraLight_72_glyph_56,
+ &Font_TTSatoshi_ExtraLight_72_glyph_57,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+ &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+];
+
/// FontInfo struct for normal ASCII usage
pub const Font_TTSatoshi_ExtraLight_72_info: FontInfo = FontInfo {
translation_blob_idx: 1,
@@ -404,3 +505,13 @@ pub const Font_TTSatoshi_ExtraLight_72_info: FontInfo = FontInfo {
glyph_data: &Font_TTSatoshi_ExtraLight_72,
glyph_nonprintable: &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
};
+
+/// FontInfo struct for numeral-only ASCII usage
+pub const Font_TTSatoshi_ExtraLight_72_NumsOnly_info: FontInfo = FontInfo {
+ translation_blob_idx: 1,
+ height: 72,
+ max_height: 74,
+ baseline: 15,
+ glyph_data: &Font_TTSatoshi_ExtraLight_72_nums,
+ glyph_nonprintable: &Font_TTSatoshi_ExtraLight_72_glyph_nonprintable,
+};
diff --git a/core/embed/rust/src/ui/layout_eckhart/fonts/mod.rs b/core/embed/rust/src/ui/layout_eckhart/fonts/mod.rs
index 4580ff5f..b39a1183 100644
--- a/core/embed/rust/src/ui/layout_eckhart/fonts/mod.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/fonts/mod.rs
@@ -1,4 +1,6 @@
//! Font definitions for the Eckhart layout
+//! Allowed only in Bootloader:
+//! - FONT_SATOSHI_EXTRALIGHT_72_NUMS_ONLY
//! Allowed in Bootloader and Firmware:
//! - FONT_SATOSHI_MEDIUM_26
//! - FONT_SATOSHI_REGULAR_38
@@ -15,7 +17,6 @@ mod font_robotomono_light_30;
mod font_robotomono_medium_38;
#[cfg(any(feature = "prodtest", feature = "micropython"))]
mod font_ttsatoshi_extralight_46;
-#[cfg(any(feature = "prodtest", feature = "micropython"))]
mod font_ttsatoshi_extralight_72;
mod font_ttsatoshi_medium_26;
#[cfg(any(feature = "prodtest", feature = "micropython"))]
@@ -28,6 +29,8 @@ use font_robotomono_light_30::Font_RobotoMono_Light_30_info;
use font_robotomono_medium_38::Font_RobotoMono_Medium_38_info;
#[cfg(any(feature = "prodtest", feature = "micropython"))]
use font_ttsatoshi_extralight_46::Font_TTSatoshi_ExtraLight_46_info;
+#[cfg(feature = "bootloader")]
+use font_ttsatoshi_extralight_72::Font_TTSatoshi_ExtraLight_72_NumsOnly_info;
#[cfg(any(feature = "prodtest", feature = "micropython"))]
use font_ttsatoshi_extralight_72::Font_TTSatoshi_ExtraLight_72_info;
use font_ttsatoshi_medium_26::Font_TTSatoshi_Medium_26_info;
@@ -43,6 +46,9 @@ pub const FONT_MONO_MEDIUM_38: crate::ui::display::Font = &Font_RobotoMono_Mediu
pub const FONT_SATOSHI_EXTRALIGHT_46: crate::ui::display::Font = &Font_TTSatoshi_ExtraLight_46_info;
#[cfg(any(feature = "prodtest", feature = "micropython"))]
pub const FONT_SATOSHI_EXTRALIGHT_72: crate::ui::display::Font = &Font_TTSatoshi_ExtraLight_72_info;
+#[cfg(feature = "bootloader")]
+pub const FONT_SATOSHI_EXTRALIGHT_72_NUMS_ONLY: crate::ui::display::Font =
+ &Font_TTSatoshi_ExtraLight_72_NumsOnly_info;
pub const FONT_SATOSHI_MEDIUM_26: crate::ui::display::Font = &Font_TTSatoshi_Medium_26_info;
#[cfg(any(feature = "prodtest", feature = "micropython"))]
pub const FONT_SATOSHI_REGULAR_22: crate::ui::display::Font = &Font_TTSatoshi_Regular_22_info;
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.