@@ -319,6 +319,16 @@ pub fn get_ed25519_seed() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
319
319
}
320
320
}
321
321
322
+ // Currently only used in the functional tests below.
323
+ #[ cfg( feature = "testing" ) ]
324
+ pub fn get_u2f_seed ( ) -> Result < zeroize:: Zeroizing < Vec < u8 > > , ( ) > {
325
+ let mut seed = zeroize:: Zeroizing :: new ( [ 0u8 ; 32 ] . to_vec ( ) ) ;
326
+ match unsafe { bitbox02_sys:: keystore_get_u2f_seed ( seed. as_mut_ptr ( ) ) } {
327
+ true => Ok ( seed) ,
328
+ false => Err ( ( ) ) ,
329
+ }
330
+ }
331
+
322
332
pub fn secp256k1_schnorr_sign (
323
333
keypath : & [ u32 ] ,
324
334
msg : & [ u8 ; 32 ] ,
@@ -346,7 +356,8 @@ pub fn secp256k1_schnorr_sign(
346
356
#[ cfg( test) ]
347
357
mod tests {
348
358
use super :: * ;
349
- use crate :: testing:: { mock_unlocked, mock_unlocked_using_mnemonic} ;
359
+ use crate :: testing:: { mock_memory, mock_unlocked, mock_unlocked_using_mnemonic} ;
360
+ use alloc:: string:: ToString ;
350
361
use util:: bip32:: HARDENED ;
351
362
352
363
#[ test]
@@ -479,4 +490,120 @@ mod tests {
479
490
// Invalid seed side
480
491
assert ! ( bip39_mnemonic_from_seed( b"foo" ) . is_err( ) ) ;
481
492
}
493
+
494
+ // Functional test to store seeds, unlock, retrieve seed.
495
+ #[ test]
496
+ fn test_seeds ( ) {
497
+ let seed = hex:: decode ( "cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044" )
498
+ . unwrap ( ) ;
499
+
500
+ for seed_size in [ 16 , 24 , 32 ] {
501
+ mock_memory ( ) ;
502
+ lock ( ) ;
503
+
504
+ // Can repeat until initialized - initialized means backup has been created.
505
+ for _ in 0 ..2 {
506
+ assert ! ( encrypt_and_store_seed( & seed[ ..seed_size] , "foo" ) . is_ok( ) ) ;
507
+ }
508
+
509
+ // Wrong password.
510
+ assert ! ( matches!(
511
+ unlock( "bar" ) ,
512
+ Err ( Error :: IncorrectPassword {
513
+ remaining_attempts: 9
514
+ } )
515
+ ) ) ;
516
+
517
+ // Can't get seed before unlock.
518
+ assert ! ( copy_seed( ) . is_err( ) ) ;
519
+ // Correct password. First time: unlock. After unlock, it becomes a password check.
520
+ for _ in 0 ..3 {
521
+ assert ! ( unlock( "foo" ) . is_ok( ) ) ;
522
+ }
523
+ assert_eq ! ( copy_seed( ) . unwrap( ) . as_slice( ) , & seed[ ..seed_size] ) ;
524
+
525
+ // Can't store new seed once initialized.
526
+ crate :: memory:: set_initialized ( ) . unwrap ( ) ;
527
+ assert ! ( matches!(
528
+ encrypt_and_store_seed( & seed[ ..seed_size] , "foo" ) ,
529
+ Err ( Error :: Memory )
530
+ ) ) ;
531
+ }
532
+ }
533
+
534
+ #[ test]
535
+ fn test_fixtures ( ) {
536
+ struct Test {
537
+ seed_len : usize ,
538
+ mnemonic_passphrase : & ' static str ,
539
+ expected_mnemonic : & ' static str ,
540
+ expected_xpub : & ' static str ,
541
+ expected_u2f_seed_hex : & ' static str ,
542
+ }
543
+ let seed = hex:: decode ( "cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044" )
544
+ . unwrap ( ) ;
545
+
546
+ let tests = [
547
+ Test {
548
+ seed_len : 32 ,
549
+ mnemonic_passphrase : "" ,
550
+ expected_mnemonic : "sleep own lobster state clean thrive tail exist cactus bitter pass soccer clinic riot dream turkey before sport action praise tunnel hood donate man" ,
551
+ expected_xpub : "xpub6Cj6NNCGj2CRPHvkuEG1rbW3nrNCAnLjaoTg1P67FCGoahSsbg9WQ7YaMEEP83QDxt2kZ3hTPAPpGdyEZcfAC1C75HfR66UbjpAb39f4PnG" ,
552
+ expected_u2f_seed_hex : "4f464a6667ad88eebcd0f02982761e474ee0dd16253160320f49d1d6681745e9" ,
553
+ } ,
554
+ Test {
555
+ seed_len : 32 ,
556
+ mnemonic_passphrase : "abc" ,
557
+ expected_mnemonic : "sleep own lobster state clean thrive tail exist cactus bitter pass soccer clinic riot dream turkey before sport action praise tunnel hood donate man" ,
558
+ expected_xpub : "xpub6DXBP3HhFdhUTafatEULxfTXUUxDVuCxfa9RAiBU5r6aRgKiABbeBDyqwWWjmKPP1BZvpvVNMbVR5LeHzhQphtLcPZ8jk3MdLBgc2sACJwR" ,
559
+ expected_u2f_seed_hex : "d599da991ad83baaf449c789e2dff1539dd66983b47a1dec1c00ff3f352cccbc" ,
560
+ } ,
561
+ Test {
562
+ seed_len : 24 ,
563
+ mnemonic_passphrase : "" ,
564
+ expected_mnemonic : "sleep own lobster state clean thrive tail exist cactus bitter pass soccer clinic riot dream turkey before subject" ,
565
+ expected_xpub : "xpub6C7fKxGtTzEVxCC22U2VHx4GpaVy77DzU6KdZ1CLuHgoUGviBMWDc62uoQVxqcRa5RQbMPnffjpwxve18BG81VJhJDXnSpRe5NGKwVpXiAb" ,
566
+ expected_u2f_seed_hex : "fb9dc3fb0a17390776df5c3d8f9261bc5fd5df9f00414cee1393e37e0efda7ef" ,
567
+ } ,
568
+ Test {
569
+ seed_len : 16 ,
570
+ mnemonic_passphrase : "" ,
571
+ expected_mnemonic : "sleep own lobster state clean thrive tail exist cactus bitter pass sniff" ,
572
+ expected_xpub : "xpub6DLvpzjKpJ8k4xYrWYPmZQkUe9dkG1eRig2v6Jz4iYgo8hcpHWx87gGoCGDaB2cHFZ3ExUfe1jDiMu7Ch6gA4ULCBhvwZj29mHCPYSux3YV" ,
573
+ expected_u2f_seed_hex : "20d68b206aff9667b623a460ce61fc94762de67561d6855ca9a6df7b409b2a54" ,
574
+ } ,
575
+ ] ;
576
+
577
+ for test in tests {
578
+ mock_memory ( ) ;
579
+ lock ( ) ;
580
+ let seed = & seed[ ..test. seed_len ] ;
581
+ assert ! ( unlock_bip39( test. mnemonic_passphrase) . is_err( ) ) ;
582
+ assert ! ( encrypt_and_store_seed( seed, "foo" ) . is_ok( ) ) ;
583
+ assert ! ( unlock_bip39( test. mnemonic_passphrase) . is_err( ) ) ;
584
+ assert ! ( is_locked( ) ) ;
585
+ assert ! ( unlock( "foo" ) . is_ok( ) ) ;
586
+ assert ! ( is_locked( ) ) ;
587
+ assert ! ( unlock_bip39( test. mnemonic_passphrase) . is_ok( ) ) ;
588
+ assert ! ( !is_locked( ) ) ;
589
+ assert_eq ! (
590
+ bip39_mnemonic_from_seed( & copy_seed( ) . unwrap( ) )
591
+ . unwrap( )
592
+ . as_str( ) ,
593
+ test. expected_mnemonic,
594
+ ) ;
595
+ let keypath = & [ 44 + HARDENED , 0 + HARDENED , 0 + HARDENED ] ;
596
+ let encoded_xpub = encode_xpub_at_keypath ( keypath) . unwrap ( ) ;
597
+ assert_eq ! (
598
+ bitcoin:: bip32:: Xpub :: decode( & encoded_xpub)
599
+ . unwrap( )
600
+ . to_string( ) ,
601
+ test. expected_xpub,
602
+ ) ;
603
+ assert_eq ! (
604
+ hex:: encode( get_u2f_seed( ) . unwrap( ) ) ,
605
+ test. expected_u2f_seed_hex,
606
+ ) ;
607
+ }
608
+ }
482
609
}
0 commit comments