make unlock-animation slower again like it used to be
What changed, and why it matters
This commit deliberately runs the wallet-unlock calculation twice instead of once, purely to keep the on-screen unlock animation from finishing too quickly after a performance improvement. The second run is used as a consistency check: if the two results don't match, the device reports a memory error. The change is framed as a user-experience fix that happens to add a safety double-check, not as a response to a known security bug.
No immediate action required. Treat as a hardening/UX commit. If auditing, verify that the repeated derivation does not introduce observable timing or power side channels, and that Error::Memory is handled correctly upstream without leaking sensitive state.
Security signals we found
Sensitive cryptographic operation (BIP39 seed derivation) is executed twice and results are compared
Mismatch in repeated derivation returns Error::Memory and aborts unlock
Change is described by the vendor as adding a 'security check for free' rather than fixing a known flaw
No bounds, input validation, or cryptographic primitive changes are present
No incident, CVE, or researcher attribution is mentioned in the commit or changelog
Evidence from the diff
The BitBox02 firmware’s BIP39 unlock was made faster by switching from libwally to rust-bip39. To keep the unlock animation duration visually consistent, the code now calls derive_bip39_seed twice and compares the two (bip39_seed, root_fingerprint) outputs, returning Error::Memory on mismatch. The animation slowdown factor is doubled from 2 to 4 and frame bounds are adjusted so the animation spans the longer computation. The commit message explicitly calls this a side-effect security check, not a fix for a reported vulnerability.
Changed components
src/rust/bitbox02/src/keystore.rssrc/ui/components/unlock_animation.cInspect captured patch +16 / −10
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d431cfc..331dfd3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,6 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
### [Unreleased]
- Change title when entering recovery words to `1 of 24`, `2 of 24`, etc.
-- Unlock is now faster after password/passphrase entry (shorter unlock animation)
- Remove option to restore from 18 recovery words
- simulator: enable Test Merchant for payment requests
- simulator: simulate a Nova device
diff --git a/src/rust/bitbox02/src/keystore.rs b/src/rust/bitbox02/src/keystore.rs
index 86477df..a0f5b23 100644
--- a/src/rust/bitbox02/src/keystore.rs
+++ b/src/rust/bitbox02/src/keystore.rs
@@ -152,7 +152,14 @@ pub async fn unlock_bip39(
unlock_bip39_check(seed)?;
let (bip39_seed, root_fingerprint) =
- derive_bip39_seed(secp, seed, mnemonic_passphrase, yield_now).await;
+ derive_bip39_seed(secp, seed, mnemonic_passphrase, &yield_now).await;
+
+ let (bip39_seed_2, root_fingerprint_2) =
+ derive_bip39_seed(secp, seed, mnemonic_passphrase, &yield_now).await;
+
+ if bip39_seed != bip39_seed_2 || root_fingerprint != root_fingerprint_2 {
+ return Err(Error::Memory);
+ }
unlock_bip39_finalize(bip39_seed.as_slice().try_into().unwrap())?;
diff --git a/src/ui/components/unlock_animation.c b/src/ui/components/unlock_animation.c
index 7a0829d..24b7d93 100644
--- a/src/ui/components/unlock_animation.c
+++ b/src/ui/components/unlock_animation.c
@@ -26,13 +26,13 @@
// This many iterations times the slowdown factor to render the whole animation.
#define LOCK_ANIMATION_N_FRAMES (38)
-// Since BIP39 unlock takes 2048 iterations, and the screen frame rate is 30 (SCREEN_FRAME_RATE,
-// render is called only every 30th iteration), if we want both to finish at the same time, the
-// slowdown factor becomes the following:
-// (1.1f * (2048 / ((float)LOCK_ANIMATION_N_FRAMES * (float)SCREEN_FRAME_RATE)))
+// Since BIP39 unlock takes 2048*2 iterations (it is performed twice), and the screen frame rate is
+// 30 (SCREEN_FRAME_RATE, render is called only every 30th iteration), if we want both to finish at
+// the same time, the slowdown factor becomes the following:
+// (1.1f * (2048*2 / ((float)LOCK_ANIMATION_N_FRAMES * (float)SCREEN_FRAME_RATE)))
// 10% is added so the animation takes a bit longer than the actual unlock.
-// The above value is 1.9761, we simply round up to 2.
-#define SLOWDOWN_FACTOR (2)
+// The above value is 3.95, we simply round up to 4.
+#define SLOWDOWN_FACTOR (4)
#define LOCK_ANIMATION_FRAME_WIDTH (28)
#define LOCK_ANIMATION_FRAME_HEIGHT (25)
@@ -137,7 +137,7 @@ static const uint8_t LOCK_ANIMATION[LOCK_ANIMATION_ACTUAL_N_FRAMES][LOCK_ANIMATI
*/
static const uint8_t* _get_frame(int frame_idx)
{
- if (frame_idx >= LOCK_ANIMATION_N_FRAMES) {
+ if (frame_idx >= LOCK_ANIMATION_N_FRAMES + LOCK_ANIMATION_FRAMES_STOP_TIME) {
Abort("Invalid lock animation frame requested.");
}
/* First part of the animation: Closed lock for LOCK_ANIMATION_FRAMES_STOP_TIME frames. */
@@ -166,7 +166,7 @@ static void _render(component_t* component)
data_t* data = (data_t*)component->data;
int frame = data->frame / SLOWDOWN_FACTOR;
- if (frame >= LOCK_ANIMATION_N_FRAMES) {
+ if (frame >= LOCK_ANIMATION_N_FRAMES + LOCK_ANIMATION_FRAMES_STOP_TIME) {
/* End of the animation */
if (data->on_done) {
data->on_done(data->on_done_param);
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.