What changed, and why it matters
This commit adds a brand-new, minimal firmware binary for the BitBox03 hardware wallet. It is essentially a development stub: it initializes the board, sets up a debug logger, prints a log message, and then halts. There is no user-facing functionality, no cryptographic code, and no security-sensitive logic introduced in this change. It is a build-system and scaffolding addition, not a security fix or vulnerability.
No security action required. Treat as routine feature/scaffolding commit. If reviewing for supply-chain assurance, verify that the `image_header.json` magic value and linker-script layout match the bootloader's expected firmware image format, but that is a design-consistency check rather than a vulnerability.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces src/rust/bins/bitbox03-firmware/, a new Rust crate producing a bitbox03-firmware ELF for the STM32U5A9J-DK board. It wires the crate into the workspace, CI matrix, Makefile, and Cargo aliases. The firmware currently only calls board::init(), initializes an RTT logger, logs “BitBox03 firmware stub”, and halts in a panic-style loop. A linker script reserves a 1024-byte image header at the start of flash, and build.rs invokes a Python script to render that header from a JSON manifest. No existing code is modified; only new scaffolding is added.
Changed components
BitBox03 firmware build systemCI workflow `.github/workflows/ci-common.yml`Cargo workspace `src/rust/Cargo.toml`New crate `src/rust/bins/bitbox03-firmware`Inspect captured patch +204 / −0
### .github/workflows/ci-common.yml
@@ -212,6 +212,7 @@ jobs:
- firmware-debug
- bitbox03-boot0
- bitbox03-boot1
+ - bitbox03-firmware
- simulator
- simulator-graphical
- simulator-graphical-bb03
### Makefile
@@ -279,6 +279,16 @@ bitbox03-boot1-release:
python3 scripts/bitbox03_image_header.py finalize-elf src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot1
arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot1
arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot1
+bitbox03-firmware:
+ (cd src/rust; cargo bitbox03-firmware-stm32u5a9j-dk)
+ python3 scripts/bitbox03_image_header.py finalize-elf src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-firmware
+ arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-firmware
+ arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-firmware
+bitbox03-firmware-release:
+ (cd src/rust; cargo bitbox03-firmware-stm32u5a9j-dk-release)
+ python3 scripts/bitbox03_image_header.py finalize-elf src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-firmware
+ arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-firmware
+ arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-firmware
flash-bitbox03-boot0-openocd:
./scripts/flash-bitbox03-boot0-openocd.sh
### src/rust/.cargo/config.toml
@@ -26,3 +26,5 @@ bitbox03-boot0-stm32u5a9j-dk = "build -p bitbox03-boot0 --target=thumbv8m.main-n
bitbox03-boot0-stm32u5a9j-dk-release = "build -p bitbox03-boot0 --target=thumbv8m.main-none-eabihf --release --features board-stm32u5a9j-dk"
bitbox03-boot1-stm32u5a9j-dk = "build -p bitbox03-boot1 --target=thumbv8m.main-none-eabihf --features board-stm32u5a9j-dk,rtt"
bitbox03-boot1-stm32u5a9j-dk-release = "build -p bitbox03-boot1 --target=thumbv8m.main-none-eabihf --release --features board-stm32u5a9j-dk"
+bitbox03-firmware-stm32u5a9j-dk = "build -p bitbox03-firmware --target=thumbv8m.main-none-eabihf --features board-stm32u5a9j-dk,rtt"
+bitbox03-firmware-stm32u5a9j-dk-release = "build -p bitbox03-firmware --target=thumbv8m.main-none-eabihf --release --features board-stm32u5a9j-dk"
### src/rust/Cargo.lock
@@ -484,6 +484,21 @@ dependencies = [
"log",
]
+[[package]]
+name = "bitbox03-firmware"
+version = "0.1.0"
+dependencies = [
+ "bitbox-board-stm32u5a9j-dk",
+ "bitbox-board-stm32u5a9j-dk-build",
+ "bitbox-debug",
+ "bitbox-mcu-stm32u5",
+ "bitbox-platform-stm32u5",
+ "cortex-m",
+ "cortex-m-rt",
+ "log",
+ "rtt-target",
+]
+
[[package]]
name = "bitcoin"
version = "0.32.7"
### src/rust/Cargo.toml
@@ -6,6 +6,7 @@ members = [
"async_test",
"bins/bitbox03-boot0",
"bins/bitbox03-boot1",
+ "bins/bitbox03-firmware",
"bitbox-aes",
"bitbox-board-stm32u5a9j-dk",
"bitbox-board-stm32u5a9j-dk-build",
### src/rust/bins/bitbox03-firmware/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "bitbox03-firmware"
+version = "0.1.0"
+edition = "2024"
+
+[[bin]]
+name = "bitbox03-firmware"
+path = "src/main.rs"
+test = false
+bench = false
+
+[features]
+board-stm32u5a9j-dk = [
+ "dep:bitbox-board-stm32u5a9j-dk",
+ "dep:bitbox-board-stm32u5a9j-dk-build",
+]
+rtt = ["bitbox-debug/rtt", "dep:rtt-target"]
+
+[dependencies]
+bitbox-board-stm32u5a9j-dk = { path = "../../bitbox-board-stm32u5a9j-dk", optional = true }
+bitbox-mcu-stm32u5 = { path = "../../bitbox-mcu-stm32u5" }
+bitbox-platform-stm32u5 = { path = "../../bitbox-platform-stm32u5" }
+bitbox-debug = { path = "../../bitbox-debug" }
+cortex-m-rt = "0.7"
+cortex-m = { workspace = true }
+log = { version = "0.4.22", default-features = false }
+rtt-target = { version = "0.6.2", features = ["log"], optional = true }
+
+[build-dependencies]
+bitbox-board-stm32u5a9j-dk-build = { path = "../../bitbox-board-stm32u5a9j-dk-build", optional = true }
### src/rust/bins/bitbox03-firmware/bitbox03-firmware.ld
@@ -0,0 +1,27 @@
+INCLUDE memory.x
+REGION_ALIAS("FLASH", FW_FLASH);
+
+IMAGE_HEADER_LEN = 1024;
+
+SECTIONS
+{
+ .image_header ORIGIN(FLASH) :
+ {
+ . = ALIGN(4);
+ KEEP(*(.image_header .image_header.*));
+ . = ALIGN(4);
+ } > FLASH :flash
+} INSERT BEFORE .vector_table;
+
+PROVIDE(_image_payload_start = ORIGIN(FLASH) + IMAGE_HEADER_LEN);
+
+INCLUDE bitbox03-common.ld
+
+ASSERT(ADDR(.image_header) == ORIGIN(FLASH), "
+ERROR(cortex-m-rt): The image header must start at the beginning of the FLASH memory.");
+
+ASSERT(SIZEOF(.image_header) == IMAGE_HEADER_LEN, "
+ERROR(cortex-m-rt): The image header must exactly fill the reserved header space.");
+
+ASSERT(ADDR(.vector_table) == ORIGIN(FLASH) + IMAGE_HEADER_LEN, "
+ERROR(cortex-m-rt): The vector table must be placed immediately after the image header.");
### src/rust/bins/bitbox03-firmware/build.rs
@@ -0,0 +1,84 @@
+#[cfg(feature = "board-stm32u5a9j-dk")]
+use bitbox_board_stm32u5a9j_dk_build::build_hal_overrides_object;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+fn run_command(command: &mut Command, description: &str) {
+ let status = command.status().unwrap_or_else(|err| {
+ panic!("failed to execute {description}: {err}");
+ });
+ assert!(
+ status.success(),
+ "{description} failed with status {status}"
+ );
+}
+
+fn generate_header_object(manifest_dir: &Path, out_dir: &Path, repo_root: &Path) {
+ let script = repo_root.join("scripts/bitbox03_image_header.py");
+ let header_manifest = manifest_dir.join("image_header.json");
+ let header_bin = out_dir.join("bitbox03-firmware-header.bin");
+ let header_object = out_dir.join("bitbox03-firmware-header.o");
+
+ println!("cargo::rerun-if-changed={}", script.display());
+ println!("cargo::rerun-if-changed={}", header_manifest.display());
+
+ run_command(
+ Command::new("python3")
+ .arg(&script)
+ .arg("render-header")
+ .arg("--manifest")
+ .arg(&header_manifest)
+ .arg("--output")
+ .arg(&header_bin),
+ "render firmware image header",
+ );
+
+ run_command(
+ Command::new("arm-none-eabi-objcopy")
+ .arg("-I")
+ .arg("binary")
+ .arg("-O")
+ .arg("elf32-littlearm")
+ .arg("-B")
+ .arg("arm")
+ .arg("--rename-section")
+ .arg(".data=.image_header,alloc,load,readonly,data,contents")
+ .arg(&header_bin)
+ .arg(&header_object),
+ "convert firmware image header to object",
+ );
+
+ println!("cargo::rustc-link-arg={}", header_object.display());
+}
+
+fn main() {
+ let target = std::env::var("TARGET").expect("TARGET not set");
+ if target.starts_with("thumb") {
+ if !cfg!(feature = "board-stm32u5a9j-dk") {
+ panic!("select a BitBox03 board feature, e.g. `board-stm32u5a9j-dk`")
+ }
+
+ let manifest_dir =
+ PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
+ let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set"));
+ let repo_root = manifest_dir.join("../../../..");
+
+ let lds_from = manifest_dir.join("bitbox03-firmware.ld");
+ let lds_to = out_dir.join("bitbox03-firmware.ld");
+ println!("cargo::rerun-if-changed={}", lds_from.display());
+ std::fs::copy(lds_from, &lds_to).expect("copy linker script");
+ generate_header_object(&manifest_dir, &out_dir, &repo_root);
+
+ // Search paths to linker scripts
+ println!("cargo::rustc-link-search={}", out_dir.display());
+
+ println!(
+ "cargo::rustc-link-arg=-Map={}",
+ out_dir.join("bitbox03-firmware.map").display()
+ );
+ println!("cargo::rustc-link-arg=-Tbitbox03-firmware.ld");
+
+ #[cfg(feature = "board-stm32u5a9j-dk")]
+ build_hal_overrides_object(&repo_root, &out_dir);
+ }
+}
### src/rust/bins/bitbox03-firmware/image_header.json
@@ -0,0 +1,3 @@
+{
+ "magic": "BBFW"
+}
### src/rust/bins/bitbox03-firmware/src/main.rs
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#![no_std]
+#![no_main]
+
+#[cfg(feature = "board-stm32u5a9j-dk")]
+use bitbox_board_stm32u5a9j_dk as board;
+use bitbox_platform_stm32u5 as _;
+use core::panic::PanicInfo;
+use cortex_m_rt::entry;
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ log::error!("{info}");
+ halt()
+}
+
+fn halt() -> ! {
+ cortex_m::asm::bkpt();
+ loop {
+ cortex_m::asm::wfe();
+ }
+}
+
+#[entry]
+fn main() -> ! {
+ board::init();
+ bitbox_debug::rtt_logger_init!();
+ log::info!("BitBox03 firmware stub");
+ halt()
+}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.