What changed, and why it matters
This commit is a routine code cleanup: it moves the progress-bar UI code behind a Rust trait (interface) so different parts of the firmware can use it through a common abstraction. It does not change security behavior, fix a bug, or add a new user-facing feature beyond the existing progress bars.
No security action required. Treat as normal refactoring during code review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces a Progress trait and a progress_create() method on the Ui HAL trait, then refactors Bitcoin transaction signing and Bluetooth firmware upgrade code to use hal.ui().progress_create(...) and progress.set(...) instead of calling bitbox02::ui::progress_create(...) and bitbox02::ui::progress_set(...) directly. The concrete BitBox02 implementation still calls the same underlying C UI functions (progress_create, progress_set, screen_stack_push). The test mock provides a NoopProgress. This is purely an architectural refactor with no functional change to progress values, screen-stack behavior, or security checks.
Changed components
src/rust/bitbox-hal/src/ui.rssrc/rust/bitbox02/src/hal/ui.rssrc/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rssrc/rust/bitbox02-rust/src/hww/api/bluetooth.rssrc/rust/bitbox02-rust/src/hal/testing/ui.rsInspect captured patch +65 / −32
diff --git a/src/rust/bitbox-hal/src/ui.rs b/src/rust/bitbox-hal/src/ui.rs
index c45a3fd..746845d 100644
--- a/src/rust/bitbox-hal/src/ui.rs
+++ b/src/rust/bitbox-hal/src/ui.rs
@@ -60,8 +60,15 @@ pub enum CanCancel {
Yes,
}
+pub trait Progress {
+ /// Set progress. `progress` should be in the range `[0.0, 1.0]`.
+ fn set(&mut self, progress: f32);
+}
+
#[allow(async_fn_in_trait)]
pub trait Ui {
+ type Progress: Progress;
+
/// Returns `Ok(())` if the user accepts, `Err(UserAbort)` if the user rejects.
async fn confirm(&mut self, params: &ConfirmParams<'_>) -> Result<(), UserAbort>;
@@ -78,6 +85,8 @@ pub trait Ui {
async fn status(&mut self, title: &str, status_success: bool);
+ fn progress_create(&mut self, title: &str) -> Self::Progress;
+
/// 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 0405203..efb7ee5 100644
--- a/src/rust/bitbox02-rust/src/hal/testing/ui.rs
+++ b/src/rust/bitbox02-rust/src/hal/testing/ui.rs
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::hal::Ui;
-use crate::hal::ui::{CanCancel, ConfirmParams, EnterStringParams, TrinaryChoice, UserAbort};
+use crate::hal::ui::{
+ CanCancel, ConfirmParams, EnterStringParams, Progress, TrinaryChoice, UserAbort,
+};
use alloc::boxed::Box;
use alloc::collections::VecDeque;
@@ -55,7 +57,19 @@ pub struct TestingUi<'a> {
_quiz_choices: VecDeque<u8>,
}
+pub struct NoopProgress;
+
+impl Progress for NoopProgress {
+ fn set(&mut self, _progress: f32) {}
+}
+
impl Ui for TestingUi<'_> {
+ type Progress = NoopProgress;
+
+ fn progress_create(&mut self, _title: &str) -> Self::Progress {
+ NoopProgress
+ }
+
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 93c6103..1d7a042 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -2,7 +2,7 @@
use super::Error;
use super::pb;
-use crate::hal::ui::ConfirmParams;
+use crate::hal::ui::{ConfirmParams, Progress};
use super::common::format_amount;
use super::payment_request;
@@ -306,7 +306,7 @@ async fn handle_prevtx(
input_index: u32,
input: &pb::BtcSignInputRequest,
num_inputs: u32,
- progress_component: &mut bitbox02::ui::Component,
+ progress_component: &mut impl Progress,
next_response: &mut NextResponse,
) -> Result<(), Error> {
let prevtx_init = get_prevtx_init(input_index, next_response).await?;
@@ -324,7 +324,7 @@ async fn handle_prevtx(
hasher.update(serialize_varint(prevtx_init.num_inputs as u64).as_slice());
for prevtx_input_index in 0..prevtx_init.num_inputs {
// Update progress.
- bitbox02::ui::progress_set(progress_component, {
+ progress_component.set({
let step = 1f32 / (num_inputs as f32);
let subprogress: f32 = (prevtx_input_index as f32)
/ (prevtx_init.num_inputs + prevtx_init.num_outputs) as f32;
@@ -342,7 +342,7 @@ async fn handle_prevtx(
hasher.update(serialize_varint(prevtx_init.num_outputs as u64).as_slice());
for prevtx_output_index in 0..prevtx_init.num_outputs {
// Update progress.
- bitbox02::ui::progress_set(progress_component, {
+ progress_component.set({
let step = 1f32 / (num_inputs as f32);
let subprogress: f32 = (prevtx_init.num_inputs + prevtx_output_index) as f32
/ (prevtx_init.num_inputs + prevtx_init.num_outputs) as f32;
@@ -687,11 +687,7 @@ async fn _process(
// transaction.
let mut payment_request_seen = false;
- let mut progress_component = {
- let mut c = bitbox02::ui::progress_create("Loading transaction...");
- c.screen_stack_push();
- Some(c)
- };
+ let mut progress_component = Some(hal.ui().progress_create("Loading transaction..."));
let mut next_response = NextResponse {
next: Default::default(),
@@ -720,10 +716,10 @@ async fn _process(
for input_index in 0..request.num_inputs {
// Update progress.
- bitbox02::ui::progress_set(
- progress_component.as_mut().unwrap(),
- (input_index as f32) / (request.num_inputs as f32),
- );
+ progress_component
+ .as_mut()
+ .unwrap()
+ .set((input_index as f32) / (request.num_inputs as f32));
let tx_input = get_tx_input(input_index, &mut next_response).await?;
let script_config_account = validated_script_configs
@@ -809,7 +805,7 @@ async fn _process(
}
// The progress for loading the inputs is 100%.
- bitbox02::ui::progress_set(progress_component.as_mut().unwrap(), 1.);
+ progress_component.as_mut().unwrap().set(1.);
let hash_prevouts = hasher_prevouts.finalize();
let hash_sequence = hasher_sequence.finalize();
@@ -1117,9 +1113,7 @@ async fn _process(
// Show progress of signing inputs if there are more than 2 inputs. This is an arbitrary cutoff;
// less or equal to 2 inputs is fast enough so it does not need a progress bar.
let mut progress_component = if request.num_inputs > 2 {
- let mut c = bitbox02::ui::progress_create("Signing transaction...");
- c.screen_stack_push();
- Some(c)
+ Some(hal.ui().progress_create("Signing transaction..."))
} else {
None
};
@@ -1262,7 +1256,7 @@ async fn _process(
// Update progress.
if let Some(ref mut c) = progress_component {
- bitbox02::ui::progress_set(c, (input_index + 1) as f32 / (request.num_inputs as f32));
+ c.set((input_index + 1) as f32 / (request.num_inputs as f32));
}
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs b/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs
index 4e31343..0721f0b 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs
@@ -2,7 +2,7 @@
use super::Error;
use super::pb;
-use crate::hal::ui::ConfirmParams;
+use crate::hal::ui::{ConfirmParams, Progress};
use hex_lit::hex;
@@ -58,6 +58,7 @@ trait Funcs {
async fn _process_upgrade(
funcs: &mut impl Funcs,
+ progress: &mut impl Progress,
request: &pb::BluetoothUpgradeInitRequest,
allowed_hash: &[u8; 32],
) -> Result<Response, Error> {
@@ -81,10 +82,6 @@ async fn _process_upgrade(
// The host needs to send this many chunks.
let num_chunks = request.firmware_length.div_ceil(SPI_ERASE_SIZE);
- // Show progress
- let mut progress_component = bitbox02::ui::progress_create("Upgrading...");
- progress_component.screen_stack_push();
-
// Stream chunks from host.
for chunk_index in 0..num_chunks {
let chunk_offset = chunk_index * SPI_ERASE_SIZE;
@@ -102,14 +99,9 @@ async fn _process_upgrade(
.map_err(|_| Error::Memory)?;
// Update progress.
- bitbox02::ui::progress_set(
- &mut progress_component,
- (chunk_index + 1) as f32 / (num_chunks as f32),
- );
+ progress.set((chunk_index + 1) as f32 / (num_chunks as f32));
}
- drop(progress_component);
-
let firmware_hash: [u8; 32] = firmware_hasher.finalize().into();
if &firmware_hash != allowed_hash {
return Err(Error::InvalidInput);
@@ -151,7 +143,9 @@ async fn process_upgrade(
})
.await?;
- let response = _process_upgrade(&mut RealFuncs, request, &ALLOWED_HASH).await;
+ let mut progress = hal.ui().progress_create("Upgrading...");
+ let response = _process_upgrade(&mut RealFuncs, &mut progress, request, &ALLOWED_HASH).await;
+ drop(progress);
if response.is_ok() {
hal.ui().status("Upgrade\nsuccessful", true).await;
@@ -296,6 +290,7 @@ mod tests {
assert!(
block_on(_process_upgrade(
&mut mock_funcs,
+ &mut crate::hal::testing::ui::NoopProgress,
&pb::BluetoothUpgradeInitRequest {
firmware_length: test.firmware_length,
},
diff --git a/src/rust/bitbox02/src/hal/ui.rs b/src/rust/bitbox02/src/hal/ui.rs
index 6fa2daf..31faa44 100644
--- a/src/rust/bitbox02/src/hal/ui.rs
+++ b/src/rust/bitbox02/src/hal/ui.rs
@@ -3,10 +3,23 @@
use alloc::string::String;
use bitbox_hal::Ui;
-use bitbox_hal::ui::{CanCancel, ConfirmParams, EnterStringParams, Font, TrinaryChoice, UserAbort};
+use bitbox_hal::ui::{
+ CanCancel, ConfirmParams, EnterStringParams, Font, Progress as HalProgress, TrinaryChoice,
+ UserAbort,
+};
pub struct BitBox02Ui;
+pub struct BitBox02Progress {
+ component: crate::ui::Component,
+}
+
+impl HalProgress for BitBox02Progress {
+ fn set(&mut self, progress: f32) {
+ crate::ui::progress_set(&mut self.component, progress);
+ }
+}
+
fn to_bitbox02_font(font: Font) -> crate::ui::Font {
match font {
Font::Default => crate::ui::Font::Default,
@@ -53,6 +66,14 @@ fn to_hal_trinary_choice(choice: crate::ui::TrinaryChoice) -> TrinaryChoice {
}
impl Ui for BitBox02Ui {
+ type Progress = BitBox02Progress;
+
+ fn progress_create(&mut self, title: &str) -> Self::Progress {
+ let mut component = crate::ui::progress_create(title);
+ component.screen_stack_push();
+ BitBox02Progress { component }
+ }
+
#[inline(always)]
async fn confirm(&mut self, params: &ConfirmParams<'_>) -> Result<(), UserAbort> {
let params = to_bitbox02_confirm_params(params);
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.