What changed, and why it matters
This commit is a routine internal code cleanup in the BitBox02 firmware. It moves a debug/error screen-printing function into a hardware-abstraction layer (HAL) so different parts of the code can use it without directly depending on low-level screen drivers. The visible behavior—showing error messages on the device screen—stays the same. There is no indication this fixes a security vulnerability or introduces a new attack path.
No security action required. Review as normal code-quality refactor. If auditing, verify that the new `print_screen` HAL implementation preserves the previous delay/screen behavior and that the unlock borrow restructure does not alter error handling.
Security signals we found
Refactor only: same screen-clear, font-select, string-draw, send-buffer, delay sequence is preserved in the new HAL implementation
Panic handler still prints the panic info to screen indefinitely and then hits a breakpoint
Error paths in reset and unlock still halt the device with an on-screen error message
No new input validation, bounds checking, or memory-safety changes are visible
Evidence from the diff
The change refactors print_debug_internal and the print_screen! macro out of bitbox02_rust::general::screen and into the Ui HAL trait as print_screen(duration, msg). Concrete implementations are added for the real BitBox02Ui and the test TestingUi. Call sites in panic handling, reset, and unlock workflows are updated to call the new HAL method. The unlock workflow is also slightly restructured to avoid borrowing conflicts when abort(hal, ...) is called after the async block. No security-relevant behavior change is evident from the diff.
Changed components
src/rust/bitbox-hal/src/ui.rssrc/rust/bitbox02-rust-c/src/lib.rssrc/rust/bitbox02-rust/src/general.rssrc/rust/bitbox02-rust/src/general/screen.rssrc/rust/bitbox02-rust/src/hal/testing/ui.rssrc/rust/bitbox02-rust/src/lib.rssrc/rust/bitbox02-rust/src/reset.rssrc/rust/bitbox02-rust/src/workflow/unlock.rssrc/rust/bitbox02/src/hal/ui.rsInspect captured patch +73 / −68
diff --git a/src/rust/bitbox-hal/src/ui.rs b/src/rust/bitbox-hal/src/ui.rs
index ecb55b9..877050e 100644
--- a/src/rust/bitbox-hal/src/ui.rs
+++ b/src/rust/bitbox-hal/src/ui.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
use alloc::string::String;
+use core::time::Duration;
pub struct UserAbort;
@@ -88,6 +89,10 @@ pub trait Ui {
async fn status(&mut self, title: &str, status_success: bool);
+ /// Render a debug/error message directly to the screen.
+ /// If `duration` is zero, the message remains visible indefinitely.
+ fn print_screen(&mut self, duration: Duration, msg: &str);
+
/// Switches the waiting screen from the lockscreen (as described in
/// [`crate::system::System::startup`]) to the BitBox logo.
fn switch_to_logo(&mut self);
diff --git a/src/rust/bitbox02-rust-c/src/lib.rs b/src/rust/bitbox02-rust-c/src/lib.rs
index 7968dd8..0922e9c 100644
--- a/src/rust/bitbox02-rust-c/src/lib.rs
+++ b/src/rust/bitbox02-rust-c/src/lib.rs
@@ -8,6 +8,7 @@ extern crate std;
#[macro_use]
mod c_alloc;
+extern crate alloc;
#[cfg(feature = "firmware")]
pub mod async_usb;
@@ -115,7 +116,14 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
#[cfg(feature = "firmware")]
::util::log::log!("{}", info);
#[cfg(feature = "firmware")]
- bitbox02_rust::print_screen!(0, "Error: {}", info);
+ {
+ use bitbox_hal::{Hal, Ui};
+
+ let mut hal = crate::HalImpl::new();
+ let msg = alloc::format!("Error: {}", info);
+ hal.ui()
+ .print_screen(core::time::Duration::from_millis(0), &msg);
+ }
cortex_m::asm::bkpt();
loop {}
}
diff --git a/src/rust/bitbox02-rust/src/general.rs b/src/rust/bitbox02-rust/src/general.rs
index 388f8ad..6517268 100644
--- a/src/rust/bitbox02-rust/src/general.rs
+++ b/src/rust/bitbox02-rust/src/general.rs
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
-#[macro_use]
-pub mod screen;
+use crate::hal::Ui;
+use core::time::Duration;
/// displays the input error message on the screen and enters
/// an infinite loop.
#[allow(clippy::empty_loop)]
-pub fn abort(err: &str) -> ! {
- print_screen!(0, "Error: {}", err);
+pub fn abort(hal: &mut impl crate::hal::Hal, err: &str) -> ! {
+ hal.ui()
+ .print_screen(Duration::from_millis(0), &format!("Error: {}", err));
loop {}
}
diff --git a/src/rust/bitbox02-rust/src/general/screen.rs b/src/rust/bitbox02-rust/src/general/screen.rs
deleted file mode 100644
index e3e00b3..0000000
--- a/src/rust/bitbox02-rust/src/general/screen.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-use core::time::Duration;
-
-use bitbox02::{delay, screen_clear, ug_font_select_9x9, ug_put_string, ug_send_buffer};
-
-pub fn print_debug_internal(duration: Duration, msg: &str) {
- screen_clear();
- ug_font_select_9x9();
- ug_put_string(0, 0, msg, false);
- ug_send_buffer();
- delay(duration);
-}
-
-/// This is a convenience macro for printing to the screen.
-///
-/// Example usage:
-///
-/// ```no_run
-/// # #[macro_use] extern crate bitbox02_rust; fn main() {
-/// let my_str = "abc";
-/// print_screen!(1000, "{}", &my_str);
-/// # }
-/// ```
-#[macro_export]
-macro_rules! print_screen {
- ($duration:expr, $($arg:tt)*) => ({
- extern crate alloc;
- let duration = core::time::Duration::from_millis($duration);
- $crate::general::screen::print_debug_internal(duration, &alloc::format!($($arg)*));
- })
-}
diff --git a/src/rust/bitbox02-rust/src/hal/testing/ui.rs b/src/rust/bitbox02-rust/src/hal/testing/ui.rs
index db58a03..490c1fe 100644
--- a/src/rust/bitbox02-rust/src/hal/testing/ui.rs
+++ b/src/rust/bitbox02-rust/src/hal/testing/ui.rs
@@ -9,6 +9,7 @@ use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::string::String;
use alloc::vec::Vec;
+use core::time::Duration;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Screen {
@@ -30,6 +31,10 @@ pub enum Screen {
title: String,
success: bool,
},
+ PrintScreen {
+ message: String,
+ duration: Duration,
+ },
ShowMnemonic {
words: Vec<String>,
},
@@ -147,6 +152,13 @@ impl Ui for TestingUi<'_> {
}
}
+ fn print_screen(&mut self, duration: Duration, msg: &str) {
+ self.screens.push(Screen::PrintScreen {
+ message: msg.into(),
+ duration,
+ });
+ }
+
fn switch_to_logo(&mut self) {}
async fn enter_string(
diff --git a/src/rust/bitbox02-rust/src/lib.rs b/src/rust/bitbox02-rust/src/lib.rs
index fe6f863..105b7a8 100644
--- a/src/rust/bitbox02-rust/src/lib.rs
+++ b/src/rust/bitbox02-rust/src/lib.rs
@@ -12,14 +12,13 @@ mod pb_backup {
include!("./shiftcrypto.bitbox02.backups.rs");
}
-#[macro_use]
-pub mod general;
pub mod async_usb;
pub mod attestation;
pub mod backup;
mod bip32;
pub mod bip39;
pub mod communication_mode;
+pub mod general;
pub mod hal;
pub mod hash;
pub mod hww;
diff --git a/src/rust/bitbox02-rust/src/reset.rs b/src/rust/bitbox02-rust/src/reset.rs
index e63b866..670767d 100644
--- a/src/rust/bitbox02-rust/src/reset.rs
+++ b/src/rust/bitbox02-rust/src/reset.rs
@@ -29,7 +29,7 @@ pub(crate) async fn reset(hal: &mut impl crate::hal::Hal, status: bool) {
}
}
if !reset_ok {
- abort("Could not reset secure chip.");
+ abort(hal, "Could not reset secure chip.");
}
#[cfg(feature = "app-u2f")]
@@ -42,12 +42,12 @@ pub(crate) async fn reset(hal: &mut impl crate::hal::Hal, status: bool) {
}
}
if !u2f_ok {
- abort("Could not initialize U2F counter.");
+ abort(hal, "Could not initialize U2F counter.");
}
}
if hal.memory().reset_hww().is_err() {
- abort("Could not reset memory.");
+ abort(hal, "Could not reset memory.");
}
// Disable SmartEEPROM so it will be erased on next reboot.
diff --git a/src/rust/bitbox02-rust/src/workflow/unlock.rs b/src/rust/bitbox02-rust/src/workflow/unlock.rs
index 8683d53..813c8bd 100644
--- a/src/rust/bitbox02-rust/src/workflow/unlock.rs
+++ b/src/rust/bitbox02-rust/src/workflow/unlock.rs
@@ -156,34 +156,37 @@ pub async fn unlock_bip39(hal: &mut impl crate::hal::Hal, seed: &[u8]) {
}
}
- let crate::hal::HalSubsystems {
- ui,
- random,
- securechip,
- memory,
- ..
- } = hal.as_mut();
- let mut keystore_hal = crate::keystore::KeystoreHalImpl::new(memory, random, securechip);
-
- let ((), result) = futures_lite::future::zip(
- ui.unlock_animation(),
- crate::keystore::unlock_bip39(
- &mut keystore_hal,
- seed,
- &mnemonic_passphrase,
- // for the simulator, we don't yield at all, otherwise unlock becomes very slow in the
- // simulator.
- #[cfg(any(feature = "c-unit-testing", feature = "simulator-graphical"))]
- async || {},
- // we yield every time to keep the processing time per iteration to a minimum.
- #[cfg(not(any(feature = "c-unit-testing", feature = "simulator-graphical")))]
- futures_lite::future::yield_now,
- ),
- )
- .await;
+ let result = {
+ let crate::hal::HalSubsystems {
+ ui,
+ random,
+ securechip,
+ memory,
+ ..
+ } = hal.as_mut();
+ let mut keystore_hal = crate::keystore::KeystoreHalImpl::new(memory, random, securechip);
+
+ let ((), result) = futures_lite::future::zip(
+ ui.unlock_animation(),
+ crate::keystore::unlock_bip39(
+ &mut keystore_hal,
+ seed,
+ &mnemonic_passphrase,
+ // for the simulator, we don't yield at all, otherwise unlock becomes very slow in the
+ // simulator.
+ #[cfg(any(feature = "c-unit-testing", feature = "simulator-graphical"))]
+ async || {},
+ // we yield every time to keep the processing time per iteration to a minimum.
+ #[cfg(not(any(feature = "c-unit-testing", feature = "simulator-graphical")))]
+ futures_lite::future::yield_now,
+ ),
+ )
+ .await;
+ result
+ };
if result.is_err() {
- abort("bip39 unlock failed");
+ abort(hal, "bip39 unlock failed");
}
}
diff --git a/src/rust/bitbox02/src/hal/ui.rs b/src/rust/bitbox02/src/hal/ui.rs
index 93159cb..ac6b72d 100644
--- a/src/rust/bitbox02/src/hal/ui.rs
+++ b/src/rust/bitbox02/src/hal/ui.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
use alloc::string::String;
+use core::time::Duration;
use bitbox_hal::Ui;
use bitbox_hal::ui::{
@@ -129,6 +130,14 @@ impl Ui for BitBox02Ui {
crate::ui::status(title, status_success).await
}
+ fn print_screen(&mut self, duration: Duration, msg: &str) {
+ crate::screen_clear();
+ crate::ug_font_select_9x9();
+ crate::ug_put_string(0, 0, msg, false);
+ crate::ug_send_buffer();
+ crate::delay(duration);
+ }
+
#[inline(always)]
fn switch_to_logo(&mut self) {
crate::ui::screen_process_waiting_switch_to_logo();
Why this scored 18/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.