What changed, and why it matters
This commit updates the BitBox02 firmware's vendored copy of a Rust library (rust-bip39) used to turn a recovery phrase into a cryptographic seed. The main change is adding an async (cooperative multitasking) version of the seed-derivation function so the device can briefly pause between heavy PBKDF2 rounds instead of blocking the main loop. It also moves the dependency from a personal fork to the BitBoxSwiss organization fork and adjusts a transitive dependency version. There is no direct security fix or vulnerability patch visible in this commit; it is a dependency/vendor refresh and async API addition.
Treat as a routine dependency/vendor update with a new async API. Review the full rust-bip39 fork diff (not included here) to confirm no unintended changes beyond the two stated patches, and verify that the async PBKDF2 path does not introduce timing side channels or mainloop re-entrancy issues in the firmware integration. No immediate security patch action is indicated by this commit alone.
Security signals we found
Dependency source moved from personal fork (benma/rust-bip39) to vendor-controlled fork (BitBoxSwiss/rust-bip39)
Transitive dependency version constraint on bitcoin_hashes added to avoid duplicate crate versions
New async PBKDF2 path added; correctness verified against existing test vectors, but async code introduces new concurrency surface
No explicit security bug, CVE, or vulnerability fix is described in the commit message or diff
Evidence from the diff
The diff updates external/vendor/bip39 to a new fork branch (BitBoxSwiss/rust-bip39@bitbox-20250922) and adds an async pbkdf2_async() implementation plus Mnemonic::to_seed_normalized_async(). The async function yields control via an injected yield_now callback after each PBKDF2 round, intended to keep the firmware mainloop responsive during BIP39 unlock. Cargo.toml and .cargo/config.toml are updated to point to the new fork and to constrain bitcoin_hashes to avoid duplicate versions. The existing synchronous to_seed_normalized() is unchanged. Tests are converted to tokio tests and now also assert that the async path produces identical seeds.
Changed components
external/vendor/bip39 (vendored rust-bip39 crate)src/rust/Cargo.tomlsrc/rust/Cargo.lock.cargo/config.tomlInspect captured patch +86 / −9
diff --git a/.cargo/config.toml b/.cargo/config.toml
index 6913f1b..bd7d967 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -11,9 +11,9 @@ git = "https://github.com/BitBoxSwiss/rust-bip32-ed25519"
tag = "v0.2.1"
replace-with = "vendored-sources"
-[source."git+https://github.com/benma/rust-bip39.git?branch=bb02"]
-git = "https://github.com/benma/rust-bip39.git"
-branch = "bb02"
+[source."git+https://github.com/BitBoxSwiss/rust-bip39.git?branch=bitbox-20250922"]
+git = "https://github.com/BitBoxSwiss/rust-bip39.git"
+branch = "bitbox-20250922"
replace-with = "vendored-sources"
[source.vendored-sources]
diff --git a/external/vendor/bip39/.cargo-checksum.json b/external/vendor/bip39/.cargo-checksum.json
index 6652940..d20c0bd 100644
--- a/external/vendor/bip39/.cargo-checksum.json
+++ b/external/vendor/bip39/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{".editorconfig":"cfca97a9600b0581127f3075083447e3221a823b937e940bb87d9747175a9adb",".github/workflows/rust.yml":"8f69f78c671bd2ec88803bbcefec49055a6a2b24c276d2d0de8aef5c441c8089",".rustfmt.toml":"090f8fdb44474290d98f32e9b69f7d9d9806cb17874aed6b64e3e3ebb258f781","CHANGELOG.md":"30b9addc19f1f923d252ac262eaaed7ee4dc28e39a9784f74b7d39dea80946a4","Cargo-minimal.lock":"f26f1c7bf57f8120047d15fc8c90f2be101ccb3a4c093da79c2089d431aa95d6","Cargo.toml":"a9048e13d747618996fbc221205304b7a38dde0708961e8fe5758b4e7bf001be","LICENSE":"7179683e8000e6bdc9bbc60d85edf0a4ac8e76f951857f54fcb775d5886f1309","README.md":"d7e0e2aae5ba9e3b285107a16b9a719ce3cc8d52449032656e46fc9f455ac180","benches/bench.rs":"84781745a31f1a63c761d5c6bfcaabf8b2f8f570645a46a7ef0dd030958e866f","contrib/test.sh":"7f659d719cd4d9ffe741615dda795dd314aa08f9cffdd3e94821d8ba2f211291","src/internal_macros.rs":"76710d6d4eb360e40931344e8119b5e21d1d40f25bf1965ab95413f528e5373e","src/language/chinese_simplified.rs":"379581a1a5de22d783bcfeb807c0fca1fe1a3a0be804c1a86448285bff42ed24","src/language/chinese_traditional.rs":"60cd3fe7696d2be88ec9569d9fa16cf1381f23a9f97674d54459e284b9dad103","src/language/czech.rs":"80555a97108ba7d86e536d98585ccbede6ddfe0cd91c01e8a05e5cf4eaff453c","src/language/english.rs":"3f4d2e39891d12d8193741c68663d29f568606cec65c864eff7e3231f0d29693","src/language/french.rs":"b51846bb6f7cd8960499a32c10c1767ef87f2dd052d09bccf082189af3dcc36d","src/language/italian.rs":"17c46fefcc8f67884867cd70240f4c1e2813408ef65912cbce70b925d55d7c59","src/language/japanese.rs":"fc3860147c5cee29eab715499e748de7d29626fc8b279a7761f79a08235aab2e","src/language/korean.rs":"e0e982106c18911389e7d8c7eda92155681f7d60bd19b895196316d2ef195e90","src/language/mod.rs":"870151f8c2b72a7b3b7a26566b73126fe7ba217370c6e52a77586916a240f9e1","src/language/portuguese.rs":"3ff2f0fcf0902907097f53c1f84293109a8368e76a0b725e6a7b8eed106f80e9","src/language/spanish.rs":"d4bb09a9eb9d1a54fed4d6d32b110038a998c59dc77f7bb79df5d6738d69bc0e","src/lib.rs":"6a15087ec5f8dc8a016a05256bc23b4ae1209eaba1109f439d2f65fc9ff6a4c3","src/pbkdf2.rs":"f5c255f7c795f8c74e6defda589fdb8907a349ddaf0249418ccd35635a6616d7"},"package":null}
\ No newline at end of file
+{"files":{".editorconfig":"cfca97a9600b0581127f3075083447e3221a823b937e940bb87d9747175a9adb",".github/workflows/rust.yml":"8f69f78c671bd2ec88803bbcefec49055a6a2b24c276d2d0de8aef5c441c8089",".rustfmt.toml":"090f8fdb44474290d98f32e9b69f7d9d9806cb17874aed6b64e3e3ebb258f781","CHANGELOG.md":"30b9addc19f1f923d252ac262eaaed7ee4dc28e39a9784f74b7d39dea80946a4","Cargo-minimal.lock":"f26f1c7bf57f8120047d15fc8c90f2be101ccb3a4c093da79c2089d431aa95d6","Cargo.toml":"8f90f307f5464fe32c615b98162bc94e1dcd07e80848f6ffeebf4bf2a56a6bdd","LICENSE":"7179683e8000e6bdc9bbc60d85edf0a4ac8e76f951857f54fcb775d5886f1309","README.md":"d7e0e2aae5ba9e3b285107a16b9a719ce3cc8d52449032656e46fc9f455ac180","benches/bench.rs":"84781745a31f1a63c761d5c6bfcaabf8b2f8f570645a46a7ef0dd030958e866f","contrib/test.sh":"7f659d719cd4d9ffe741615dda795dd314aa08f9cffdd3e94821d8ba2f211291","src/internal_macros.rs":"76710d6d4eb360e40931344e8119b5e21d1d40f25bf1965ab95413f528e5373e","src/language/chinese_simplified.rs":"379581a1a5de22d783bcfeb807c0fca1fe1a3a0be804c1a86448285bff42ed24","src/language/chinese_traditional.rs":"60cd3fe7696d2be88ec9569d9fa16cf1381f23a9f97674d54459e284b9dad103","src/language/czech.rs":"80555a97108ba7d86e536d98585ccbede6ddfe0cd91c01e8a05e5cf4eaff453c","src/language/english.rs":"3f4d2e39891d12d8193741c68663d29f568606cec65c864eff7e3231f0d29693","src/language/french.rs":"b51846bb6f7cd8960499a32c10c1767ef87f2dd052d09bccf082189af3dcc36d","src/language/italian.rs":"17c46fefcc8f67884867cd70240f4c1e2813408ef65912cbce70b925d55d7c59","src/language/japanese.rs":"fc3860147c5cee29eab715499e748de7d29626fc8b279a7761f79a08235aab2e","src/language/korean.rs":"e0e982106c18911389e7d8c7eda92155681f7d60bd19b895196316d2ef195e90","src/language/mod.rs":"870151f8c2b72a7b3b7a26566b73126fe7ba217370c6e52a77586916a240f9e1","src/language/portuguese.rs":"3ff2f0fcf0902907097f53c1f84293109a8368e76a0b725e6a7b8eed106f80e9","src/language/spanish.rs":"d4bb09a9eb9d1a54fed4d6d32b110038a998c59dc77f7bb79df5d6738d69bc0e","src/lib.rs":"e0b61730e7ac073d94cb1d10796c08d93d2c6bccf8e8606b08b2f3993c1d75d4","src/pbkdf2.rs":"30a2553fa0a7b294f63fbe7487c7cce5a9d34bb96c1ee985cdddf693aa79641c"},"package":null}
\ No newline at end of file
diff --git a/external/vendor/bip39/Cargo.toml b/external/vendor/bip39/Cargo.toml
index 38dd43b..7ff1667 100644
--- a/external/vendor/bip39/Cargo.toml
+++ b/external/vendor/bip39/Cargo.toml
@@ -116,3 +116,10 @@ bitcoin_hashes = ">=0.12,<0.15"
[dev-dependencies.bip39]
path = "."
features = ["rand"]
+
+[dev-dependencies.tokio]
+version = "1"
+features = [
+ "macros",
+ "rt",
+]
diff --git a/external/vendor/bip39/src/lib.rs b/external/vendor/bip39/src/lib.rs
index bd9223e..ecfa40c 100644
--- a/external/vendor/bip39/src/lib.rs
+++ b/external/vendor/bip39/src/lib.rs
@@ -557,6 +557,27 @@ impl Mnemonic {
seed
}
+ /// Async version of `to_seed_normalized`.
+ pub async fn to_seed_normalized_async(
+ &self,
+ normalized_passphrase: &str,
+ yield_now: impl AsyncFn(),
+ ) -> [u8; 64] {
+ const PBKDF2_ROUNDS: usize = 2048;
+ const PBKDF2_BYTES: usize = 64;
+
+ let mut seed = [0u8; PBKDF2_BYTES];
+ pbkdf2::pbkdf2_async(
+ self.words(),
+ normalized_passphrase.as_bytes(),
+ PBKDF2_ROUNDS,
+ &mut seed,
+ yield_now,
+ )
+ .await;
+ seed
+ }
+
/// Convert to seed bytes.
#[cfg(feature = "unicode-normalization")]
pub fn to_seed<'a, P: Into<Cow<'a, str>>>(&self, passphrase: P) -> [u8; 64] {
@@ -729,8 +750,8 @@ mod tests {
}
}
- #[test]
- fn test_vectors_english() {
+ #[tokio::test]
+ async fn test_vectors_english() {
// These vectors are tuples of
// (entropy, mnemonic, seed)
let test_vectors = [
@@ -882,6 +903,15 @@ mod tests {
mnemonic_str
);
+ assert_eq!(
+ &seed[..],
+ &mnemonic
+ .to_seed_normalized_async("TREZOR", tokio::task::yield_now)
+ .await[..],
+ "failed vector: {}",
+ mnemonic_str
+ );
+
#[cfg(feature = "unicode-normalization")]
{
assert_eq!(&mnemonic.to_string(), mnemonic_str, "failed vector: {}", mnemonic_str);
diff --git a/external/vendor/bip39/src/pbkdf2.rs b/external/vendor/bip39/src/pbkdf2.rs
index 326ca55..d039721 100644
--- a/external/vendor/bip39/src/pbkdf2.rs
+++ b/external/vendor/bip39/src/pbkdf2.rs
@@ -126,3 +126,41 @@ pub(crate) fn pbkdf2<M>(mnemonic: M, unprefixed_salt: &[u8], c: usize, res: &mut
}
}
}
+
+pub(crate) async fn pbkdf2_async<M>(
+ mnemonic: M,
+ unprefixed_salt: &[u8],
+ c: usize,
+ res: &mut [u8],
+ yield_now: impl AsyncFn(),
+) where
+ M: Iterator<Item = &'static str> + Clone,
+{
+ let prf = create_hmac_engine(mnemonic);
+
+ for (i, chunk) in res.chunks_mut(sha512::Hash::LEN).enumerate() {
+ for v in chunk.iter_mut() {
+ *v = 0;
+ }
+
+ let mut salt = {
+ let mut prfc = prf.clone();
+ prfc.input(SALT_PREFIX.as_bytes());
+ prfc.input(unprefixed_salt);
+ prfc.input(&u32_to_array_be((i + 1) as u32));
+
+ let salt = hmac::Hmac::from_engine(prfc).to_byte_array();
+ xor(chunk, &salt);
+ salt
+ };
+
+ for _ in 1..c {
+ let mut prfc = prf.clone();
+ prfc.input(&salt);
+ salt = hmac::Hmac::from_engine(prfc).to_byte_array();
+
+ xor(chunk, &salt);
+ yield_now().await;
+ }
+ }
+}
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 47cccff..9d87312 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -81,7 +81,7 @@ dependencies = [
[[package]]
name = "bip39"
version = "2.2.0"
-source = "git+https://github.com/benma/rust-bip39.git?branch=bb02#64894af6b7f56bcae9c919866e0c37dc5bf0837b"
+source = "git+https://github.com/BitBoxSwiss/rust-bip39.git?branch=bitbox-20250922#d69f68c837ee7962a26619316fb7a725e2e8d44c"
dependencies = [
"bitcoin_hashes",
"zeroize",
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 506b437..f45290a 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -32,8 +32,10 @@ resolver = "2"
# The secp-recovery feature is currently only needed in tests to make use of `RecoverableSignature`.
# Attempting to enable it conditionally only for tests somehow leads to linking errors (duplicate secp256k1 symbols).
bitcoin = { version = "0.32.7", default-features = false, features = ["secp-recovery"] }
-# Replace with upstream once https://github.com/rust-bitcoin/rust-bip39/pull/76 is merged and released.
-bip39 = { git = "https://github.com/benma/rust-bip39.git", branch = "bb02", default-features = false, features = ["zeroize"] }
+# Forked for:
+# - https://github.com/rust-bitcoin/rust-bip39/pull/76 -> custom commit can be removed once this is merged
+# - async functionality
+bip39 = { git = "https://github.com/BitBoxSwiss/rust-bip39.git", branch = "bitbox-20250922", default-features = false, features = ["zeroize"] }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
num-bigint = { version = "0.4.6", default-features = false }
# force-soft-compact reduces the binary size by ~3kB. In future versions of sha2 this will change to
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.