Use HAL random for noise key generation
What changed, and why it matters
This commit is a code cleanup in the BitBox02 firmware's Rust code. It changes how random numbers are supplied when creating cryptographic keys for the Noise protocol, moving from a device-specific random type to a more generic hardware abstraction layer (HAL) random source. The commit message and diff do not describe this as fixing a security bug; it reads as an architectural refactor to simplify dependencies. There is no direct evidence in the commit that the old code was exploitable or that any vulnerability was fixed.
Treat as a normal refactor. Review that the new `genkey()` path is invoked in all production call sites (static key generation and per-session responder init), that the HAL random implementation is still seeded from the secure MCU TRNG, and that the removed `Random32` trait is not used by any out-of-tree code. No urgent security response is indicated by the commit itself.
Security signals we found
Refactor of cryptographic randomness plumbing for Noise/X25519 key generation
Removal of device-specific RNG trait in favor of HAL random abstraction
Responder ephemeral key now generated explicitly before handshake state creation
No commit-level claim of vulnerability fix or security bug
Evidence from the diff
The patch refactors the bitbox02-noise crate so that X25519 key generation and Noise responder initialization take an explicit bitbox_hal::Random source instead of being generic over a Random32 trait implemented by a BitBox02-specific type. State loses its R: Random32 type parameter; init() now accepts a &mut impl Random and generates the responder ephemeral key explicitly via a new genkey() function. The old Random32 trait and BB02Random32 plumbing are removed. The actual randomness still comes from the same MCU source (random_32_bytes_mcu), and the same X25519 clamping is applied. The change is structural: it keeps the dependency boundary at bitbox-hal and makes implicit key generation unsupported (panics in DH::genkey()).
Changed components
src/rust/bitbox02-noise/src/noise_xx.rssrc/rust/bitbox02-noise/src/x25519.rssrc/rust/bitbox02-noise/src/testing.rssrc/rust/bitbox02-noise/src/lib.rssrc/rust/bitbox02-rust/src/hww/noise.rssrc/rust/bitbox02/src/random.rsInspect captured patch +75 / −73
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 96b9681..5f25a2d 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -261,6 +261,7 @@ dependencies = [
name = "bitbox02-noise"
version = "0.1.0"
dependencies = [
+ "bitbox-hal",
"noise-protocol",
"noise-rust-crypto",
"x25519-dalek",
diff --git a/src/rust/bitbox02-noise/Cargo.toml b/src/rust/bitbox02-noise/Cargo.toml
index 13f9b83..506780a 100644
--- a/src/rust/bitbox02-noise/Cargo.toml
+++ b/src/rust/bitbox02-noise/Cargo.toml
@@ -8,6 +8,9 @@ edition = "2024"
description = "BitBox02 noise protocol primitives"
license = "Apache-2.0"
+[dependencies.bitbox-hal]
+path = "../bitbox-hal"
+
[dependencies.noise-protocol]
version = "0.2.0"
default-features = false
diff --git a/src/rust/bitbox02-noise/src/lib.rs b/src/rust/bitbox02-noise/src/lib.rs
index 6ab57e8..7932dec 100644
--- a/src/rust/bitbox02-noise/src/lib.rs
+++ b/src/rust/bitbox02-noise/src/lib.rs
@@ -16,13 +16,6 @@ pub mod testing;
mod x25519;
pub use noise_xx::{Error, HandshakeHash, HandshakeResult, State};
-pub use x25519::{Random32, X25519};
+pub use x25519::{X25519, genkey};
pub use noise_rust_crypto::sensitive::Sensitive;
-
-use noise_protocol::DH;
-
-/// Generate a x25519 private key.
-pub fn generate_static_private_key<R: Random32>() -> Sensitive<x25519::PrivateKey> {
- X25519::<R>::genkey()
-}
diff --git a/src/rust/bitbox02-noise/src/noise_xx.rs b/src/rust/bitbox02-noise/src/noise_xx.rs
index 96e9bb4..ab7f975 100644
--- a/src/rust/bitbox02-noise/src/noise_xx.rs
+++ b/src/rust/bitbox02-noise/src/noise_xx.rs
@@ -3,13 +3,15 @@
extern crate alloc;
use alloc::vec::Vec;
-use crate::x25519::{PrivateKey, PublicKey, Random32, X25519};
+use bitbox_hal::Random;
+
+use crate::x25519::{PrivateKey, PublicKey, X25519, genkey};
use noise_rust_crypto::{ChaCha20Poly1305, Sha256, sensitive::Sensitive};
/// Specialization of noise_protocol::HandshakeState, picking the implementations for Diffie
/// Hellman, Cipher and Hash.
/// cbindgen:ignore
-pub type HandshakeState<R> = noise_protocol::HandshakeState<X25519<R>, ChaCha20Poly1305, Sha256>;
+pub type HandshakeState = noise_protocol::HandshakeState<X25519, ChaCha20Poly1305, Sha256>;
/// Common handshake hash that can be derived by both parties. The pairing code is derived from it.
pub type HandshakeHash = [u8; 32];
@@ -19,11 +21,11 @@ pub type HandshakeHash = [u8; 32];
/// The required state flow is:
///
/// `Nothing --init()--> Initialized --handshake()--> Initialized --handshake() --> Ready.`
-pub enum State<R: Random32> {
+pub enum State {
/// Noise not in use yet.
Nothing,
/// Initialized, ready for handhshake messages.
- Initialized(HandshakeState<R>),
+ Initialized(HandshakeState),
/// Handshake is completed. Ready to confirm the pairing and process messages.
Ready {
/// Defaults to true. No encryption/decryption is possible until `set_pairing_verified()` is
@@ -70,7 +72,7 @@ impl core::convert::From<noise_protocol::Error> for Error {
}
}
-impl<R: Random32> State<R> {
+impl State {
/// Can be called at any time to reset the state.
pub fn reset(&mut self) {
*self = State::Nothing;
@@ -79,14 +81,16 @@ impl<R: Random32> State<R> {
/// Can be called at any time to start waiting for a new communication channel.
///
/// `static_private_key` is the local static key. It can be generated using
- /// `generate_static_private_key()`.
- pub fn init(&mut self, static_private_key: Sensitive<PrivateKey>) {
+ /// `genkey()` with a HAL random source and then persisted. `random` is used to generate a
+ /// fresh ephemeral private key for each session.
+ pub fn init(&mut self, static_private_key: Sensitive<PrivateKey>, random: &mut impl Random) {
+ let ephemeral_private_key = genkey(random);
let hs = HandshakeState::new(
noise_protocol::patterns::noise_xx(),
false, /* is_initiator = false; the app is the initiator */
&b"Noise_XX_25519_ChaChaPoly_SHA256"[..],
Some(static_private_key),
- None,
+ Some(ephemeral_private_key),
None,
None,
);
@@ -221,6 +225,7 @@ impl<R: Random32> State<R> {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::testing::{MockRandom, make_mock_host};
impl HandshakeResult {
fn response(self) -> Result<Vec<u8>, ()> {
@@ -237,21 +242,14 @@ mod tests {
}
}
- enum MockRandom32 {}
- impl Random32 for MockRandom32 {
- fn mcu_32_bytes(out: &mut [u8; 32]) {
- out.copy_from_slice(b"llllllllllllllllllllllllllllllll")
- }
- }
-
#[test]
pub fn test_full() {
- use noise_protocol::DH;
- let bb02_static_key = X25519::<MockRandom32>::genkey();
+ let mut bb02_random = MockRandom;
+ let bb02_static_key = genkey(&mut bb02_random);
- let mut host = crate::testing::make_host();
- let mut bb02 = State::<MockRandom32>::Nothing;
- bb02.init(bb02_static_key);
+ let mut host = make_mock_host();
+ let mut bb02 = State::Nothing;
+ bb02.init(bb02_static_key, &mut bb02_random);
let host_handshake_1 = host.write_message_vec(b"").unwrap();
let bb02_handshake_1 = bb02
diff --git a/src/rust/bitbox02-noise/src/testing.rs b/src/rust/bitbox02-noise/src/testing.rs
index f1b2eaa..9c8f4d2 100644
--- a/src/rust/bitbox02-noise/src/testing.rs
+++ b/src/rust/bitbox02-noise/src/testing.rs
@@ -1,28 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
use crate::noise_xx::HandshakeState;
-use crate::x25519::{Random32, X25519};
+use crate::x25519::genkey;
-use noise_protocol::DH;
+pub struct MockRandom;
-pub enum MockRandom32 {}
-impl Random32 for MockRandom32 {
- fn mcu_32_bytes(out: &mut [u8; 32]) {
+impl bitbox_hal::Random for MockRandom {
+ fn factory_randomness(&mut self) -> &'static [u8; 32] {
+ unreachable!()
+ }
+
+ fn mcu_32_bytes(&mut self, out: &mut [u8; 32]) {
out.copy_from_slice(b"llllllllllllllllllllllllllllllll")
}
}
-pub type TestHandshakeState = HandshakeState<MockRandom32>;
+pub type TestHandshakeState = HandshakeState;
-pub fn make_host() -> TestHandshakeState {
- let host_static_key = X25519::<MockRandom32>::genkey();
+pub fn make_host(random: &mut impl bitbox_hal::Random) -> TestHandshakeState {
+ let host_static_key = genkey(random);
+ let host_ephemeral_key = genkey(random);
TestHandshakeState::new(
noise_protocol::patterns::noise_xx().clone(),
true,
&b"Noise_XX_25519_ChaChaPoly_SHA256"[..],
Some(host_static_key),
- None,
+ Some(host_ephemeral_key),
None,
None,
)
}
+
+pub fn make_mock_host() -> TestHandshakeState {
+ let mut random = MockRandom;
+ make_host(&mut random)
+}
diff --git a/src/rust/bitbox02-noise/src/x25519.rs b/src/rust/bitbox02-noise/src/x25519.rs
index d5c4321..55c5703 100644
--- a/src/rust/bitbox02-noise/src/x25519.rs
+++ b/src/rust/bitbox02-noise/src/x25519.rs
@@ -3,22 +3,36 @@
//! This module implements the X25519 trait needed by noise_protocol
//! by using the x25519_dalek crate. It is adapted from
//! https://github.com/sopium/noise-rust/blob/76fb694f06b429879c264087f496958a99710356/noise-rust-crypto/src/lib.rs#L31,
-//! but uses a pluggable random number generator to generate keys.
+//! but uses the HAL random source to generate keys.
-use core::ops::{Deref, DerefMut};
+use bitbox_hal::Random;
+use core::ops::Deref;
use noise_protocol::U8Array;
use noise_rust_crypto::sensitive::Sensitive;
-pub trait Random32 {
- fn mcu_32_bytes(out: &mut [u8; 32]);
-}
-
-pub struct X25519<R: Random32>(core::marker::PhantomData<R>);
+pub struct X25519;
pub type PrivateKey = [u8; 32];
pub type PublicKey = [u8; 32];
-impl<R: Random32> noise_protocol::DH for X25519<R> {
+/// Generate a fresh x25519 private key by reading 32 random bytes from the HAL and applying
+/// the standard clamping.
+pub fn genkey(random: &mut impl Random) -> Sensitive<PrivateKey> {
+ let mut k: Sensitive<PrivateKey> = Sensitive::new();
+ random.mcu_32_bytes(&mut k);
+
+ // Copied from: https://github.com/sopium/noise-rust/blob/76fb694f06b429879c264087f496958a99710356/noise-rust-crypto/src/lib.rs#L49-L51
+ // which in turn copied it from:
+ // https://github.com/dalek-cryptography/x25519-dalek/blob/ecd6be674850a99ad26404f6aa29b0cf79642b97/src/x25519.rs#L162-L164
+ // which is also in our vendored deps: `vendor/x25519-dalek/src/x25519.rs`.
+ k[0] &= 248;
+ k[31] &= 127;
+ k[31] |= 64;
+
+ k
+}
+
+impl noise_protocol::DH for X25519 {
type Key = Sensitive<PrivateKey>;
type Pubkey = PublicKey;
type Output = [u8; 32];
@@ -28,18 +42,7 @@ impl<R: Random32> noise_protocol::DH for X25519<R> {
}
fn genkey() -> Self::Key {
- let mut k = Self::Key::new();
- R::mcu_32_bytes(k.deref_mut());
-
- // Copied from: https://github.com/sopium/noise-rust/blob/76fb694f06b429879c264087f496958a99710356/noise-rust-crypto/src/lib.rs#L49-L51
- // which in turn copied it from:
- // https://github.com/dalek-cryptography/x25519-dalek/blob/ecd6be674850a99ad26404f6aa29b0cf79642b97/src/x25519.rs#L162-L164
- // which is also in our vendored deps: `vendor/x25519-dalek/src/x25519.rs`.
- k[0] &= 248;
- k[31] &= 127;
- k[31] |= 64;
-
- k
+ panic!("implicit X25519 key generation is unsupported; generate keys explicitly")
}
fn pubkey(k: &Self::Key) -> Self::Pubkey {
diff --git a/src/rust/bitbox02-rust/src/hww.rs b/src/rust/bitbox02-rust/src/hww.rs
index 6f0009b..0e9722c 100644
--- a/src/rust/bitbox02-rust/src/hww.rs
+++ b/src/rust/bitbox02-rust/src/hww.rs
@@ -147,7 +147,7 @@ mod tests {
block_on(process_packet(&mut TestingHal::new(), b"h".to_vec())),
[OP_STATUS_SUCCESS].to_vec()
);
- let mut host_noise = bitbox02_noise::testing::make_host();
+ let mut host_noise = bitbox02_noise::testing::make_mock_host();
let host_handshake_1 = host_noise.write_message_vec(b"").unwrap();
let bb02_handshake_1 = {
let result = block_on(process_packet(&mut TestingHal::new(), {
diff --git a/src/rust/bitbox02-rust/src/hww/noise.rs b/src/rust/bitbox02-rust/src/hww/noise.rs
index e95f597..0111eff 100644
--- a/src/rust/bitbox02-rust/src/hww/noise.rs
+++ b/src/rust/bitbox02-rust/src/hww/noise.rs
@@ -11,7 +11,7 @@ const OP_HER_COMEZ_TEH_HANDSHAEK: u8 = b'H';
pub const OP_NOISE_MSG: u8 = b'n';
/// A safer version of the noise state. RefCell so we cannot accidentally borrow illegally.
-struct SafeNoiseState(RefCell<bitbox02_noise::State<bitbox02::random::BB02Random32>>);
+struct SafeNoiseState(RefCell<bitbox02_noise::State>);
/// Safety: this implements Sync even though it is not thread safe. This is okay, as we run only in
/// a single thread in the BitBox02.
@@ -64,13 +64,13 @@ pub(crate) async fn process(
// Pairing is the start of a session, so we clean the screen stack in case
// we started a new session in the middle of something.
hal.ui().reset();
+ let static_private_key =
+ bitbox02_noise::Sensitive::from(hal.memory().get_noise_static_private_key()?);
NOISE_STATE
.0
.borrow_mut()
- .init(bitbox02_noise::Sensitive::from(
- hal.memory().get_noise_static_private_key()?,
- ));
+ .init(static_private_key, hal.random());
Ok(())
}
Some((&OP_HER_COMEZ_TEH_HANDSHAEK, rest)) => {
diff --git a/src/rust/bitbox02/src/random.rs b/src/rust/bitbox02/src/random.rs
index 76f74ff..ecadb74 100644
--- a/src/rust/bitbox02/src/random.rs
+++ b/src/rust/bitbox02/src/random.rs
@@ -1,14 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
-/// Supplies the randomness source to the noise crate.
-pub enum BB02Random32 {}
-
-impl bitbox02_noise::Random32 for BB02Random32 {
- fn mcu_32_bytes(out: &mut [u8; 32]) {
- mcu_32_bytes(out);
- }
-}
-
#[cfg(not(feature = "testing"))]
pub fn mcu_32_bytes(out: &mut [u8; 32]) {
unsafe { bitbox02_sys::random_32_bytes_mcu(out.as_mut_ptr()) }
@@ -31,7 +22,8 @@ pub fn mcu_32_bytes(out: &mut [u8; 32]) {
pub extern "C" fn rust_noise_generate_static_private_key(
mut private_key_out: util::bytes::BytesMut,
) {
- let key = bitbox02_noise::generate_static_private_key::<BB02Random32>();
+ let mut random = crate::hal::random::BitBox02Random;
+ let key = bitbox02_noise::genkey(&mut random);
private_key_out.as_mut().copy_from_slice(&key[..]);
}
@@ -55,7 +47,8 @@ mod tests {
#[test]
fn test_generate_static_private_key() {
- let key = bitbox02_noise::generate_static_private_key::<BB02Random32>();
+ let mut random = crate::hal::random::BitBox02Random;
+ let key = bitbox02_noise::genkey(&mut random);
assert_eq!(key[0] & 0b111, 0);
assert_eq!(key[31] & 0b1000_0000, 0);
assert_eq!(key[31] & 0b0100_0000, 0b0100_0000);
diff --git a/test/simulator-graphical-bb03/Cargo.lock b/test/simulator-graphical-bb03/Cargo.lock
index d6e2523..e9407f9 100644
--- a/test/simulator-graphical-bb03/Cargo.lock
+++ b/test/simulator-graphical-bb03/Cargo.lock
@@ -464,6 +464,7 @@ dependencies = [
name = "bitbox02-noise"
version = "0.1.0"
dependencies = [
+ "bitbox-hal",
"noise-protocol",
"noise-rust-crypto",
"x25519-dalek",
diff --git a/test/simulator-graphical/Cargo.lock b/test/simulator-graphical/Cargo.lock
index 9a1a49e..60afc2a 100644
--- a/test/simulator-graphical/Cargo.lock
+++ b/test/simulator-graphical/Cargo.lock
@@ -408,6 +408,7 @@ dependencies = [
name = "bitbox02-noise"
version = "0.1.0"
dependencies = [
+ "bitbox-hal",
"noise-protocol",
"noise-rust-crypto",
"x25519-dalek",
Why this scored 27/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.