What changed, and why it matters
This commit rewrites the BitBox02's startup orientation screen from C to Rust and introduces a new shared delay/timer subsystem. It is a refactoring change: the device still asks the user to pick screen orientation at boot, then waits 1.3 seconds before switching to the lock screen and enabling USB/Bluetooth. The rewrite changes how internal timers and product-version strings are handled, but it does not add or remove security features. There is no vendor statement that this fixes a security bug.
Treat as a normal refactoring commit. Review the new delay subsystem for race conditions and slot exhaustion, verify the Rust/C callback lifetime contract in `orientation_arrows`, and ensure `delay_cancel` is always called (including on task abort) to avoid leaking timer slots. No immediate security patch is indicated.
Security signals we found
New timer/delay subsystem with fixed 10-slot pool; `delay_init_ms` aborts on exhaustion
Callback double-invocation mitigation in `orientation_arrows.c` (`done_callback` nulled after use)
Product/version string generation moved to `platform_init.c` and shared between bootloader and firmware
Removal of `volatile` qualifier on BLE product pointer/length now that it is no longer set from interrupt context
Async Rust task state for orientation screen polled from main loop; potential lifetime concerns around callback closure passed to C
Evidence from the diff
The change migrates workflow/orientation_screen.c to Rust (bitbox02-rust/src/workflow/orientation_screen.rs) and adds a delay.c/delay.h module backed by TIMER_0 with up to 10 concurrent one-shot timer slots. A Rust Future-based wrapper (bitbox02/src/delay.rs) allows async delay_for(Duration). The orientation workflow is now spawned as an async task polled from the firmware main loop; on completion it sets the BLE product string via platform_product() and calls usb_start(). The C orientation-arrows component now nulls its done callback after first invocation to prevent double invocation. The old volatile qualifier on da14531_handler_current_product is removed because the value is now set from normal context, not interrupt context.
Changed components
src/workflow/orientation_screen.c/h (deleted)src/rust/bitbox02-rust/src/workflow/orientation_screen.rs (new)src/delay.c/h (new)src/rust/bitbox02/src/delay.rs (new)src/firmware_main_loop.csrc/da14531/da14531_handler.c/hsrc/platform/platform_init.c/hsrc/ui/components/orientation_arrows.csrc/rust/bitbox02-rust-c/src/workflow.rsInspect captured patch +506 / −202
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fd3383b..29b5f67 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -16,6 +16,7 @@
set(DBB-FIRMWARE-SOURCES
${CMAKE_SOURCE_DIR}/src/firmware_main_loop.c
+ ${CMAKE_SOURCE_DIR}/src/delay.c
${CMAKE_SOURCE_DIR}/src/keystore.c
${CMAKE_SOURCE_DIR}/src/random.c
${CMAKE_SOURCE_DIR}/src/hardfault.c
@@ -36,7 +37,6 @@ set(DBB-FIRMWARE-SOURCES
${CMAKE_SOURCE_DIR}/src/touch/gestures.c
${CMAKE_SOURCE_DIR}/src/reset.c
${CMAKE_SOURCE_DIR}/src/cipher/cipher.c
- ${CMAKE_SOURCE_DIR}/src/workflow/orientation_screen.c
${CMAKE_SOURCE_DIR}/src/queue.c
${CMAKE_SOURCE_DIR}/src/usb/usb_processing.c
)
diff --git a/src/bootloader/startup.c b/src/bootloader/startup.c
index 3314a02..830da3d 100644
--- a/src/bootloader/startup.c
+++ b/src/bootloader/startup.c
@@ -104,8 +104,9 @@ int main(void)
// Set product to bootloader string, this is necessary if we have rebooted from firmware. Must
// be done after usb_processing is initalized to avoid getting request from the app to early.
- da14531_handler_current_product = (const uint8_t*)DEVICE_MODE;
- da14531_handler_current_product_len = sizeof(DEVICE_MODE) - 1;
+ size_t product_len;
+ da14531_handler_current_product = (const uint8_t*)platform_product(&product_len);
+ da14531_handler_current_product_len = product_len;
da14531_set_product(
da14531_handler_current_product, da14531_handler_current_product_len, &uart_write_queue);
diff --git a/src/da14531/da14531_handler.c b/src/da14531/da14531_handler.c
index 7e2185e..90d2403 100644
--- a/src/da14531/da14531_handler.c
+++ b/src/da14531/da14531_handler.c
@@ -25,10 +25,8 @@
#include <ui/components/ui_images.h>
#include <ui/fonts/monogram_5X9.h>
-// These are set from interrupt context in the orientation workflow :/ therefore they need to be
-// volatile
-volatile const uint8_t* da14531_handler_current_product = NULL;
-volatile uint16_t da14531_handler_current_product_len = 0;
+const uint8_t* da14531_handler_current_product = NULL;
+uint16_t da14531_handler_current_product_len = 0;
struct da14531_ctrl_frame {
enum da14531_protocol_packet_type type;
diff --git a/src/da14531/da14531_handler.h b/src/da14531/da14531_handler.h
index de0061b..c39535e 100644
--- a/src/da14531/da14531_handler.h
+++ b/src/da14531/da14531_handler.h
@@ -19,8 +19,8 @@
#include <platform/platform_config.h>
#include <utils_ringbuffer.h>
-extern volatile const uint8_t* da14531_handler_current_product;
-extern volatile uint16_t da14531_handler_current_product_len;
+extern const uint8_t* da14531_handler_current_product;
+extern uint16_t da14531_handler_current_product_len;
#if FACTORYSETUP == 1
bool da14531_handler_bond_db_set(void);
diff --git a/src/delay.c b/src/delay.c
new file mode 100644
index 0000000..0b7edeb
--- /dev/null
+++ b/src/delay.c
@@ -0,0 +1,92 @@
+// Copyright 2025 Shift Crypto AG
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <delay.h>
+#include <hal_timer.h>
+#include <hardfault.h>
+#include <platform/driver_init.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <util.h>
+#include <utils_assert.h>
+
+struct task {
+ struct timer_task timer;
+ volatile bool done;
+};
+
+static struct task _tasks[10] = {0};
+
+static void _hal_timer_cb(const struct timer_task* const timer)
+{
+ for (size_t i = 0; i < COUNT_OF(_tasks); i++) {
+ if (&_tasks[i].timer == timer) {
+ _tasks[i].done = true;
+ }
+ }
+}
+
+void delay_init_ms(delay_t* self, uint32_t ms)
+{
+ // find an unused slot in tasks
+ size_t i;
+ bool full = false;
+ CRITICAL_SECTION_ENTER()
+ for (i = 0; i < COUNT_OF(_tasks); i++) {
+ if (_tasks[i].timer.cb == NULL && _tasks[i].done == false) {
+ break;
+ }
+ }
+ if (i == COUNT_OF(_tasks)) {
+ full = true;
+ } else if (ms == 0) {
+ _tasks[i].done = true;
+ } else {
+ _tasks[i].done = false;
+ memset(&_tasks[i], 0, sizeof(struct task));
+ _tasks[i].timer.interval = ms;
+ _tasks[i].timer.cb = _hal_timer_cb;
+ _tasks[i].timer.mode = TIMER_TASK_ONE_SHOT;
+ timer_add_task(&TIMER_0, &_tasks[i].timer);
+ }
+ CRITICAL_SECTION_LEAVE()
+ if (full) {
+ Abort("Too many concurrent delays");
+ }
+ self->id = i;
+}
+
+bool delay_is_elapsed(const delay_t* self)
+{
+ ASSERT(self->id < COUNT_OF(_tasks));
+ if (_tasks[self->id].done) {
+ memset(&_tasks[self->id], 0, sizeof(struct task));
+ return true;
+ }
+ return false;
+}
+
+void delay_cancel(const delay_t* self)
+{
+ ASSERT(self->id < COUNT_OF(_tasks));
+ // Check and remove task with disabled interrupts. Otherwise the interrupt may occur
+ // after checking the done flag and then task is removed twice (not allowed).
+ CRITICAL_SECTION_ENTER();
+ if (_tasks[self->id].timer.cb && !_tasks[self->id].done) {
+ timer_remove_task(&TIMER_0, &_tasks[self->id].timer);
+ }
+ memset(&_tasks[self->id], 0, sizeof(struct task));
+ CRITICAL_SECTION_LEAVE();
+}
diff --git a/src/delay.h b/src/delay.h
new file mode 100644
index 0000000..ee7e6c3
--- /dev/null
+++ b/src/delay.h
@@ -0,0 +1,34 @@
+// Copyright 2025 Shift Crypto AG
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef DELAY_H
+#define DELAY_H
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct {
+ size_t id;
+} delay_t;
+
+// Creates a non-blocking delay. Check with delay_is_elapsed if it has elapsed.
+// Limited to 10 concurrent delays, will abort if it fails to allocate one
+void delay_init_ms(delay_t* self, uint32_t ms);
+
+// returns true if time has passed. After it has returned true once it must not be called again
+bool delay_is_elapsed(const delay_t* self);
+
+// Cancel delay if you don't intend to check it until it elapses
+void delay_cancel(const delay_t* self);
+#endif
diff --git a/src/firmware_main_loop.c b/src/firmware_main_loop.c
index 34c1a56..2fca2b2 100644
--- a/src/firmware_main_loop.c
+++ b/src/firmware_main_loop.c
@@ -32,7 +32,7 @@
#include "usb/usb.h"
#include "usb/usb_frame.h"
#include "usb/usb_processing.h"
-#include "workflow/orientation_screen.h"
+#include <platform/platform_init.h>
#include <rust/rust.h>
#include <ui/fonts/monogram_5X9.h>
#include <utils_ringbuffer.h>
@@ -45,6 +45,27 @@
// Must be power of 2
#define UART_OUT_BUF_LEN 2048
+static void _orientation_screen_poll(struct ringbuffer* uart_write_queue)
+{
+ static bool orientation_set = false;
+ bool _orientation;
+ if (!orientation_set && rust_workflow_orientation_screen_poll(&_orientation)) {
+ orientation_set = true;
+ // hww handler in usb_process must be setup before we can allow ble connections
+ if (memory_get_platform() == MEMORY_PLATFORM_BITBOX02_PLUS) {
+ size_t len;
+ da14531_handler_current_product = (const uint8_t*)platform_product(&len);
+ da14531_handler_current_product_len = len;
+ util_log("%s %d", da14531_handler_current_product, da14531_handler_current_product_len);
+ da14531_set_product(
+ da14531_handler_current_product,
+ da14531_handler_current_product_len,
+ uart_write_queue);
+ }
+ usb_start();
+ }
+}
+
void firmware_main_loop(void)
{
// Set the size of uart_read_buf to the size of the ringbuffer in the UART driver so we can read
@@ -63,7 +84,7 @@ void firmware_main_loop(void)
da14531_set_name(buf, strlen(buf), &uart_write_queue);
// This starts the async orientation screen workflow, which is processed by the loop below.
- orientation_screen(&uart_write_queue);
+ rust_workflow_spawn_orientation_screen();
const uint8_t* hww_data = NULL;
uint8_t hww_frame[USB_REPORT_SIZE] = {0};
@@ -178,5 +199,7 @@ void firmware_main_loop(void)
rust_workflow_spin();
rust_async_usb_spin();
+
+ _orientation_screen_poll(&uart_write_queue);
}
}
diff --git a/src/platform/platform_init.c b/src/platform/platform_init.c
index 735bed4..2773984 100644
--- a/src/platform/platform_init.c
+++ b/src/platform/platform_init.c
@@ -17,10 +17,14 @@
#include "memory/spi_mem.h"
#include <driver_init.h>
#include <ui/oled/oled.h>
-#if !defined(BOOTLOADER)
+#if defined(BOOTLOADER)
+ #include <bootloader_version.h>
+#else
#include "sd_mmc/sd_mmc_start.h"
#endif
#include "util.h"
+#include <platform/platform_config.h>
+#include <version.h>
#if !(defined(BOOTLOADER) && PLATFORM_BITBOX02 == 1)
#include "uart.h"
@@ -50,3 +54,38 @@ void platform_init(void)
spi_mem_protected_area_lock();
}
}
+
+#if !(defined(BOOTLOADER) && PLATFORM_BITBOX02PLUS == 0)
+ #if defined(BOOTLOADER)
+ #if PRODUCT_BITBOX_PLUS_MULTI == 1
+ #define DEVICE_MODE "{\"p\":\"bb02p-bl-multi\",\"v\":\"" BOOTLOADER_VERSION "\"}"
+ #elif PRODUCT_BITBOX_PLUS_BTCONLY == 1
+ #define DEVICE_MODE "{\"p\":\"bb02p-bl-btconly\",\"v\":\"" BOOTLOADER_VERSION "\"}"
+ #else
+ #error "unknown product"
+ #endif
+ #else
+ // Currently we have one firmware for both BB02 and BB02_PLUS, and only the
+ // PRODUCT_BITBOX_MULTI/BTCONLY definitions apply. The PRODUCT_BITBOX_PLUS_MULTI/BTCONLY
+ // defs currently only apply in the bootloader, which we don't need here.
+ #if PRODUCT_BITBOX_MULTI == 1
+ #define PRODUCT_STRING_SUFFIX "multi"
+ #elif PRODUCT_BITBOX_BTCONLY == 1
+ #define PRODUCT_STRING_SUFFIX "btconly"
+ #elif PRODUCT_BITBOX02_FACTORYSETUP == 1
+ // Dummy, not actually needed, but this file is currently needlessly compiled for
+ // factorysetup.
+ #define PRODUCT_STRING_SUFFIX "factory"
+ #else
+ #error "unknown edition"
+ #endif
+ #define DEVICE_MODE \
+ "{\"p\":\"bb02p-" PRODUCT_STRING_SUFFIX "\",\"v\":\"" DIGITAL_BITBOX_VERSION "\"}"
+ #endif
+
+const char* platform_product(size_t* len)
+{
+ *len = sizeof(DEVICE_MODE) - 1;
+ return DEVICE_MODE;
+}
+#endif
diff --git a/src/platform/platform_init.h b/src/platform/platform_init.h
index 86164b8..7798c3b 100644
--- a/src/platform/platform_init.h
+++ b/src/platform/platform_init.h
@@ -14,5 +14,12 @@
#ifndef _PLATFORM_INIT_H_
#define _PLATFORM_INIT_H_
+#include <platform/platform_config.h>
+#include <stddef.h>
void platform_init(void);
+
+#if !(defined(BOOTLOADER) && PLATFORM_BITBOX02PLUS == 0)
+// Returns a json string representing the firmware type and version
+const char* platform_product(size_t* len);
+#endif
#endif
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 0ff39fa..68821cd 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -165,6 +165,7 @@ dependencies = [
"bitbox02-noise",
"bitbox02-rust",
"bitcoin",
+ "cortex-m",
"der",
"digest",
"hex",
diff --git a/src/rust/bitbox02-rust-c/src/lib.rs b/src/rust/bitbox02-rust-c/src/lib.rs
index 0030667..aef5a40 100644
--- a/src/rust/bitbox02-rust-c/src/lib.rs
+++ b/src/rust/bitbox02-rust-c/src/lib.rs
@@ -39,7 +39,7 @@ extern crate util;
// handler will print the available information on the screen and over RTT. If we compile with
// `panic=abort` this code will never get executed.
#[cfg_attr(feature = "bootloader", allow(unused_variables))]
-#[cfg(not(any(test, feature = "testing", feature = "c-unit-testing")))]
+#[cfg(not(any(feature = "testing", feature = "c-unit-testing")))]
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
#[cfg(feature = "firmware")]
diff --git a/src/rust/bitbox02-rust-c/src/workflow.rs b/src/rust/bitbox02-rust-c/src/workflow.rs
index e873997..ad903df 100644
--- a/src/rust/bitbox02-rust-c/src/workflow.rs
+++ b/src/rust/bitbox02-rust-c/src/workflow.rs
@@ -24,7 +24,7 @@ extern crate alloc;
use alloc::boxed::Box;
use alloc::string::String;
-use bitbox02_rust::workflow::confirm;
+use bitbox02_rust::workflow::{confirm, orientation_screen};
use core::task::Poll;
use util::bb02_async::{Task, spin};
@@ -42,6 +42,8 @@ static mut CONFIRM_PARAMS: Option<confirm::Params> = None;
static mut CONFIRM_STATE: TaskState<'static, Result<(), confirm::UserAbort>> = TaskState::Nothing;
static mut BITBOX02_HAL: bitbox02_rust::hal::BitBox02Hal = bitbox02_rust::hal::BitBox02Hal::new();
+static mut ORIENTATION_SCREEN_STATE: TaskState<'static, bool> = TaskState::Nothing;
+
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_spawn_unlock() {
unsafe {
@@ -71,6 +73,14 @@ pub unsafe extern "C" fn rust_workflow_spawn_confirm(
}
}
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn rust_workflow_spawn_orientation_screen() {
+ unsafe {
+ ORIENTATION_SCREEN_STATE =
+ TaskState::Running(Box::pin(orientation_screen::orientation_screen()));
+ }
+}
+
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_spin() {
unsafe {
@@ -92,6 +102,15 @@ pub unsafe extern "C" fn rust_workflow_spin() {
}
_ => (),
}
+ match ORIENTATION_SCREEN_STATE {
+ TaskState::Running(ref mut task) => {
+ let result = spin(task);
+ if let Poll::Ready(result) = result {
+ ORIENTATION_SCREEN_STATE = TaskState::ResultAvailable(result);
+ }
+ }
+ _ => (),
+ }
}
}
@@ -131,6 +150,21 @@ pub unsafe extern "C" fn rust_workflow_confirm_poll(result_out: &mut bool) -> bo
}
}
+/// Returns true if there was a result.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn rust_workflow_orientation_screen_poll(result_out: &mut bool) -> bool {
+ unsafe {
+ match ORIENTATION_SCREEN_STATE {
+ TaskState::ResultAvailable(result) => {
+ ORIENTATION_SCREEN_STATE = TaskState::Nothing;
+ *result_out = result;
+ true
+ }
+ _ => false,
+ }
+ }
+}
+
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_abort_current() {
unsafe {
@@ -140,5 +174,7 @@ pub unsafe extern "C" fn rust_workflow_abort_current() {
CONFIRM_BODY = None;
CONFIRM_PARAMS = None;
CONFIRM_STATE = TaskState::Nothing;
+
+ ORIENTATION_SCREEN_STATE = TaskState::Nothing;
}
}
diff --git a/src/rust/bitbox02-rust/src/workflow.rs b/src/rust/bitbox02-rust/src/workflow.rs
index 6be29da..7da142b 100644
--- a/src/rust/bitbox02-rust/src/workflow.rs
+++ b/src/rust/bitbox02-rust/src/workflow.rs
@@ -17,6 +17,7 @@ pub mod confirm;
pub mod menu;
#[cfg_attr(feature = "c-unit-testing", path = "workflow/mnemonic_c_unit_tests.rs")]
pub mod mnemonic;
+pub mod orientation_screen;
pub mod pairing;
pub mod password;
pub mod sdcard;
diff --git a/src/rust/bitbox02-rust/src/workflow/orientation_screen.rs b/src/rust/bitbox02-rust/src/workflow/orientation_screen.rs
new file mode 100644
index 0000000..530190e
--- /dev/null
+++ b/src/rust/bitbox02-rust/src/workflow/orientation_screen.rs
@@ -0,0 +1,42 @@
+// Copyright 2025 Shift Crypto AG
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use bitbox02::delay::delay_for;
+use core::time::Duration;
+use util::bb02_async::option;
+
+pub async fn choose_orientation() -> bool {
+ let result = core::cell::RefCell::new(None as Option<bool>);
+ let mut orientation_arrows = bitbox02::ui::orientation_arrows(|upside_down| {
+ *result.borrow_mut() = Some(upside_down);
+ });
+ orientation_arrows.screen_stack_push();
+ // Wait until orientation has been chosen
+ option(&result).await
+}
+
+pub async fn orientation_screen() -> bool {
+ let upside_down = choose_orientation().await;
+ if upside_down {
+ bitbox02::screen_rotate()
+ }
+
+ // During this delay the bb02 logotype is shown
+ delay_for(Duration::from_millis(1300)).await;
+
+ // Switch to lockscreen that shows "See the bitbox app" and device name
+ bitbox02::ui::screen_process_waiting_switch_to_lockscreen();
+
+ upside_down
+}
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index 56dfd9a..c4e4bbe 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -57,61 +57,63 @@ const ALLOWLIST_TYPES: &[&str] = &[
];
const ALLOWLIST_FNS: &[&str] = &[
- "UG_ClearBuffer",
- "UG_FontSelect",
- "UG_PutString",
- "UG_SendBuffer",
"bip32_derive_xpub",
- "bitbox02_smarteeprom_init",
"bitbox_secp256k1_dleq_prove",
"bitbox_secp256k1_dleq_verify",
+ "bitbox02_smarteeprom_init",
+ "communication_mode_ble_enabled",
"confirm_create",
"confirm_transaction_address_create",
"confirm_transaction_fee_create",
+ "delay_cancel",
+ "delay_init_ms",
"delay_ms",
+ "delay_is_elapsed",
"delay_us",
"empty_create",
- "unlock_animation_create",
+ "fake_memory_factoryreset",
+ "fake_securechip_event_counter_reset",
+ "fake_securechip_event_counter",
+ "gmtime",
"keystore_bip39_mnemonic_to_seed",
+ "keystore_copy_bip39_seed",
+ "keystore_copy_seed",
"keystore_encrypt_and_store_seed",
"keystore_get_bip39_word",
"keystore_secp256k1_nonce_commit",
"keystore_secp256k1_sign",
"keystore_unlock",
"label_create",
- "gmtime",
- "memory_set_salt_root",
"memory_add_noise_remote_static_pubkey",
+ "memory_ble_enable",
+ "memory_ble_enabled",
"memory_bootloader_hash",
"memory_check_noise_remote_static_pubkey",
"memory_get_attestation_bootloader_hash",
"memory_get_attestation_pubkey_and_certificate",
- "memory_get_encrypted_seed_and_hmac",
+ "memory_get_ble_metadata",
"memory_get_device_name",
+ "memory_get_encrypted_seed_and_hmac",
"memory_get_noise_static_private_key",
+ "memory_get_platform",
+ "memory_get_salt_root",
+ "memory_get_securechip_type",
"memory_get_seed_birthdate",
"memory_is_initialized",
"memory_is_mnemonic_passphrase_enabled",
"memory_is_seeded",
- "memory_get_salt_root",
"memory_multisig_get_by_hash",
"memory_multisig_set_by_hash",
+ "memory_set_ble_metadata",
"memory_set_device_name",
"memory_set_initialized",
"memory_set_mnemonic_passphrase_enabled",
+ "memory_set_salt_root",
"memory_set_seed_birthdate",
"memory_setup",
- "memory_ble_enabled",
- "memory_ble_enable",
- "memory_get_ble_metadata",
- "memory_set_ble_metadata",
- "memory_get_platform",
- "memory_get_securechip_type",
"memory_spi_get_active_ble_firmware_version",
- "spi_mem_protected_area_write",
"menu_create",
- "fake_memory_factoryreset",
- "spi_mem_full_erase",
+ "orientation_arrows_create",
"printf",
"progress_create",
"progress_set",
@@ -119,11 +121,13 @@ const ALLOWLIST_FNS: &[&str] = &[
"random_32_bytes",
"random_fake_reset",
"reboot_to_bootloader",
- "reset_reset",
"reset_ble",
+ "reset_reset",
"screen_print_debug",
- "screen_process",
+ "screen_process_waiting_switch_to_lockscreen",
"screen_process_waiting_switch_to_logo",
+ "screen_process",
+ "screen_rotate",
"screen_saver_disable",
"screen_saver_enable",
"sd_card_inserted",
@@ -134,28 +138,32 @@ const ALLOWLIST_FNS: &[&str] = &[
"sd_load_bin",
"sd_write_bin",
"sdcard_create",
+ "secp256k1_anti_exfil_host_verify",
"secp256k1_ecdsa_anti_exfil_host_commit",
"secp256k1_ecdsa_s2c_opening_parse",
- "secp256k1_anti_exfil_host_verify",
"securechip_attestation_sign",
"securechip_kdf",
"securechip_model",
"securechip_monotonic_increments_remaining",
"securechip_u2f_counter_set",
- "fake_securechip_event_counter",
- "fake_securechip_event_counter_reset",
- "smarteeprom_is_enabled",
- "smarteeprom_disable",
"smarteeprom_bb02_config",
+ "smarteeprom_disable",
+ "smarteeprom_is_enabled",
+ "spi_mem_full_erase",
+ "spi_mem_protected_area_write",
"status_create",
"trinary_choice_create",
"trinary_input_string_create",
"trinary_input_string_set_input",
- "ui_screen_stack_pop",
+ "UG_ClearBuffer",
+ "UG_FontSelect",
+ "UG_PutString",
+ "UG_SendBuffer",
"ui_screen_stack_pop_all",
+ "ui_screen_stack_pop",
"ui_screen_stack_push",
+ "unlock_animation_create",
"util_format_datetime",
- "communication_mode_ble_enabled",
];
const RUSTIFIED_ENUMS: &[&str] = &[
@@ -247,7 +255,6 @@ const BITBOX02_SOURCES: &[&str] = &[
"src/usb/usb_processing.c",
"src/usb/usb.c",
"src/util.c",
- "src/workflow/orientation_screen.c",
"external/asf4-drivers/hal/utils/src/utils_ringbuffer.c",
];
diff --git a/src/rust/bitbox02-sys/wrapper.h b/src/rust/bitbox02-sys/wrapper.h
index 94a38d5..df4f059 100644
--- a/src/rust/bitbox02-sys/wrapper.h
+++ b/src/rust/bitbox02-sys/wrapper.h
@@ -13,6 +13,7 @@
// limitations under the License.
#include <communication_mode.h>
+#include <delay.h>
#include <keystore.h>
#include <memory/bitbox02_smarteeprom.h>
#include <memory/memory.h>
@@ -34,6 +35,7 @@
#include <ui/components/empty.h>
#include <ui/components/label.h>
#include <ui/components/menu.h>
+#include <ui/components/orientation_arrows.h>
#include <ui/components/progress.h>
#include <ui/components/sdcard.h>
#include <ui/components/status.h>
@@ -49,6 +51,7 @@
#include <ui/screen_saver.h>
#include <ui/screen_stack.h>
#include <ui/ugui/ugui.h>
+#include <usb/usb.h>
#include <util.h>
#if defined(TESTING)
diff --git a/src/rust/bitbox02/src/delay.rs b/src/rust/bitbox02/src/delay.rs
new file mode 100644
index 0000000..dc9905d
--- /dev/null
+++ b/src/rust/bitbox02/src/delay.rs
@@ -0,0 +1,110 @@
+// Copyright 2025 Shift Crypto AG
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use core::pin::Pin;
+use core::task::{Context, Poll};
+use core::time::Duration;
+
+#[cfg(not(any(feature = "testing", feature = "c-unit-testing")))]
+struct DelayInner {
+ bitbox02_delay: bitbox02_sys::delay_t,
+}
+
+#[cfg(any(feature = "testing", feature = "c-unit-testing"))]
+struct DelayInner {
+ thread_handle: Option<std::thread::JoinHandle<()>>,
+ done: std::sync::Arc<std::sync::atomic::AtomicBool>,
+}
+
+pub struct Delay {
+ inner: DelayInner,
+}
+
+impl Delay {
+ #[cfg(not(any(feature = "testing", feature = "c-unit-testing")))]
+ pub fn from_ms(ms: u32) -> Delay {
+ let mut delay = Delay {
+ inner: DelayInner {
+ bitbox02_delay: bitbox02_sys::delay_t { id: usize::MAX },
+ },
+ };
+ unsafe { bitbox02_sys::delay_init_ms(&mut delay.inner.bitbox02_delay as *mut _, ms) }
+ delay
+ }
+ #[cfg(any(feature = "testing", feature = "c-unit-testing"))]
+ pub fn from_ms(ms: u32) -> Delay {
+ let (thread_handle, done) = if ms == 0 {
+ (
+ None,
+ std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)),
+ )
+ } else {
+ let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
+ let handle = Some(std::thread::spawn({
+ let done = std::sync::Arc::clone(&done);
+ move || {
+ std::thread::sleep(std::time::Duration::from_millis(ms as u64));
+ (*done).store(true, std::sync::atomic::Ordering::Relaxed);
+ // TODO: Waker.wake, once we have an async runtime
+ }
+ }));
+ (handle, done)
+ };
+ Delay {
+ inner: DelayInner {
+ thread_handle,
+ done,
+ },
+ }
+ }
+}
+
+#[cfg(not(any(feature = "testing", feature = "c-unit-testing")))]
+impl Future for Delay {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
+ if unsafe { bitbox02_sys::delay_is_elapsed(&self.inner.bitbox02_delay as *const _) } {
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ }
+}
+
+#[cfg(any(feature = "testing", feature = "c-unit-testing"))]
+impl Future for Delay {
+ type Output = ();
+ fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
+ if self.inner.done.load(std::sync::atomic::Ordering::Relaxed) {
+ if let Some(th) = self.inner.thread_handle.take() {
+ th.join().unwrap();
+ }
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ }
+}
+
+#[cfg(not(any(feature = "testing", feature = "c-unit-testing")))]
+impl Drop for Delay {
+ fn drop(&mut self) {
+ unsafe { bitbox02_sys::delay_cancel(&self.inner.bitbox02_delay as *const _) }
+ }
+}
+
+pub fn delay_for(duration: Duration) -> Delay {
+ Delay::from_ms(duration.as_millis() as u32)
+}
diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs
index 06d0f38..a37b8f4 100644
--- a/src/rust/bitbox02/src/lib.rs
+++ b/src/rust/bitbox02/src/lib.rs
@@ -16,7 +16,8 @@
// This crate contains safe wrappers around C functions provided by bitbox02_sys.
#![no_std]
-#[cfg(test)]
+#[cfg(any(test, feature = "c-unit-testing"))]
+#[allow(unused_imports)]
#[macro_use]
extern crate std;
@@ -33,6 +34,7 @@ use alloc::string::String;
#[cfg(feature = "testing")]
pub mod testing;
+pub mod delay;
pub mod keystore;
pub mod memory;
pub mod random;
@@ -78,6 +80,10 @@ pub fn ug_font_select_11x10() {
unsafe { bitbox02_sys::UG_FontSelect(&bitbox02_sys::font_font_a_11X10) }
}
+pub fn screen_rotate() {
+ unsafe { bitbox02_sys::screen_rotate() }
+}
+
#[cfg_attr(not(target_arch = "arm"), allow(unused_variables))]
pub fn delay(duration: Duration) {
#[cfg(target_arch = "arm")]
diff --git a/src/rust/bitbox02/src/ui.rs b/src/rust/bitbox02/src/ui.rs
index 507845c..e69b695 100644
--- a/src/rust/bitbox02/src/ui.rs
+++ b/src/rust/bitbox02/src/ui.rs
@@ -29,3 +29,7 @@ pub use ui::*;
pub fn screen_process_waiting_switch_to_logo() {
unsafe { bitbox02_sys::screen_process_waiting_switch_to_logo() }
}
+
+pub fn screen_process_waiting_switch_to_lockscreen() {
+ unsafe { bitbox02_sys::screen_process_waiting_switch_to_lockscreen() }
+}
diff --git a/src/rust/bitbox02/src/ui/ui.rs b/src/rust/bitbox02/src/ui/ui.rs
index b595976..904b506 100644
--- a/src/rust/bitbox02/src/ui/ui.rs
+++ b/src/rust/bitbox02/src/ui/ui.rs
@@ -497,3 +497,31 @@ where
_p: PhantomData,
}
}
+
+pub fn orientation_arrows<'a, F>(on_done: F) -> Component<'a>
+where
+ // Callback must outlive component.
+ F: FnMut(bool) + 'a,
+{
+ unsafe extern "C" fn c_on_done<F2>(upside_down: bool, param: *mut c_void)
+ where
+ F2: FnOnce(bool),
+ {
+ // The callback is dropped afterwards. This is safe because
+ // this C callback is guaranteed to be called only once.
+ let on_done = unsafe { Box::from_raw(param as *mut F2) };
+ on_done(upside_down);
+ }
+ let component = unsafe {
+ bitbox02_sys::orientation_arrows_create(
+ Some(c_on_done::<F>),
+ Box::into_raw(Box::new(on_done)) as *mut _, // passed to c_on_done as `param`.
+ )
+ };
+ Component {
+ component,
+ is_pushed: false,
+ on_drop: None,
+ _p: PhantomData,
+ }
+}
diff --git a/src/rust/bitbox02/src/ui/ui_stub.rs b/src/rust/bitbox02/src/ui/ui_stub.rs
index cb13ff2..5b409c6 100644
--- a/src/rust/bitbox02/src/ui/ui_stub.rs
+++ b/src/rust/bitbox02/src/ui/ui_stub.rs
@@ -147,3 +147,15 @@ where
_p: PhantomData,
}
}
+
+pub fn orientation_arrows<'a, F>(on_done: F) -> Component<'a>
+where
+ // Callback must outlive component.
+ F: FnOnce(bool) + 'a,
+{
+ on_done(false);
+ Component {
+ is_pushed: false,
+ _p: PhantomData,
+ }
+}
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 0f19469..69a4b22 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
@@ -190,3 +190,15 @@ where
_p: PhantomData,
}
}
+
+pub fn orientation_arrows<'a, F>(on_done: F) -> Component<'a>
+where
+ // Callback must outlive component.
+ F: FnOnce(bool) + 'a,
+{
+ on_done(false);
+ Component {
+ is_pushed: false,
+ _p: PhantomData,
+ }
+}
diff --git a/src/ui/components/orientation_arrows.c b/src/ui/components/orientation_arrows.c
index 7e64283..9bd962f 100644
--- a/src/ui/components/orientation_arrows.c
+++ b/src/ui/components/orientation_arrows.c
@@ -50,8 +50,9 @@ typedef struct {
static void _flip(component_t* component)
{
orientation_data_t* data = (orientation_data_t*)component->parent->data;
- if (data->enable_touch) {
+ if (data->enable_touch && data->done_callback) {
data->done_callback(true, data->cb_param);
+ data->done_callback = NULL;
}
}
@@ -61,8 +62,9 @@ static void _flip(component_t* component)
static void _stay(component_t* component)
{
orientation_data_t* data = (orientation_data_t*)component->parent->data;
- if (data->enable_touch) {
+ if (data->enable_touch && data->done_callback) {
data->done_callback(false, data->cb_param);
+ data->done_callback = NULL;
}
}
diff --git a/src/util.h b/src/util.h
index cee2235..6eb8d10 100644
--- a/src/util.h
+++ b/src/util.h
@@ -45,6 +45,8 @@
#define STREQ(a, b) (strcmp((a), (b)) == 0)
#define MEMEQ(a, b, c) (memcmp((a), (b), (c)) == 0)
#define SIGMOID(a) (0.0018F * (a) * abs(a) / (1 + 0.002F * (a) * (a)));
+#define COUNT_OF(x) \
+ ((sizeof(x) / sizeof(0 [x])) / ((size_t)(!(sizeof(x) % sizeof(0 [x]))))) // NOLINT
// We define our own true false which are more secure than stdbool true/false becuase it requires
// flipping many more bits.
diff --git a/src/workflow/orientation_screen.c b/src/workflow/orientation_screen.c
deleted file mode 100644
index e00dec7..0000000
--- a/src/workflow/orientation_screen.c
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2019 Shift Cryptosecurity AG
-// Copyright 2025 Shift Crypto AG
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "orientation_screen.h"
-
-#ifndef TESTING
- #include <hal_timer.h>
- #include <platform/driver_init.h>
-#endif
-#include <da14531/da14531.h>
-#include <da14531/da14531_handler.h>
-#include <memory/memory_shared.h>
-#include <screen.h>
-#include <ui/components/lockscreen.h>
-#include <ui/components/orientation_arrows.h>
-#include <ui/screen_process.h>
-#include <ui/screen_stack.h>
-#include <usb/usb.h>
-#include <utils_ringbuffer.h>
-#include <version.h>
-
-#ifndef TESTING
- #define IDLE_PERIOD_MS 1300
-
- // Currently we have one firmware for both BB02 and BB02_PLUS, and only the
- // PRODUCT_BITBOX_MULTI/BTCONLY definitions apply. The PRODUCT_BITBOX_PLUS_MULTI/BTCONLY defs
- // currently only apply in the bootloader, which we don't need here.
- #if PRODUCT_BITBOX_MULTI == 1
- #define PRODUCT_STRING_SUFFIX "multi"
- #elif PRODUCT_BITBOX_BTCONLY == 1
- #define PRODUCT_STRING_SUFFIX "btconly"
- #elif PRODUCT_BITBOX02_FACTORYSETUP == 1
- // Dummy, not actually needed, but this file is currently needlessly compiled for
- // factorysetup.
- #define PRODUCT_STRING_SUFFIX "factory"
- #else
- #error "unknown edition"
- #endif
-
- #define DEVICE_MODE \
- "{\"p\":\"bb02p-" PRODUCT_STRING_SUFFIX "\",\"v\":\"" DIGITAL_BITBOX_VERSION "\"}"
-
-static struct timer_task _idle_timer_task = {0};
-
-struct select_orientation_data {
- struct ringbuffer* uart_out_queue;
-};
-
-static struct select_orientation_data _data = {0};
-
-static void _idle_timer_cb(const struct timer_task* const timer_task)
-{
- (void)timer_task;
-
- // hww handler in usb_process must be setup before we can allow ble connections
- if (memory_get_platform() == MEMORY_PLATFORM_BITBOX02_PLUS) {
- da14531_handler_current_product = (const uint8_t*)DEVICE_MODE;
- da14531_handler_current_product_len = sizeof(DEVICE_MODE) - 1;
- da14531_set_product(
- da14531_handler_current_product,
- da14531_handler_current_product_len,
- _data.uart_out_queue);
- }
-
- usb_start();
- screen_process_waiting_switch_to_lockscreen();
-}
-#endif
-
-static void _select_orientation_done(bool upside_down, void* cb_param)
-{
- (void)cb_param;
- if (upside_down) {
- screen_rotate();
- }
- ui_screen_stack_pop();
-
-#ifndef TESTING
- // Added deliberately as a UX/visual improvement, to show the BB02 logo first before moving onto
- // the lock screen and unlocking USB.
- _idle_timer_task.interval = IDLE_PERIOD_MS;
- _idle_timer_task.cb = _idle_timer_cb;
- _idle_timer_task.mode = TIMER_TASK_ONE_SHOT;
- timer_add_task(&TIMER_0, &_idle_timer_task);
-#endif
-}
-
-void orientation_screen(struct ringbuffer* uart_out_queue)
-{
-#ifndef TESTING
- _data.uart_out_queue = uart_out_queue;
-#else
- (void)uart_out_queue;
-#endif
- ui_screen_stack_push(orientation_arrows_create(_select_orientation_done, NULL));
-}
diff --git a/src/workflow/orientation_screen.h b/src/workflow/orientation_screen.h
deleted file mode 100644
index 23fdf90..0000000
--- a/src/workflow/orientation_screen.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2019 Shift Cryptosecurity AG
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef __ORIENTATION_SCREEN_H
-#define __ORIENTATION_SCREEN_H
-
-#include <utils_ringbuffer.h>
-
-void orientation_screen(struct ringbuffer* uart_out_queue);
-
-#endif // __ORIENTATION_SCREEN_H
diff --git a/test/hardware-fakes/src/fake_delay.c b/test/hardware-fakes/src/fake_delay.c
deleted file mode 100644
index e539f5b..0000000
--- a/test/hardware-fakes/src/fake_delay.c
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2019 Shift Cryptosecurity AG
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stdint.h>
-#include <unistd.h>
-
-void delay_ms(const uint16_t ms)
-{
- usleep(1000 * ms);
-}
-
-void delay_us(const uint16_t us)
-{
- usleep(us);
-}
Why this scored 24/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.