What changed, and why it matters
This commit is a routine code refactor, not a security fix. It moves the source of random numbers behind a new 'hardware abstraction layer' (HAL) interface so the same code can use a fake predictable random generator during automated tests and the real secure random generator on the actual BitBox02 device. The only production behavior change is that Taproot/Schnorr signing now fetches its auxiliary randomness through this new interface instead of calling the device's random function directly. The real-device implementation still uses the same secure random function as before, so security properties are unchanged.
No security action required. Treat as normal engineering refactor. If reviewing, confirm that `BitBox02Random::random_32_bytes()` remains the sole production source and that `TestingRandom` is gated behind the `testing` feature.
Security signals we found
Refactor only: production randomness still sourced from `bitbox02::random::random_32_bytes()`
Schnorr auxiliary randomness (`aux_rand`) now injected via HAL trait
Test-only deterministic random generator added (`TestingRandom`)
Two Taproot signature test vectors changed because deterministic test randomness differs from previous fake RNG
No bounds checks, memory safety, or cryptographic verification logic modified
Evidence from the diff
The change introduces a Random trait in hal.rs with random_32_bytes(), wires it into the Hal trait, and provides BitBox02Random (delegating to bitbox02::random::random_32_bytes()) and TestingRandom (deterministic counter-based SHA256). keystore::secp256k1_schnorr_sign now accepts &mut impl Random and uses it for the BIP340 aux_rand parameter. Unit tests in signtx.rs and keystore.rs are updated to remove bitbox02::random::fake_reset() and instead use TestingRandom, which changes the deterministic signatures in two Taproot test assertions. No cryptographic algorithm, key handling, or entropy source is altered on the production path.
Changed components
src/rust/bitbox02-rust/src/hal.rssrc/rust/bitbox02-rust/src/keystore.rssrc/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rsInspect captured patch +75 / −8
diff --git a/src/rust/bitbox02-rust/src/hal.rs b/src/rust/bitbox02-rust/src/hal.rs
index 953c8e0b..11deb598 100644
--- a/src/rust/bitbox02-rust/src/hal.rs
+++ b/src/rust/bitbox02-rust/src/hal.rs
@@ -15,6 +15,7 @@
use crate::workflow::RealWorkflows;
pub use crate::workflow::Workflows as Ui;
+use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
@@ -33,10 +34,15 @@ pub trait Sd {
async fn write_bin(&mut self, filename: &str, dir: &str, data: &[u8]) -> Result<(), ()>;
}
+pub trait Random {
+ fn random_32_bytes(&mut self) -> Box<zeroize::Zeroizing<[u8; 32]>>;
+}
+
/// Hardware abstraction layer for BitBox devices.
pub trait Hal {
fn ui(&mut self) -> &mut impl Ui;
fn sd(&mut self) -> &mut impl Sd;
+ fn random(&mut self) -> &mut impl Random;
}
pub struct BitBox02Sd;
@@ -82,9 +88,19 @@ impl Sd for BitBox02Sd {
}
}
+pub struct BitBox02Random;
+
+impl Random for BitBox02Random {
+ #[inline(always)]
+ fn random_32_bytes(&mut self) -> Box<zeroize::Zeroizing<[u8; 32]>> {
+ bitbox02::random::random_32_bytes()
+ }
+}
+
pub struct BitBox02Hal {
ui: RealWorkflows,
sd: BitBox02Sd,
+ random: BitBox02Random,
}
impl BitBox02Hal {
@@ -92,6 +108,7 @@ impl BitBox02Hal {
Self {
ui: crate::workflow::RealWorkflows,
sd: BitBox02Sd,
+ random: BitBox02Random,
}
}
}
@@ -103,14 +120,38 @@ impl Hal for BitBox02Hal {
fn sd(&mut self) -> &mut impl Sd {
&mut self.sd
}
+ fn random(&mut self) -> &mut impl Random {
+ &mut self.random
+ }
}
#[cfg(feature = "testing")]
pub mod testing {
+ use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
+ use bitcoin::hashes::{Hash, sha256};
+
+ pub struct TestingRandom {
+ counter: u32,
+ }
+
+ impl TestingRandom {
+ pub fn new() -> Self {
+ Self { counter: 0 }
+ }
+ }
+
+ impl super::Random for TestingRandom {
+ fn random_32_bytes(&mut self) -> Box<zeroize::Zeroizing<[u8; 32]>> {
+ self.counter += 1;
+ let hash = sha256::Hash::hash(&self.counter.to_be_bytes());
+ Box::new(zeroize::Zeroizing::new(hash.to_byte_array()))
+ }
+ }
+
pub struct TestingSd {
pub inserted: Option<bool>,
files: BTreeMap<String, BTreeMap<String, Vec<u8>>>,
@@ -172,6 +213,7 @@ pub mod testing {
pub struct TestingHal<'a> {
pub ui: crate::workflow::testing::TestingWorkflows<'a>,
pub sd: TestingSd,
+ pub random: TestingRandom,
}
impl TestingHal<'_> {
@@ -179,6 +221,7 @@ pub mod testing {
Self {
ui: crate::workflow::testing::TestingWorkflows::new(),
sd: TestingSd::new(),
+ random: TestingRandom::new(),
}
}
}
@@ -190,12 +233,16 @@ pub mod testing {
fn sd(&mut self) -> &mut impl super::Sd {
&mut self.sd
}
+ fn random(&mut self) -> &mut impl super::Random {
+ &mut self.random
+ }
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::hal::Sd;
+ use crate::hal::{Random, Sd};
+ use hex_lit::hex;
use util::bb02_async::block_on;
@@ -230,5 +277,20 @@ pub mod testing {
assert!(block_on(sd.erase_file_in_subdir("file1.txt", "dir1")).is_ok());
assert_eq!(block_on(sd.list_subdir(Some("dir1"))), Ok(vec![]));
}
+
+ #[test]
+ fn test_random() {
+ let mut random = TestingRandom::new();
+ let first = random.random_32_bytes();
+ let second = random.random_32_bytes();
+ assert_eq!(
+ first.as_slice(),
+ &hex!("b40711a88c7039756fb8a73827eabe2c0fe5a0346ca7e0a104adc0fc764f528d"),
+ );
+ assert_eq!(
+ second.as_slice(),
+ &hex!("433ebf5bc03dffa38536673207a21281612cef5faa9bc7a4d5b9be2fdb12cf1a"),
+ );
+ }
}
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
index 9340cb68..d25ac88f 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -1195,6 +1195,7 @@ async fn _process(
next_response.next.has_signature = true;
next_response.next.signature = crate::keystore::secp256k1_schnorr_sign(
+ hal.random(),
&tx_input.keypath,
&sighash,
if let TaprootSpendInfo::KeySpend(tweak_hash) = &spend_info {
@@ -2155,7 +2156,6 @@ mod tests {
}));
mock_unlocked();
- bitbox02::random::fake_reset();
let mut init_request = transaction.borrow().init_request();
init_request.script_configs[0] = pb::BtcScriptConfigWithKeypath {
script_config: Some(pb::BtcScriptConfig {
@@ -2172,7 +2172,7 @@ mod tests {
assert_eq!(
next.signature,
hex!(
- "472ef2aa293d5697649a5364d40567d6eaf508fca9e51321c5a48de42c32b4bbc2d0cee4ab6fea1f3b137a1cbca2abe72aa945c50e95e02fa8ac354fddf2ca10"
+ "74fa05435a838a76ab34105f783d8d69136977b85df4644dec6afc85bba669ddb7c127d7a5a6d3cb406843b6e4366a276872228bb9efa4e3c22cfd07be3198b5"
)
);
}
@@ -3350,7 +3350,6 @@ mod tests {
"sudden tenant fault inject concert weather maid people chunk youth stumble grit",
"",
);
- bitbox02::random::fake_reset();
// For the policy registration below.
mock_memory();
@@ -3388,7 +3387,7 @@ mod tests {
assert_eq!(
next.signature,
hex!(
- "f4b760fa7f1ca8a00149bf439c07dcd3aafe4c98111607cece4b80066f7ef2e4406d18831990def0bf4a5b5647dc426ef1f749524adf0a6896844cd90b796031"
+ "63bb140c52b30f8625219dac0951cad4a6c1c2c5c6a014be40fd46a80ab77207780626f7d568e885f26484bbc3624714a26234a0da5236775cbfae5ed7a6ad8d"
)
);
}
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index c7175885..d3331943 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -406,6 +406,7 @@ pub fn secp256k1_nonce_commit(
/// Sign a message using the private key at the keypath, which is optionally tweaked with the given
/// tweak.
pub fn secp256k1_schnorr_sign(
+ random: &mut impl crate::hal::Random,
keypath: &[u32],
msg: &[u8; 32],
tweak: Option<&[u8; 32]>,
@@ -423,10 +424,11 @@ pub fn secp256k1_schnorr_sign(
.map_err(|_| ())?;
}
+ let aux_rand = random.random_32_bytes();
let sig = SECP256K1.sign_schnorr_with_aux_rand(
&bitcoin::secp256k1::Message::from_digest(*msg),
&keypair,
- &bitbox02::random::random_32_bytes(),
+ &aux_rand,
);
Ok(sig.serialize())
}
@@ -497,6 +499,7 @@ pub mod testing {
mod tests {
use super::*;
+ use crate::hal::{Random, testing::TestingRandom};
use hex_lit::hex;
use bitbox02::testing::mock_memory;
@@ -1285,7 +1288,8 @@ mod tests {
// Test without tweak
bitbox02::securechip::fake_event_counter_reset();
- let sig = secp256k1_schnorr_sign(&keypath, &msg, None).unwrap();
+ let mut random = crate::hal::testing::TestingRandom::new();
+ let sig = secp256k1_schnorr_sign(&mut random, &keypath, &msg, None).unwrap();
assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
assert!(
@@ -1304,7 +1308,9 @@ mod tests {
))
.unwrap();
let (tweaked_pubkey, _) = expected_pubkey.add_tweak(SECP256K1, &tweak).unwrap();
- let sig = secp256k1_schnorr_sign(&keypath, &msg, Some(&tweak.to_be_bytes())).unwrap();
+ let mut random = crate::hal::testing::TestingRandom::new();
+ let sig = secp256k1_schnorr_sign(&mut random, &keypath, &msg, Some(&tweak.to_be_bytes()))
+ .unwrap();
assert!(
SECP256K1
.verify_schnorr(
Why this scored 19/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.