What changed, and why it matters
This commit simply moves a helper function that looks up a BIP39 word by its index from one Rust module to another. The code itself is unchanged, and there is no indication of a security fix or vulnerability.
No security action required; treat as routine refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates get_bip39_word from bitbox02::keystore to a new bitbox02_rust::bip39 module. Call sites in bitbox02-rust-c/src/bip39.rs and bitbox02-rust/src/workflow/mnemonic.rs are updated to use the new path. The function body, including bounds checking and zeroizing of the returned string, is identical. The commit message explicitly states this is a code-organization refactor because the bitbox02 crate is meant to wrap C code, which this function no longer does.
Changed components
src/rust/bitbox02-rust-c/src/bip39.rssrc/rust/bitbox02-rust/src/bip39.rssrc/rust/bitbox02-rust/src/lib.rssrc/rust/bitbox02-rust/src/workflow/mnemonic.rssrc/rust/bitbox02/src/keystore.rsInspect captured patch +45 / −24
diff --git a/src/rust/bitbox02-rust-c/src/bip39.rs b/src/rust/bitbox02-rust-c/src/bip39.rs
index 6ea9fcd..f6e5db7 100644
--- a/src/rust/bitbox02-rust-c/src/bip39.rs
+++ b/src/rust/bitbox02-rust-c/src/bip39.rs
@@ -34,7 +34,7 @@ pub unsafe extern "C" fn rust_derive_bip39_seed(
#[no_mangle]
pub extern "C" fn rust_get_bip39_word(idx: u16, mut out: crate::util::BytesMut) -> bool {
- let word = match bitbox02::keystore::get_bip39_word(idx) {
+ let word = match bitbox02_rust::bip39::get_word(idx) {
Err(()) => return false,
Ok(w) => w,
};
diff --git a/src/rust/bitbox02-rust/src/bip39.rs b/src/rust/bitbox02-rust/src/bip39.rs
new file mode 100644
index 0000000..848c037
--- /dev/null
+++ b/src/rust/bitbox02-rust/src/bip39.rs
@@ -0,0 +1,40 @@
+// Copyright 2025 Shift Crypto AG
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use alloc::string::{String, ToString};
+
+/// `idx` must be smaller than BIP39_WORDLIST_LEN.
+pub fn get_word(idx: u16) -> Result<zeroize::Zeroizing<String>, ()> {
+ Ok(zeroize::Zeroizing::new(
+ bip39::Language::English
+ .word_list()
+ .get(idx as usize)
+ .ok_or(())?
+ .to_string(),
+ ))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_get_word() {
+ assert!(get_word(2048).is_err());
+
+ assert_eq!(get_word(0).unwrap().as_ref() as &str, "abandon");
+ assert_eq!(get_word(2047).unwrap().as_ref() as &str, "zoo");
+ assert_eq!(get_word(563).unwrap().as_ref() as &str, "edit");
+ }
+}
diff --git a/src/rust/bitbox02-rust/src/lib.rs b/src/rust/bitbox02-rust/src/lib.rs
index a8df3de..f7931b9 100644
--- a/src/rust/bitbox02-rust/src/lib.rs
+++ b/src/rust/bitbox02-rust/src/lib.rs
@@ -31,6 +31,7 @@ pub mod attestation;
pub mod backup;
pub mod bb02_async;
mod bip32;
+pub mod bip39;
pub mod hal;
mod hash;
pub mod hww;
diff --git a/src/rust/bitbox02-rust/src/workflow/mnemonic.rs b/src/rust/bitbox02-rust/src/workflow/mnemonic.rs
index 6d02d25..24e82da 100644
--- a/src/rust/bitbox02-rust/src/workflow/mnemonic.rs
+++ b/src/rust/bitbox02-rust/src/workflow/mnemonic.rs
@@ -60,7 +60,7 @@ fn create_random_unique_words(word: &str, length: u8) -> (u8, Vec<zeroize::Zeroi
if picked_indices.contains(&idx) {
continue;
};
- let random_word = bitbox02::keystore::get_bip39_word(idx).unwrap();
+ let random_word = crate::bip39::get_word(idx).unwrap();
if random_word.as_str() == word {
continue;
}
@@ -172,7 +172,7 @@ fn lastword_choices(entered_words: &[&str]) -> Vec<u16> {
let mnemonic = zeroize::Zeroizing::new(format!(
"{} {}",
entered_words.join(" "),
- bitbox02::keystore::get_bip39_word(i).unwrap().as_str(),
+ crate::bip39::get_word(i).unwrap().as_str(),
));
if let Ok(seed) = bitbox02::keystore::bip39_mnemonic_to_seed(&mnemonic) {
break seed;
@@ -205,7 +205,7 @@ fn lastword_choices(entered_words: &[&str]) -> Vec<u16> {
fn lastword_choices_strings(entered_words: &[&str]) -> Vec<zeroize::Zeroizing<String>> {
lastword_choices(entered_words)
.into_iter()
- .map(|word_idx| bitbox02::keystore::get_bip39_word(word_idx).unwrap())
+ .map(|word_idx| crate::bip39::get_word(word_idx).unwrap())
.collect()
}
diff --git a/src/rust/bitbox02/src/keystore.rs b/src/rust/bitbox02/src/keystore.rs
index 1dcfaa0..b8f4f4b 100644
--- a/src/rust/bitbox02/src/keystore.rs
+++ b/src/rust/bitbox02/src/keystore.rs
@@ -136,17 +136,6 @@ pub fn bip39_mnemonic_from_seed(seed: &[u8]) -> Result<zeroize::Zeroizing<String
Ok(zeroize::Zeroizing::new(mnemonic.to_string()))
}
-/// `idx` must be smaller than BIP39_WORDLIST_LEN.
-pub fn get_bip39_word(idx: u16) -> Result<zeroize::Zeroizing<String>, ()> {
- Ok(zeroize::Zeroizing::new(
- bip39::Language::English
- .word_list()
- .get(idx as usize)
- .ok_or(())?
- .to_string(),
- ))
-}
-
pub struct SignResult {
pub signature: [u8; 64],
pub recid: u8,
@@ -428,15 +417,6 @@ mod tests {
);
}
- #[test]
- fn test_get_bip39_word() {
- assert!(get_bip39_word(2048).is_err());
-
- assert_eq!(get_bip39_word(0).unwrap().as_ref() as &str, "abandon");
- assert_eq!(get_bip39_word(2047).unwrap().as_ref() as &str, "zoo");
- assert_eq!(get_bip39_word(563).unwrap().as_ref() as &str, "edit");
- }
-
#[test]
fn test_get_ed25519_seed() {
// No seed on a locked keystore.
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.