What changed, and why it matters
This commit rewrites a small piece of firmware logic from C to Rust. The logic decides whether the BitBox02 Nova hardware wallet should use Bluetooth or USB for communication. There is no obvious security bug introduced by the change; it appears to be a routine language port with matching behavior and added unit tests.
No immediate security action is required. Treat as a normal refactoring commit. If auditing, verify that the Rust `SyncCell` implementation provides the same atomicity/safety guarantees as the original C static state in the embedded context, and confirm the `#[unsafe(no_mangle)]` functions are only called from expected C contexts.
Security signals we found
Language port of a communication-mode state machine from C to Rust with no apparent semantic change.
Added unit tests for the ported logic.
Removed C bindings and source files; added Rust `extern "C"` entry points for C callers.
Uses `SyncCell` for mutable static state, which is a common embedded Rust pattern; no unsafe blocks are visible in the new Rust module.
Evidence from the diff
The change ports communication_mode_ble_enabled() and communication_mode_ble_disable() from C to Rust, exposing them via extern "C" as rust_communication_mode_ble_enabled() and rust_communication_mode_ble_disable(). Call sites in the bootloader, firmware main loop, DA14531 BLE protocol code, and Rust hww/noise/bluetooth modules are updated. The Rust implementation uses SyncCell<bool> and SyncCell<Option<bool>> to mirror the original C static state. Unit tests verify that BLE is disabled on non-Plus platforms and disabled after the first USB request on Plus platforms. No security-relevant semantic change is visible in the diff.
Changed components
BitBox02 firmware bootloader (BLE-enabled variant)BitBox02 firmware main loopDA14531 Bluetooth protocol driverRust hww/noise and hww/api/bluetooth modulesCMake and Rust build configurationInspect captured patch +112 / −81
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8eade8e..ab2af1c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -124,7 +124,6 @@ set(QTOUCH-SOURCES ${QTOUCH-SOURCES} PARENT_SCOPE)
# The additional files required for the plus platform
set(PLATFORM-BITBOX02-PLUS-SOURCES
- ${CMAKE_SOURCE_DIR}/src/communication_mode.c
${CMAKE_SOURCE_DIR}/src/da14531/crc.c
${CMAKE_SOURCE_DIR}/src/da14531/da14531.c
${CMAKE_SOURCE_DIR}/src/da14531/da14531_protocol.c
diff --git a/src/bootloader/bootloader.c b/src/bootloader/bootloader.c
index ff4cc40..23b18ba 100644
--- a/src/bootloader/bootloader.c
+++ b/src/bootloader/bootloader.c
@@ -11,6 +11,7 @@
#include <memory/memory_shared.h>
#include <memory/nvmctrl.h>
#include <pukcc/curve_p256.h>
+#include <rust/rust.h>
#include <screen.h>
#include <stdint.h>
#include <string.h>
@@ -30,7 +31,6 @@
#endif
#if PLATFORM_BITBOX02PLUS == 1
- #include <communication_mode.h>
#include <da14531/da14531.h>
#include <da14531/da14531_protocol.h>
#include <uart.h>
@@ -328,7 +328,7 @@ void bootloader_render_default_screen(void)
_load_logo();
#if PLATFORM_BITBOX02PLUS == 1
UG_PutString(0, SCREEN_HEIGHT - 9 * 2 - 5, "See the BitBoxApp", false);
- if (communication_mode_ble_enabled() &&
+ if (rust_communication_mode_ble_enabled() &&
da14531_connected_state < DA14531_CONNECTED_CONNECTED_SECURED) {
char buf[MEMORY_DEVICE_NAME_MAX_LEN] = {0};
memory_random_name(buf);
diff --git a/src/bootloader/startup.c b/src/bootloader/startup.c
index c9673fb..fb39534 100644
--- a/src/bootloader/startup.c
+++ b/src/bootloader/startup.c
@@ -19,7 +19,6 @@
#endif
#if PLATFORM_BITBOX02PLUS == 1
- #include <communication_mode.h>
#include <da14531/da14531.h>
#include <da14531/da14531_handler.h>
#include <da14531/da14531_protocol.h>
@@ -87,7 +86,7 @@ int main(void)
ringbuffer_init(&uart_write_queue, &uart_write_buf, UART_OUT_BUF_LEN);
if (!memory_ble_enabled()) {
- communication_mode_ble_disable();
+ rust_communication_mode_ble_disable();
}
// Set product to bootloader string, this is necessary if we have rebooted from firmware. Must
@@ -114,7 +113,7 @@ int main(void)
while (1) {
// Do UART I/O
#if PLATFORM_BITBOX02PLUS == 1
- if (communication_mode_ble_enabled()) {
+ if (rust_communication_mode_ble_enabled()) {
if (uart_read_buf_len < sizeof(uart_read_buf) ||
ringbuffer_num(&uart_write_queue) > 0) {
// screen_sprintf_debug(1000, "uart poll");
@@ -132,20 +131,20 @@ int main(void)
if (!hww_data && hid_hww_read(&hww_frame[0])) {
usb_packet_process((const USB_FRAME*)hww_frame);
#if PLATFORM_BITBOX02PLUS == 1
- if (communication_mode_ble_enabled()) {
+ if (rust_communication_mode_ble_enabled()) {
// Enqueue a power down command to the da14531
da14531_power_down(&uart_write_queue);
// Flush out the power down command. This will be the last UART communication we do.
while (ringbuffer_num(&uart_write_queue) > 0) {
uart_poll(NULL, 0, NULL, &uart_write_queue);
}
- communication_mode_ble_disable();
+ rust_communication_mode_ble_disable();
bootloader_render_default_screen();
}
#endif
}
#if PLATFORM_BITBOX02PLUS == 1
- if (communication_mode_ble_enabled()) {
+ if (rust_communication_mode_ble_enabled()) {
struct da14531_protocol_frame* frame = da14531_protocol_poll(
&uart_read_buf[0], &uart_read_buf_len, &hww_data, &uart_write_queue);
@@ -157,7 +156,7 @@ int main(void)
#endif
#if PLATFORM_BITBOX02PLUS == 1
- if (!communication_mode_ble_enabled()) {
+ if (!rust_communication_mode_ble_enabled()) {
#endif
if (hww_data) {
if (hid_hww_write_poll(hww_data)) {
diff --git a/src/communication_mode.c b/src/communication_mode.c
deleted file mode 100644
index ee7b527..0000000
--- a/src/communication_mode.c
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#include "communication_mode.h"
-
-#include "memory/memory_shared.h"
-
-static bool _usb_hww_request_seen = false;
-
-void communication_mode_ble_disable(void)
-{
- _usb_hww_request_seen = true;
-}
-
-static bool _has_ble(void)
-{
- static bool has_ble;
- static bool has_ble_initialized = false;
- if (!has_ble_initialized) {
- has_ble = memory_get_platform() == MEMORY_PLATFORM_BITBOX02_PLUS;
- has_ble_initialized = true;
- }
- return has_ble;
-}
-
-bool communication_mode_ble_enabled(void)
-{
- return !_usb_hww_request_seen && _has_ble();
-}
diff --git a/src/communication_mode.h b/src/communication_mode.h
deleted file mode 100644
index dc71f69..0000000
--- a/src/communication_mode.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#ifndef _FIRMWARE_COMMUNICATION_MODE_H_
-#define _FIRMWARE_COMMUNICATION_MODE_H_
-
-#include <stdbool.h>
-
-/**
- * Call this when the first USB request is seen. After this, `communication_mode_ble_enabled()` will
- * be false even on Bluetooth enabled devices (USB takes priority).
- */
-void communication_mode_ble_disable(void);
-
-/**
- * Returns true if this device is Bluetooth-enabled and we have not seen a USB request yet, which
- * means we are communicating via Bluetooth.
- */
-bool communication_mode_ble_enabled(void);
-#endif
diff --git a/src/da14531/da14531_protocol.c b/src/da14531/da14531_protocol.c
index c2b2540..1edc920 100644
--- a/src/da14531/da14531_protocol.c
+++ b/src/da14531/da14531_protocol.c
@@ -6,7 +6,7 @@
#include "platform_config.h"
#include "uart.h"
#include "util.h"
-#include <communication_mode.h>
+#include <rust/rust.h>
#include <stdlib.h>
#include <utils_assert.h>
#ifndef TESTING
@@ -119,7 +119,7 @@ static void _firmware_loader_poll(
case FIRMWARE_LOADER_STATE_IDLE:
if (ble_fw == NULL) {
if (!memory_spi_get_active_ble_firmware(&ble_fw, &ble_fw_size, &ble_fw_checksum)) {
- communication_mode_ble_disable();
+ rust_communication_mode_ble_disable();
util_log("da14531: no valid firmware");
}
*buf_in_len = 0;
diff --git a/src/firmware_main_loop.c b/src/firmware_main_loop.c
index 9ca5fde..bc57cfe 100644
--- a/src/firmware_main_loop.c
+++ b/src/firmware_main_loop.c
@@ -2,7 +2,6 @@
#include "firmware_main_loop.h"
-#include "communication_mode.h"
#include "da14531/da14531.h"
#include "da14531/da14531_handler.h"
#include "da14531/da14531_protocol.h"
@@ -84,12 +83,12 @@ void firmware_main_loop(void)
#endif
if (!memory_ble_enabled()) {
- communication_mode_ble_disable();
+ rust_communication_mode_ble_disable();
}
while (1) {
// Do UART I/O
- if (communication_mode_ble_enabled()) {
+ if (rust_communication_mode_ble_enabled()) {
if (uart_read_buf_len < sizeof(uart_read_buf) ||
ringbuffer_num(&uart_write_queue) > 0) {
uart_poll(
@@ -122,7 +121,7 @@ void firmware_main_loop(void)
// Do USB Input
if (!hww_data && hid_hww_read(&hww_frame[0])) {
if (usb_packet_process((const USB_FRAME*)hww_frame)) {
- if (communication_mode_ble_enabled()) {
+ if (rust_communication_mode_ble_enabled()) {
// Enqueue a power down command to the da14531
da14531_power_down(&uart_write_queue);
// Flush out the power down command. This will be the last UART communication we
@@ -130,7 +129,7 @@ void firmware_main_loop(void)
while (ringbuffer_num(&uart_write_queue) > 0) {
uart_poll(NULL, 0, NULL, &uart_write_queue);
}
- communication_mode_ble_disable();
+ rust_communication_mode_ble_disable();
}
} else {
util_log("usb_packet_process: invalid");
@@ -144,7 +143,7 @@ void firmware_main_loop(void)
#endif
// Do UART Output
- if (communication_mode_ble_enabled()) {
+ if (rust_communication_mode_ble_enabled()) {
struct da14531_protocol_frame* frame = da14531_protocol_poll(
&uart_read_buf[0], &uart_read_buf_len, &hww_data, &uart_write_queue);
@@ -154,7 +153,7 @@ void firmware_main_loop(void)
}
// Do USB Output
- if (!communication_mode_ble_enabled() && hww_data) {
+ if (!rust_communication_mode_ble_enabled() && hww_data) {
if (hid_hww_write_poll(hww_data)) {
hww_data = NULL;
}
diff --git a/src/rust/bitbox02-rust-c/src/lib.rs b/src/rust/bitbox02-rust-c/src/lib.rs
index 050e9df..a7b643e 100644
--- a/src/rust/bitbox02-rust-c/src/lib.rs
+++ b/src/rust/bitbox02-rust-c/src/lib.rs
@@ -20,6 +20,14 @@ pub mod workflow;
#[cfg(feature = "firmware")]
extern crate bitbox_aes;
+// Expose C interface defined in bitbox02_rust
+// Enable for firmware and for Nova bootloader (BitBox02 bootloader currently does not need it).
+#[cfg(any(
+ feature = "firmware",
+ all(feature = "bootloader", feature = "platform-bitbox02plus")
+))]
+extern crate bitbox02_rust;
+
// Expose C interface defined in util
extern crate util;
diff --git a/src/rust/bitbox02-rust/src/communication_mode.rs b/src/rust/bitbox02-rust/src/communication_mode.rs
new file mode 100644
index 0000000..ea789d0
--- /dev/null
+++ b/src/rust/bitbox02-rust/src/communication_mode.rs
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! The BitBox02 Nova has two communication modes: USB and Bluetooth.
+//! Bluetooth is active until the first USB request is seen, at which point USB takes priority.
+
+use crate::hal::Memory;
+use util::cell::SyncCell;
+
+static USB_HWW_REQUEST_SEEN: SyncCell<bool> = SyncCell::new(false);
+static HAS_BLE: SyncCell<Option<bool>> = SyncCell::new(None);
+
+/// Call this when the first USB request is seen. After this, `ble_enabled()` returns false even on
+/// Bluetooth-enabled devices (USB takes priority).
+pub fn ble_disable() {
+ USB_HWW_REQUEST_SEEN.write(true);
+}
+
+/// Returns true if this device is Bluetooth-enabled and we have not seen a USB request yet, which
+/// means we are communicating via Bluetooth.
+pub fn ble_enabled(hal: &mut impl crate::hal::Hal) -> bool {
+ !USB_HWW_REQUEST_SEEN.read() && has_ble(hal)
+}
+
+fn has_ble(hal: &mut impl crate::hal::Hal) -> bool {
+ if let Some(has_ble) = HAS_BLE.read() {
+ return has_ble;
+ }
+
+ let has_ble = matches!(
+ hal.memory().get_platform(),
+ Ok(bitbox02::memory::Platform::BitBox02Plus),
+ );
+ HAS_BLE.write(Some(has_ble));
+ has_ble
+}
+
+/// C interface.
+#[unsafe(no_mangle)]
+pub extern "C" fn rust_communication_mode_ble_disable() {
+ ble_disable();
+}
+
+/// C interface.
+#[unsafe(no_mangle)]
+pub extern "C" fn rust_communication_mode_ble_enabled() -> bool {
+ ble_enabled(&mut crate::hal::BitBox02Hal::new())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::hal::testing::TestingHal;
+
+ fn reset_for_testing() {
+ USB_HWW_REQUEST_SEEN.write(false);
+ HAS_BLE.write(None);
+ }
+
+ #[test]
+ fn test_ble_disabled_on_non_plus() {
+ reset_for_testing();
+ let mut hal = TestingHal::new();
+ hal.memory
+ .set_platform(bitbox02::memory::Platform::BitBox02);
+
+ assert!(!ble_enabled(&mut hal));
+
+ ble_disable();
+ assert!(!ble_enabled(&mut hal));
+ }
+
+ #[test]
+ fn test_ble_enabled_until_usb_request_seen() {
+ reset_for_testing();
+ let mut hal = TestingHal::new();
+ hal.memory
+ .set_platform(bitbox02::memory::Platform::BitBox02Plus);
+
+ assert!(ble_enabled(&mut hal));
+
+ ble_disable();
+ assert!(!ble_enabled(&mut hal));
+ }
+}
diff --git a/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs b/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs
index b9bf31f..917e08e 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs
@@ -156,7 +156,7 @@ async fn process_upgrade(
if response.is_ok() {
hal.ui().status("Upgrade\nsuccessful", true).await;
bitbox02::reset_ble();
- if bitbox02::communication_mode_ble_enabled() {
+ if crate::communication_mode::ble_enabled(hal) {
// Since the Bluetooth host will not be there anymore to read this response, this task
// will not be cleared by the executor. We do it manually to make space for the next
// task upon reconnection.
diff --git a/src/rust/bitbox02-rust/src/hww/noise.rs b/src/rust/bitbox02-rust/src/hww/noise.rs
index 3b0967a..b0d48a1 100644
--- a/src/rust/bitbox02-rust/src/hww/noise.rs
+++ b/src/rust/bitbox02-rust/src/hww/noise.rs
@@ -94,7 +94,7 @@ pub(crate) async fn process(
memory::check_noise_remote_static_pubkey(&state.remote_static_pubkey()?);
// When communicating over BLE, we don't require noise pairing code
// confirmation, as BLE already requires pairing with a pairing code.
- if bitbox02::communication_mode_ble_enabled() || already_verified {
+ if crate::communication_mode::ble_enabled(hal) || already_verified {
state.set_pairing_verified()?;
usb_out.push(0); // let app know we don't require verification
} else {
diff --git a/src/rust/bitbox02-rust/src/lib.rs b/src/rust/bitbox02-rust/src/lib.rs
index caf4941..872bc3f 100644
--- a/src/rust/bitbox02-rust/src/lib.rs
+++ b/src/rust/bitbox02-rust/src/lib.rs
@@ -20,6 +20,7 @@ pub mod backup;
pub mod bb02_async;
mod bip32;
pub mod bip39;
+pub mod communication_mode;
pub mod hal;
pub mod hash;
pub mod hww;
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index e33a9b2..ba0c776 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -60,7 +60,6 @@ const ALLOWLIST_FNS: &[&str] = &[
"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",
@@ -205,7 +204,6 @@ const RUSTIFIED_ENUMS: &[&str] = &[
// BITBOX02_SOURCES are only used for native builds (simulator). Avoid cross-target specific files.
const BITBOX02_SOURCES: &[&str] = &[
- "src/communication_mode.c",
"src/da14531/crc.c",
"src/da14531/da14531_handler.c",
"src/da14531/da14531_protocol.c",
diff --git a/src/rust/bitbox02-sys/wrapper.h b/src/rust/bitbox02-sys/wrapper.h
index 6cc52c3..98e2ca2 100644
--- a/src/rust/bitbox02-sys/wrapper.h
+++ b/src/rust/bitbox02-sys/wrapper.h
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
-#include <communication_mode.h>
#include <delay.h>
#include <memory/bitbox02_smarteeprom.h>
#include <memory/memory.h>
diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs
index b21e134..01cfdb3 100644
--- a/src/rust/bitbox02/src/lib.rs
+++ b/src/rust/bitbox02/src/lib.rs
@@ -251,16 +251,6 @@ pub fn println_stdout(msg: &str) {
}
}
-#[cfg(not(feature = "testing"))]
-pub fn communication_mode_ble_enabled() -> bool {
- unsafe { bitbox02_sys::communication_mode_ble_enabled() }
-}
-
-#[cfg(feature = "testing")]
-pub fn communication_mode_ble_enabled() -> bool {
- false
-}
-
#[cfg(test)]
mod tests {
use super::*;
Why this scored 17/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.