What changed, and why it matters
This commit is a routine internal refactor of the BitBox02 firmware's user-interface code. It converts the transaction-address confirmation screen from a callback-based (synchronous-style) API to an async/await API. There is no indication in the commit that it fixes a security bug or changes security behavior; it appears to be a code-quality improvement.
No security action required. Review as normal code-quality change; verify that the async waker logic is sound and that the screensaver inhibitor lifetime covers the full confirmation period.
Security signals we found
Refactor only: no change to input validation, display content, or user confirmation semantics
Callback-to-async conversion with explicit waker management
Screensaver inhibition moved into the async UI function
Evidence from the diff
The change rewrites confirm_transaction_address_create() into an async confirm_transaction_address() that uses a Rust poll_fn future, a shared RefCell<SharedState> storing a Waker and ConfirmResponse, and a C callback that wakes the task. The caller verify_recipient() is simplified to .await the new function and map Approved/Cancelled to Ok(())/Err(UserAbort). Corresponding stubs in ui_stub.rs and ui_stub_c_unit_tests.rs are updated. A ScreensaverInhibitor is now created inside the async function rather than via option_no_screensaver in the caller. No security boundary or trust assumption is visibly altered.
Changed components
src/rust/bitbox02-rust/src/workflow/transaction.rssrc/rust/bitbox02/src/ui/types.rssrc/rust/bitbox02/src/ui/ui.rssrc/rust/bitbox02/src/ui/ui_stub.rssrc/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rsInspect captured patch +71 / −49
diff --git a/src/rust/bitbox02-rust/src/workflow/transaction.rs b/src/rust/bitbox02-rust/src/workflow/transaction.rs
index af96200..22e0e3e 100644
--- a/src/rust/bitbox02-rust/src/workflow/transaction.rs
+++ b/src/rust/bitbox02-rust/src/workflow/transaction.rs
@@ -11,17 +11,10 @@ use alloc::string::String;
pub struct UserAbort;
pub async fn verify_recipient(recipient: &str, amount: &str) -> Result<(), UserAbort> {
- let result = RefCell::new(None as Option<Result<(), UserAbort>>);
-
- let mut component = bitbox02::ui::confirm_transaction_address_create(
- amount,
- recipient,
- Box::new(|ok| {
- *result.borrow_mut() = Some(if ok { Ok(()) } else { Err(UserAbort) });
- }),
- );
- component.screen_stack_push();
- option_no_screensaver(&result).await
+ match bitbox02::ui::confirm_transaction_address(amount, recipient).await {
+ bitbox02::ui::ConfirmResponse::Approved => Ok(()),
+ bitbox02::ui::ConfirmResponse::Cancelled => Err(UserAbort),
+ }
}
fn format_percentage(p: f64) -> String {
diff --git a/src/rust/bitbox02/src/ui/types.rs b/src/rust/bitbox02/src/ui/types.rs
index 240bc41..a65530b 100644
--- a/src/rust/bitbox02/src/ui/types.rs
+++ b/src/rust/bitbox02/src/ui/types.rs
@@ -33,6 +33,11 @@ pub enum SdcardResponse {
Cancelled,
}
+pub enum ConfirmResponse {
+ Approved,
+ Cancelled,
+}
+
#[derive(Default)]
pub struct ConfirmParams<'a> {
/// The confirmation title of the screen. Max 200 chars, otherwise **panic**.
diff --git a/src/rust/bitbox02/src/ui/ui.rs b/src/rust/bitbox02/src/ui/ui.rs
index 8cb6b59..51a9c5a 100644
--- a/src/rust/bitbox02/src/ui/ui.rs
+++ b/src/rust/bitbox02/src/ui/ui.rs
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pub use super::types::{
- AcceptRejectCb, ConfirmParams, ContinueCancelCb, Font, MenuParams, MenuResponse,
- SdcardResponse, SelectWordCb, TrinaryChoice, TrinaryChoiceCb, TrinaryInputStringParams,
+ AcceptRejectCb, ConfirmParams, ConfirmResponse, ContinueCancelCb, Font, MenuParams,
+ MenuResponse, SdcardResponse, SelectWordCb, TrinaryChoice, TrinaryChoiceCb,
+ TrinaryInputStringParams,
};
use core::ffi::{c_char, c_void};
@@ -524,34 +525,67 @@ pub fn trinary_choice_create<'a>(
}
}
-pub fn confirm_transaction_address_create<'a, 'b>(
- amount: &'a str,
- address: &'a str,
- callback: AcceptRejectCb<'b>,
-) -> Component<'b> {
- unsafe extern "C" fn c_callback(result: bool, user_data: *mut c_void) {
- let callback = user_data as *mut AcceptRejectCb;
- unsafe { (*callback)(result) };
+pub async fn confirm_transaction_address(amount: &str, address: &str) -> ConfirmResponse {
+ let _no_screensaver = crate::screen_saver::ScreensaverInhibitor::new();
+
+ // Shared between the async context and the c callback
+ struct SharedState {
+ waker: Option<Waker>,
+ result: Option<ConfirmResponse>,
+ }
+ let shared_state = Box::new(RefCell::new(SharedState {
+ waker: None,
+ result: None,
+ }));
+ let shared_state_ptr = shared_state.as_ref() as *const RefCell<SharedState> as *mut c_void;
+
+ unsafe extern "C" fn callback(result: bool, user_data: *mut c_void) {
+ let shared_state = unsafe { &*(user_data as *mut RefCell<SharedState>) };
+ let mut shared_state = shared_state.borrow_mut();
+ if shared_state.result.is_none() {
+ shared_state.result = Some(if result {
+ ConfirmResponse::Approved
+ } else {
+ ConfirmResponse::Cancelled
+ });
+ if let Some(waker) = shared_state.waker.as_ref() {
+ waker.wake_by_ref();
+ }
+ }
}
- let user_data = Box::into_raw(Box::new(callback)) as *mut c_void;
let component = unsafe {
bitbox02_sys::confirm_transaction_address_create(
util::strings::str_to_cstr_vec(amount).unwrap().as_ptr(), // copied in C
util::strings::str_to_cstr_vec(address).unwrap().as_ptr(), // copied in C
- Some(c_callback as _),
- user_data,
+ Some(callback),
+ shared_state_ptr, // passed to callback as `user_data`.
)
};
- Component {
+
+ let mut component = Component {
component,
is_pushed: false,
- on_drop: Some(Box::new(move || unsafe {
- // Drop all callbacks.
- drop(Box::from_raw(user_data as *mut AcceptRejectCb));
- })),
+ on_drop: None,
_p: PhantomData,
- }
+ };
+ component.screen_stack_push();
+
+ core::future::poll_fn({
+ let shared_state = &shared_state;
+ move |cx| {
+ let mut shared_state = shared_state.borrow_mut();
+
+ if let Some(result) = shared_state.result.take() {
+ Poll::Ready(result)
+ } else {
+ // Store the waker so the callback can wake up this task
+ shared_state.waker = Some(cx.waker().clone());
+ Poll::Pending
+ }
+ }
+ })
+ .await
}
pub fn confirm_transaction_fee_create<'a, 'b>(
diff --git a/src/rust/bitbox02/src/ui/ui_stub.rs b/src/rust/bitbox02/src/ui/ui_stub.rs
index 5e89f6d..3549430 100644
--- a/src/rust/bitbox02/src/ui/ui_stub.rs
+++ b/src/rust/bitbox02/src/ui/ui_stub.rs
@@ -6,8 +6,9 @@
//! workflows now.
pub use super::types::{
- AcceptRejectCb, ConfirmParams, ContinueCancelCb, Font, MenuParams, MenuResponse,
- SdcardResponse, SelectWordCb, TrinaryChoice, TrinaryChoiceCb, TrinaryInputStringParams,
+ AcceptRejectCb, ConfirmParams, ConfirmResponse, ContinueCancelCb, Font, MenuParams,
+ MenuResponse, SdcardResponse, SelectWordCb, TrinaryChoice, TrinaryChoiceCb,
+ TrinaryInputStringParams,
};
use core::marker::PhantomData;
@@ -74,11 +75,7 @@ pub fn trinary_choice_create<'a>(
panic!("not used")
}
-pub fn confirm_transaction_address_create<'a, 'b>(
- _amount: &'a str,
- _address: &'a str,
- _callback: AcceptRejectCb<'b>,
-) -> Component<'b> {
+pub async fn confirm_transaction_address(_amount: &str, _address: &str) -> ConfirmResponse {
panic!("not used");
}
diff --git a/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs b/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs
index ae03ff6..9ca6213 100644
--- a/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs
+++ b/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs
@@ -3,8 +3,9 @@
//! Stubs for the Bitbox02 simulator and also C unit-tests.
pub use super::types::{
- AcceptRejectCb, ConfirmParams, ContinueCancelCb, Font, MenuParams, MenuResponse,
- SdcardResponse, SelectWordCb, TrinaryChoice, TrinaryChoiceCb, TrinaryInputStringParams,
+ AcceptRejectCb, ConfirmParams, ConfirmResponse, ContinueCancelCb, Font, MenuParams,
+ MenuResponse, SdcardResponse, SelectWordCb, TrinaryChoice, TrinaryChoiceCb,
+ TrinaryInputStringParams,
};
use core::marker::PhantomData;
@@ -88,20 +89,12 @@ pub fn trinary_choice_create<'a>(
panic!("not implemented")
}
-pub fn confirm_transaction_address_create<'a, 'b>(
- _amount: &'a str,
- _address: &'a str,
- mut callback: AcceptRejectCb<'b>,
-) -> Component<'b> {
+pub async fn confirm_transaction_address(_amount: &str, _address: &str) -> ConfirmResponse {
crate::print_stdout(&format!(
"CONFIRM TRANSACTION ADDRESS SCREEN START\nAMOUNT: {}\nADDRESS: {}\nCONFIRM TRANSACTION ADDRESS SCREEN END\n",
_amount, _address
));
- callback(true);
- Component {
- is_pushed: false,
- _p: PhantomData,
- }
+ ConfirmResponse::Approved
}
pub fn confirm_transaction_fee_create<'a, 'b>(
Why this scored 12/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.