@@ -40,6 +40,10 @@ const ENCRYPTION_OVERHEAD: usize = 64;
4040// to roughly seven seconds so we don't assume communication was lost mid-unlock.
4141const LONG_TIMEOUT : i16 = -70 ;
4242
43+ // Must match MAX_UNLOCK_ATTEMPTS in bitbox02_smarteeprom.h and
44+ // SMALL_MONOTONIC_COUNTER_MAX_USE in optiga.c.
45+ const MAX_UNLOCK_ATTEMPTS : u8 = 10 ;
46+
4347#[ derive( Debug ) ]
4448pub enum Error {
4549 InvalidState ,
@@ -337,7 +341,7 @@ pub async fn unlock(
337341 if !hal. memory ( ) . is_seeded ( ) {
338342 return Err ( Error :: Unseeded ) ;
339343 }
340- if get_remaining_unlock_attempts ( ) == 0 {
344+ if get_remaining_unlock_attempts ( hal ) == 0 {
341345 //
342346 // We reset the device as soon as the MAX_UNLOCK_ATTEMPTSth attempt
343347 // is made. So we should never enter this branch...
@@ -347,11 +351,11 @@ pub async fn unlock(
347351 return Err ( Error :: MaxAttemptsExceeded ) ;
348352 }
349353 bitbox02:: usb_processing:: timeout_reset ( LONG_TIMEOUT ) ;
350- bitbox02 :: memory:: smarteeprom_increment_unlock_attempts ( ) ;
354+ hal . memory ( ) . increment_unlock_attempts ( ) ;
351355 let seed = match get_and_decrypt_seed ( hal, password) {
352356 Ok ( seed) => seed,
353357 err @ Err ( _) => {
354- if get_remaining_unlock_attempts ( ) == 0 {
358+ if get_remaining_unlock_attempts ( hal ) == 0 {
355359 crate :: reset:: reset ( hal, false ) . await ;
356360 return Err ( Error :: MaxAttemptsExceeded ) ;
357361 }
@@ -367,15 +371,15 @@ pub async fn unlock(
367371 } else {
368372 retain_seed ( hal, & seed) ?;
369373 }
370- bitbox02 :: memory:: smarteeprom_reset_unlock_attempts ( ) ;
374+ hal . memory ( ) . reset_unlock_attempts ( ) ;
371375 Ok ( seed)
372376}
373377
374378/// Returns the number of remaining unlock attempts (calls to `unlock()`) that are allowed before
375379/// the device resets itself.
376- pub fn get_remaining_unlock_attempts ( ) -> u8 {
377- let failed_attempts: u8 = bitbox02 :: memory:: smarteeprom_get_unlock_attempts ( ) ;
378- bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS . saturating_sub ( failed_attempts)
380+ pub fn get_remaining_unlock_attempts ( hal : & mut impl crate :: hal :: Hal ) -> u8 {
381+ let failed_attempts: u8 = hal . memory ( ) . get_unlock_attempts ( ) ;
382+ MAX_UNLOCK_ATTEMPTS . saturating_sub ( failed_attempts)
379383}
380384
381385/// Unlocks the bip39 seed. The input seed must be the keystore seed (i.e. must match the output
@@ -1173,14 +1177,14 @@ mod tests {
11731177 assert_eq ! ( decrypted. as_slice( ) , seed. as_slice( ) ) ;
11741178
11751179 // First 9 wrong attempts.
1176- for i in 1 ..bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS {
1180+ for i in 1 ..MAX_UNLOCK_ATTEMPTS {
11771181 assert ! ( matches!(
11781182 block_on( unlock( & mut mock_hal, "invalid password" ) ) ,
11791183 Err ( Error :: IncorrectPassword )
11801184 ) ) ;
11811185 assert_eq ! (
1182- get_remaining_unlock_attempts( ) ,
1183- bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS - i
1186+ get_remaining_unlock_attempts( & mut mock_hal ) ,
1187+ MAX_UNLOCK_ATTEMPTS - i
11841188 ) ;
11851189 // Still seeded.
11861190 assert ! ( mock_hal. memory. is_seeded( ) ) ;
@@ -1218,15 +1222,15 @@ mod tests {
12181222 assert ! ( is_locked( ) ) ;
12191223 assert ! ( copy_seed( & mut mock_hal) . is_err( ) ) ;
12201224
1221- for attempt in 1 ..bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS {
1225+ for attempt in 1 ..MAX_UNLOCK_ATTEMPTS {
12221226 assert ! ( matches!(
12231227 block_on( unlock( & mut mock_hal, "invalid password" ) ) ,
12241228 Err ( Error :: IncorrectPassword ) ,
12251229 ) ) ;
12261230
12271231 assert_eq ! (
1228- get_remaining_unlock_attempts( ) ,
1229- bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS - attempt
1232+ get_remaining_unlock_attempts( & mut mock_hal ) ,
1233+ MAX_UNLOCK_ATTEMPTS - attempt
12301234 ) ;
12311235 assert ! ( is_locked( ) ) ;
12321236 assert ! ( copy_seed( & mut mock_hal) . is_err( ) ) ;
@@ -1265,12 +1269,12 @@ mod tests {
12651269 lock ( ) ;
12661270 assert ! ( is_locked( ) ) ;
12671271
1268- bitbox02 :: memory :: set_unlock_attempts_for_testing ( bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS ) ;
1269- assert_eq ! ( get_remaining_unlock_attempts ( ) , 0 ) ;
1270- assert_eq ! (
1271- bitbox02 :: memory :: smarteeprom_get_unlock_attempts ( ) ,
1272- bitbox02 :: memory :: MAX_UNLOCK_ATTEMPTS
1273- ) ;
1272+ mock_hal
1273+ . memory
1274+ . set_unlock_attempts_for_testing ( MAX_UNLOCK_ATTEMPTS ) ;
1275+
1276+ assert_eq ! ( get_remaining_unlock_attempts ( & mut mock_hal ) , 0 ) ;
1277+ assert_eq ! ( mock_hal . memory . get_unlock_attempts ( ) , MAX_UNLOCK_ATTEMPTS ) ;
12741278
12751279 assert ! ( matches!(
12761280 block_on( unlock( & mut mock_hal, "password" ) ) ,
@@ -1303,10 +1307,7 @@ mod tests {
13031307 block_on( unlock( hal, "wrong" ) ) ,
13041308 Err ( Error :: IncorrectPassword )
13051309 ) ) ;
1306- assert_eq ! (
1307- get_remaining_unlock_attempts( ) ,
1308- bitbox02:: memory:: MAX_UNLOCK_ATTEMPTS - 1
1309- ) ;
1310+ assert_eq ! ( get_remaining_unlock_attempts( hal) , MAX_UNLOCK_ATTEMPTS - 1 ) ;
13101311 }
13111312
13121313 wrong_attempt ( & mut mock_hal) ;
@@ -1358,10 +1359,7 @@ mod tests {
13581359 block_on( unlock( hal, "wrong" ) ) ,
13591360 Err ( Error :: IncorrectPassword )
13601361 ) ) ;
1361- assert_eq ! (
1362- get_remaining_unlock_attempts( ) ,
1363- bitbox02:: memory:: MAX_UNLOCK_ATTEMPTS - 1
1364- ) ;
1362+ assert_eq ! ( get_remaining_unlock_attempts( hal) , MAX_UNLOCK_ATTEMPTS - 1 ) ;
13651363 }
13661364
13671365 wrong_attempt ( & mut mock_hal) ;
@@ -2049,7 +2047,7 @@ mod tests {
20492047 block_on( unlock( & mut mock_hal, "bar" ) ) ,
20502048 Err ( Error :: IncorrectPassword )
20512049 ) ) ;
2052- assert_eq ! ( get_remaining_unlock_attempts( ) , 9 ) ;
2050+ assert_eq ! ( get_remaining_unlock_attempts( & mut mock_hal ) , 9 ) ;
20532051
20542052 // Correct password. First time: unlock. After unlock, it becomes a password check.
20552053 for _ in 0 ..3 {
0 commit comments