What changed, and why it matters
This commit is a small internal code cleanup in the BitBox02 hardware wallet firmware. It moves the creation of an empty on-screen placeholder component into a common hardware-abstraction layer (HAL) so that both real device code and automated test code use the same interface. There is no direct security fix here; it is a refactoring that makes the code easier to test and maintain.
No immediate action required. Treat as routine refactoring. If reviewing a release that includes this commit, verify that the real `BitBox02Empty` implementation continues to push the component onto the screen stack, preserving the intended anti-flicker behavior during signing workflows.
Security signals we found
Refactoring only: no change to cryptographic operations, user confirmation logic, or memory safety invariants
Real implementation still calls the same underlying `bitbox02::ui::empty_create()` and `screen_stack_push()` primitives
Test mock now provides a no-op implementation, improving testability
No explicit security relevance stated by the vendor in commit title or message
Evidence from the diff
The change adds an empty_create() method to the Ui HAL trait and implements it for the real BitBox02 UI (BitBox02Empty) and for the testing mock (NoopEmpty). Previously, Bitcoin and Ethereum signing code called bitbox02::ui::empty_create() directly and then pushed it onto the screen stack. Now they call hal.ui().empty_create(), and the real implementation wraps both empty_create() and screen_stack_push() in one place. The behavior on the real device is unchanged; only the call site is abstracted.
Changed components
src/rust/bitbox-hal/src/ui.rssrc/rust/bitbox02/src/hal/ui.rssrc/rust/bitbox02-rust/src/hal/testing/ui.rssrc/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rssrc/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rsInspect captured patch +34 / −10
diff --git a/src/rust/bitbox-hal/src/ui.rs b/src/rust/bitbox-hal/src/ui.rs
index f922a01..ecb55b9 100644
--- a/src/rust/bitbox-hal/src/ui.rs
+++ b/src/rust/bitbox-hal/src/ui.rs
@@ -65,9 +65,12 @@ pub trait Progress {
fn set(&mut self, progress: f32);
}
+pub trait Empty {}
+
#[allow(async_fn_in_trait)]
pub trait Ui {
type Progress: Progress;
+ type Empty: Empty;
/// Returns `Ok(())` if the user accepts, `Err(UserAbort)` if the user rejects.
async fn confirm(&mut self, params: &ConfirmParams<'_>) -> Result<(), UserAbort>;
@@ -91,6 +94,8 @@ pub trait Ui {
fn progress_create(&mut self, title: &str) -> Self::Progress;
+ fn empty_create(&mut self) -> Self::Empty;
+
/// If `can_cancel` is `Yes`, the workflow can be cancelled.
/// If it is `No`, the result is always `Ok(())`.
/// If `preset` is not empty, it must be part of `params.wordlist` and will be pre-entered.
diff --git a/src/rust/bitbox02-rust/src/hal/testing/ui.rs b/src/rust/bitbox02-rust/src/hal/testing/ui.rs
index 0a11bc9..db58a03 100644
--- a/src/rust/bitbox02-rust/src/hal/testing/ui.rs
+++ b/src/rust/bitbox02-rust/src/hal/testing/ui.rs
@@ -2,7 +2,7 @@
use crate::hal::Ui;
use crate::hal::ui::{
- CanCancel, ConfirmParams, EnterStringParams, Progress, TrinaryChoice, UserAbort,
+ CanCancel, ConfirmParams, Empty, EnterStringParams, Progress, TrinaryChoice, UserAbort,
};
use alloc::boxed::Box;
@@ -63,13 +63,22 @@ impl Progress for NoopProgress {
fn set(&mut self, _progress: f32) {}
}
+pub struct NoopEmpty;
+
+impl Empty for NoopEmpty {}
+
impl Ui for TestingUi<'_> {
type Progress = NoopProgress;
+ type Empty = NoopEmpty;
fn progress_create(&mut self, _title: &str) -> Self::Progress {
NoopProgress
}
+ fn empty_create(&mut self) -> Self::Empty {
+ NoopEmpty
+ }
+
async fn confirm(&mut self, params: &ConfirmParams<'_>) -> Result<(), UserAbort> {
self.screens.push(Screen::Confirm {
title: params.title.into(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
index 85c0f8e..858e5bf 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -835,11 +835,7 @@ async fn _process(
// Stop rendering inputs progress update.
drop(progress_component.take());
- empty_component = {
- let mut c = bitbox02::ui::empty_create();
- c.screen_stack_push();
- Some(c)
- };
+ empty_component = Some(hal.ui().empty_create());
}
let output_type = pb::BtcOutputType::try_from(tx_output.r#type)?;
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
index 3b82576..b42b753 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
@@ -520,8 +520,7 @@ pub async fn process(
// Base component on the screen stack during signing, which is shown while the device is waiting
// for the next signing api call. Without this, the 'See the BitBoxApp' waiting screen would
// flicker in between user confirmations.
- let mut empty_component = bitbox02::ui::empty_create();
- empty_component.screen_stack_push();
+ let _empty_component = hal.ui().empty_create();
// Verify address. We don't need the actual result, but we have to propagate validation or user
// abort errors.
diff --git a/src/rust/bitbox02/src/hal/ui.rs b/src/rust/bitbox02/src/hal/ui.rs
index a5ddcbb..93159cb 100644
--- a/src/rust/bitbox02/src/hal/ui.rs
+++ b/src/rust/bitbox02/src/hal/ui.rs
@@ -4,8 +4,8 @@ use alloc::string::String;
use bitbox_hal::Ui;
use bitbox_hal::ui::{
- CanCancel, ConfirmParams, EnterStringParams, Font, Progress as HalProgress, TrinaryChoice,
- UserAbort,
+ CanCancel, ConfirmParams, Empty as HalEmpty, EnterStringParams, Font, Progress as HalProgress,
+ TrinaryChoice, UserAbort,
};
pub struct BitBox02Ui;
@@ -20,6 +20,12 @@ impl HalProgress for BitBox02Progress {
}
}
+pub struct BitBox02Empty {
+ _component: crate::ui::Component,
+}
+
+impl HalEmpty for BitBox02Empty {}
+
fn to_bitbox02_font(font: Font) -> crate::ui::Font {
match font {
Font::Default => crate::ui::Font::Default,
@@ -67,6 +73,7 @@ fn to_hal_trinary_choice(choice: crate::ui::TrinaryChoice) -> TrinaryChoice {
impl Ui for BitBox02Ui {
type Progress = BitBox02Progress;
+ type Empty = BitBox02Empty;
fn progress_create(&mut self, title: &str) -> Self::Progress {
let mut component = crate::ui::progress_create(title);
@@ -74,6 +81,14 @@ impl Ui for BitBox02Ui {
BitBox02Progress { component }
}
+ fn empty_create(&mut self) -> Self::Empty {
+ let mut component = crate::ui::empty_create();
+ component.screen_stack_push();
+ BitBox02Empty {
+ _component: component,
+ }
+ }
+
#[inline(always)]
async fn confirm(&mut self, params: &ConfirmParams<'_>) -> Result<(), UserAbort> {
let params = to_bitbox02_confirm_params(params);
Why this scored 19/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.