What changed, and why it matters
This commit strips out Rust's automatic 'Debug' text-formatting code from the hardware wallet firmware to save space. It replaces a few user-facing error messages that previously relied on Debug formatting with small, hand-written formatters so users still see useful text. The change is primarily a size optimization and code-hardening step, not a fix for an active security bug.
Treat as a defensive hardening/size-optimization change. Review that all remaining user-facing `{:?}` usages in firmware code are covered by the new explicit formatters or are no longer compiled in. Verify that the `-Zfmt-debug=none` flag does not suppress any diagnostic output needed for support or debugging workflows, and confirm clippy::use_debug enforcement catches future regressions in non-cross-compiled builds.
Security signals we found
Compiler flag strips Debug formatters from firmware image, reducing attack surface and binary size
User-facing error strings no longer depend on derived Debug implementations, avoiding accidental information leakage
Third-party library Debug formatting paths identified as still present in no-flag builds are now compiled out
Panic message in hex-encoding utility changed from Debug-formatted error to static string
Evidence from the diff
The firmware build now passes -Zfmt-debug=none, an unstable Rust flag that prevents the compiler from emitting Debug trait implementations. Because {:?} then prints nothing, the commit adds explicit format_error helpers for backup::Error and keystore::Error and updates all UI status strings that used {:?} to use these helpers. A panic message in rust_util_uint8_to_hex is also simplified. The commit notes that third-party crates (protobuf, miniscript) still construct detailed Debug strings internally in a normal build, and this flag prevents those formatters and their generic formatting machinery from being linked, saving 6464 bytes.
Changed components
src/CMakeLists.txt (Rust firmware build flags)src/rust/bitbox02-rust/src/backup.rssrc/rust/bitbox02-rust/src/hww/api/backup.rssrc/rust/bitbox02-rust/src/hww/api/change_password.rssrc/rust/bitbox02-rust/src/hww/api/restore.rssrc/rust/bitbox02-rust/src/hww/api/set_password.rssrc/rust/bitbox02-rust/src/keystore.rssrc/rust/bitbox02-rust/src/workflow/unlock.rssrc/rust/util/src/bytes.rsInspect captured patch +59 / −9
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 693e2e0..eeeb30b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -219,7 +219,7 @@ if(CMAKE_CROSSCOMPILING)
set(RUST_TARGET_ARCH_DIR ${RUST_TARGET_ARCH})
set(RUST_TARGET_ARCH_ARG --target ${RUST_TARGET_ARCH})
set(RUST_CARGO_FLAGS ${RUST_CARGO_FLAGS} -Zbuild-std=core,alloc -Zbuild-std-features=optimize_for_size)
- string(JOIN " " RUSTFLAGS "${RUSTFLAGS}" "-Zunstable-options -Cpanic=immediate-abort")
+ string(JOIN " " RUSTFLAGS "${RUSTFLAGS}" "-Zunstable-options -Zfmt-debug=none -Cpanic=immediate-abort")
else()
set(RUST_TARGET_ARCH_DIR .)
endif()
@@ -259,7 +259,9 @@ if(NOT CMAKE_CROSSCOMPILING)
--target-dir ${RUST_BINARY_DIR}/clippy
--release
--tests
- -- # disabled linters:
+ -- # enabled linters:
+ -W clippy::use_debug
+ # disabled linters:
-A clippy::large_enum_variant
-A clippy::identity_op
-A clippy::new_without_default
diff --git a/src/rust/bitbox02-rust/src/backup.rs b/src/rust/bitbox02-rust/src/backup.rs
index 28533e4..c395279 100644
--- a/src/rust/bitbox02-rust/src/backup.rs
+++ b/src/rust/bitbox02-rust/src/backup.rs
@@ -24,6 +24,17 @@ pub enum Error {
Check,
}
+pub fn format_error(error: &Error) -> &'static str {
+ match error {
+ Error::Generic => "Generic",
+ Error::Stale => "Stale",
+ Error::SdList => "SdList",
+ Error::SdRead => "SdRead",
+ Error::SdWrite => "SdWrite",
+ Error::Check => "Check",
+ }
+}
+
#[derive(Default)]
pub struct BackupData(pub Box<pb_backup::BackupData>);
diff --git a/src/rust/bitbox02-rust/src/hww/api/backup.rs b/src/rust/bitbox02-rust/src/hww/api/backup.rs
index 7b2d052..b99ed43 100644
--- a/src/rust/bitbox02-rust/src/hww/api/backup.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/backup.rs
@@ -124,7 +124,10 @@ pub async fn create(
Ok(Response::Success(pb::Success {}))
}
Err(err) => {
- let msg = format!("Backup not created\nPlease contact\nsupport ({:?})", err);
+ let msg = format!(
+ "Backup not created\nPlease contact\nsupport ({})",
+ backup::format_error(&err)
+ );
hal.ui().status(&msg, false).await;
Err(Error::Generic)
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/change_password.rs b/src/rust/bitbox02-rust/src/hww/api/change_password.rs
index 4cb2242..a6c51ea 100644
--- a/src/rust/bitbox02-rust/src/hww/api/change_password.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/change_password.rs
@@ -27,7 +27,9 @@ pub async fn process(hal: &mut impl crate::hal::Hal) -> Result<Response, Error>
// Re-encrypt seed with new password
if let Err(err) = keystore::re_encrypt_seed(hal, &seed, &new_password).await {
- hal.ui().status(&format!("Error\n{:?}", err), false).await;
+ hal.ui()
+ .status(&format!("Error\n{}", keystore::format_error(&err)), false)
+ .await;
return Err(Error::Generic);
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/restore.rs b/src/rust/bitbox02-rust/src/hww/api/restore.rs
index d58ad51..e65cd80 100644
--- a/src/rust/bitbox02-rust/src/hww/api/restore.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/restore.rs
@@ -61,7 +61,13 @@ pub async fn from_file(
if let Err(err) = crate::keystore::encrypt_and_store_seed(hal, seed, &password).await {
drop(unlock_animation);
hal.ui()
- .status(&format!("Could not\nrestore backup\n{:?}", err), false)
+ .status(
+ &format!(
+ "Could not\nrestore backup\n{}",
+ crate::keystore::format_error(&err)
+ ),
+ false,
+ )
.await;
return Err(Error::Generic);
}
@@ -139,7 +145,13 @@ pub async fn from_mnemonic(
if let Err(err) = crate::keystore::encrypt_and_store_seed(hal, &seed, &password).await {
drop(unlock_animation);
hal.ui()
- .status(&format!("Could not\nrestore backup\n{:?}", err), false)
+ .status(
+ &format!(
+ "Could not\nrestore backup\n{}",
+ crate::keystore::format_error(&err)
+ ),
+ false,
+ )
.await;
return Err(Error::Generic);
};
diff --git a/src/rust/bitbox02-rust/src/hww/api/set_password.rs b/src/rust/bitbox02-rust/src/hww/api/set_password.rs
index df73a34..fd215be 100644
--- a/src/rust/bitbox02-rust/src/hww/api/set_password.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/set_password.rs
@@ -27,7 +27,9 @@ pub async fn process(
let unlock_animation = hal.ui().unlock_animation_create();
if let Err(err) = keystore::create_and_store_seed(hal, &password, entropy).await {
drop(unlock_animation);
- hal.ui().status(&format!("Error\n{:?}", err), false).await;
+ hal.ui()
+ .status(&format!("Error\n{}", keystore::format_error(&err)), false)
+ .await;
return Err(Error::Generic);
}
let seed = keystore::copy_seed(hal).await?;
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 6833d14..1da9c65 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -121,6 +121,21 @@ pub enum Error {
Decrypt,
}
+pub fn format_error(error: &Error) -> String {
+ match error {
+ Error::InvalidState => "InvalidState".into(),
+ Error::CannotUnlockBIP39 => "CannotUnlockBIP39".into(),
+ Error::IncorrectPassword => "IncorrectPassword".into(),
+ Error::MaxAttemptsExceeded => "MaxAttemptsExceeded".into(),
+ Error::Unseeded => "Unseeded".into(),
+ Error::Memory => "Memory".into(),
+ Error::SecureChip(code) => format!("SecureChip({})", code),
+ Error::SeedSize => "SeedSize".into(),
+ Error::Salt => "Salt".into(),
+ Error::Decrypt => "Decrypt".into(),
+ }
+}
+
impl core::convert::From<securechip::Error> for Error {
fn from(error: securechip::Error) -> Self {
match error {
diff --git a/src/rust/bitbox02-rust/src/workflow/unlock.rs b/src/rust/bitbox02-rust/src/workflow/unlock.rs
index 638250e..18d4e1b 100644
--- a/src/rust/bitbox02-rust/src/workflow/unlock.rs
+++ b/src/rust/bitbox02-rust/src/workflow/unlock.rs
@@ -123,7 +123,10 @@ pub async fn unlock_keystore(
Err(UnlockError::IncorrectPassword)
}
Err(err) => {
- let msg = format!("keystore unlock failed\n{:?}", err);
+ let msg = format!(
+ "keystore unlock failed\n{}",
+ crate::keystore::format_error(&err)
+ );
hal.ui().status(&msg, false).await;
Err(UnlockError::Generic)
}
diff --git a/src/rust/util/src/bytes.rs b/src/rust/util/src/bytes.rs
index b6f9eac..1dd0d7c 100644
--- a/src/rust/util/src/bytes.rs
+++ b/src/rust/util/src/bytes.rs
@@ -14,7 +14,7 @@ pub extern "C" fn rust_util_uint8_to_hex(buf: Bytes, mut out: BytesMut) {
// https://github.com/rust-lang/rust/issues/83925
match hex::encode_to_slice(bytes, &mut out.as_mut()[..hexlen]) {
Ok(()) => {}
- Err(err) => panic!("{:?}", err),
+ Err(_) => panic!("hex encoding failed"),
}
// Null terminator.
out.as_mut()[hexlen] = 0;
Why this scored 25/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.