@@ -356,10 +356,51 @@ pub fn secp256k1_schnorr_sign(
356
356
#[ cfg( test) ]
357
357
mod tests {
358
358
use super :: * ;
359
- use crate :: testing:: { mock_memory, mock_unlocked_using_mnemonic} ;
359
+ use bitcoin:: secp256k1;
360
+
361
+ use crate :: testing:: { mock_memory, mock_unlocked, mock_unlocked_using_mnemonic} ;
360
362
use alloc:: string:: ToString ;
361
363
use util:: bip32:: HARDENED ;
362
364
365
+ #[ test]
366
+ fn test_secp256k1_sign ( ) {
367
+ lock ( ) ;
368
+ let keypath = [ 44 + HARDENED , 0 + HARDENED , 0 + HARDENED , 0 , 5 ] ;
369
+ let msg = [ 0x88u8 ; 32 ] ;
370
+ let host_nonce = [ 0x56u8 ; 32 ] ;
371
+
372
+ // Fails because keystore is locked.
373
+ assert ! ( secp256k1_sign( & keypath, & msg, & host_nonce) . is_err( ) ) ;
374
+
375
+ mock_unlocked ( ) ;
376
+ let sign_result = secp256k1_sign ( & keypath, & msg, & host_nonce) . unwrap ( ) ;
377
+ // Verify signature against expected pubkey.
378
+
379
+ let secp = secp256k1:: Secp256k1 :: new ( ) ;
380
+ let expected_pubkey = {
381
+ let pubkey =
382
+ hex:: decode ( "023ffb4a4e41444d40e4e1e4c6cc329bcba2be50d0ef380aea19d490c373be58fb" )
383
+ . unwrap ( ) ;
384
+ secp256k1:: PublicKey :: from_slice ( & pubkey) . unwrap ( )
385
+ } ;
386
+ let msg = secp256k1:: Message :: from_digest_slice ( & msg) . unwrap ( ) ;
387
+ // Test recid by recovering the public key from the signature and checking against the
388
+ // expected public key.
389
+ let recoverable_sig = secp256k1:: ecdsa:: RecoverableSignature :: from_compact (
390
+ & sign_result. signature ,
391
+ secp256k1:: ecdsa:: RecoveryId :: from_i32 ( sign_result. recid as i32 ) . unwrap ( ) ,
392
+ )
393
+ . unwrap ( ) ;
394
+
395
+ let recovered_pubkey = secp. recover_ecdsa ( & msg, & recoverable_sig) . unwrap ( ) ;
396
+ assert_eq ! ( recovered_pubkey, expected_pubkey) ;
397
+
398
+ // Verify signature.
399
+ assert ! ( secp
400
+ . verify_ecdsa( & msg, & recoverable_sig. to_standard( ) , & expected_pubkey)
401
+ . is_ok( ) ) ;
402
+ }
403
+
363
404
#[ test]
364
405
fn test_bip39_mnemonic_to_seed ( ) {
365
406
assert ! ( bip39_mnemonic_to_seed( "invalid" ) . is_err( ) ) ;
@@ -509,6 +550,123 @@ mod tests {
509
550
assert ! ( bip39_mnemonic_from_seed( b"foo" ) . is_err( ) ) ;
510
551
}
511
552
553
+ #[ test]
554
+ fn test_unlock ( ) {
555
+ mock_memory ( ) ;
556
+ lock ( ) ;
557
+
558
+ assert ! ( matches!( unlock( "password" ) , Err ( Error :: Unseeded ) ) ) ;
559
+
560
+ let seed = hex:: decode ( "cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044" )
561
+ . unwrap ( ) ;
562
+
563
+ let mock_salt_root =
564
+ hex:: decode ( "3333333333333333444444444444444411111111111111112222222222222222" )
565
+ . unwrap ( ) ;
566
+ crate :: memory:: set_salt_root ( mock_salt_root. as_slice ( ) . try_into ( ) . unwrap ( ) ) . unwrap ( ) ;
567
+
568
+ assert ! ( encrypt_and_store_seed( & seed, "password" ) . is_ok( ) ) ;
569
+ // Loop to check that unlocking works while unlocked.
570
+ for _ in 0 ..3 {
571
+ assert ! ( unlock( "password" ) . is_ok( ) ) ;
572
+ }
573
+
574
+ // Also check that the retained seed was encrypted with the expected encryption key.
575
+ let decrypted = {
576
+ let retained_seed_encrypted: & [ u8 ] = unsafe {
577
+ let mut len = 0usize ;
578
+ let ptr = bitbox02_sys:: keystore_test_get_retained_seed_encrypted ( & mut len) ;
579
+ core:: slice:: from_raw_parts ( ptr, len)
580
+ } ;
581
+ let expected_retained_seed_secret =
582
+ hex:: decode ( "b156be416530c6fc00018844161774a3546a53ac6dd4a0462608838e216008f7" )
583
+ . unwrap ( ) ;
584
+ bitbox_aes:: decrypt_with_hmac ( & expected_retained_seed_secret, retained_seed_encrypted)
585
+ . unwrap ( )
586
+ } ;
587
+ assert_eq ! ( decrypted. as_slice( ) , seed. as_slice( ) ) ;
588
+
589
+ // First 9 wrong attempts.
590
+ for i in 1 ..bitbox02_sys:: MAX_UNLOCK_ATTEMPTS {
591
+ assert ! ( matches!(
592
+ unlock( "invalid password" ) ,
593
+ Err ( Error :: IncorrectPassword { remaining_attempts } ) if remaining_attempts
594
+ == ( bitbox02_sys:: MAX_UNLOCK_ATTEMPTS - i) as u8
595
+ ) ) ;
596
+ // Still seeded.
597
+ assert ! ( crate :: memory:: is_seeded( ) ) ;
598
+ // Wrong password does not lock the keystore again if already unlocked.
599
+ assert ! ( copy_seed( ) . is_ok( ) ) ;
600
+ }
601
+ // Last attempt, triggers reset.
602
+ assert ! ( matches!(
603
+ unlock( "invalid password" ) ,
604
+ Err ( Error :: MaxAttemptsExceeded ) ,
605
+ ) ) ;
606
+ // Last wrong attempt locks & resets. There is no more seed.
607
+ assert ! ( !crate :: memory:: is_seeded( ) ) ;
608
+ assert ! ( copy_seed( ) . is_err( ) ) ;
609
+ assert ! ( matches!( unlock( "password" ) , Err ( Error :: Unseeded ) ) ) ;
610
+ }
611
+
612
+ #[ test]
613
+ fn test_unlock_bip39 ( ) {
614
+ mock_memory ( ) ;
615
+ lock ( ) ;
616
+
617
+ let seed = hex:: decode ( "1111111111111111222222222222222233333333333333334444444444444444" )
618
+ . unwrap ( ) ;
619
+
620
+ let mock_salt_root =
621
+ hex:: decode ( "3333333333333333444444444444444411111111111111112222222222222222" )
622
+ . unwrap ( ) ;
623
+ crate :: memory:: set_salt_root ( mock_salt_root. as_slice ( ) . try_into ( ) . unwrap ( ) ) . unwrap ( ) ;
624
+
625
+ assert ! ( encrypt_and_store_seed( & seed, "password" ) . is_ok( ) ) ;
626
+ assert ! ( unlock( "password" ) . is_ok( ) ) ;
627
+ assert ! ( is_locked( ) ) ; // still locked, it is only unlocked after unlock_bip39.
628
+ assert ! ( unlock_bip39( "foo" ) . is_ok( ) ) ;
629
+
630
+ // Check that the retained bip39 seed was encrypted with the expected encryption key.
631
+ let decrypted = {
632
+ let retained_bip39_seed_encrypted: & [ u8 ] = unsafe {
633
+ let mut len = 0usize ;
634
+ let ptr = bitbox02_sys:: keystore_test_get_retained_bip39_seed_encrypted ( & mut len) ;
635
+ core:: slice:: from_raw_parts ( ptr, len)
636
+ } ;
637
+ let expected_retained_bip39_seed_secret =
638
+ hex:: decode ( "856d9a8c1ea42a69ae76324244ace674397ff1360a4ba4c85ffbd42cee8a7f29" )
639
+ . unwrap ( ) ;
640
+ bitbox_aes:: decrypt_with_hmac (
641
+ & expected_retained_bip39_seed_secret,
642
+ retained_bip39_seed_encrypted,
643
+ )
644
+ . unwrap ( )
645
+ } ;
646
+ let expected_bip39_seed = hex:: decode ( "2b3c63de86f0f2b13cc6a36c1ba2314fbc1b40c77ab9cb64e96ba4d5c62fc204748ca6626a9f035e7d431bce8c9210ec0bdffc2e7db873dee56c8ac2153eee9a" ) . unwrap ( ) ;
647
+ assert_eq ! ( decrypted. as_slice( ) , expected_bip39_seed. as_slice( ) ) ;
648
+ }
649
+
650
+ // This tests that you can create a keystore, unlock it, and then do this again. This is an
651
+ // expected workflow for when the wallet setup process is restarted after seeding and unlocking,
652
+ // but before creating a backup, in which case a new seed is created.
653
+ #[ test]
654
+ fn test_create_and_unlock_twice ( ) {
655
+ mock_memory ( ) ;
656
+ lock ( ) ;
657
+
658
+ let seed = hex:: decode ( "cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044" )
659
+ . unwrap ( ) ;
660
+ let seed2 = hex:: decode ( "c28135734876aff9ccf4f1d60df8d19a0a38fd02085883f65fc608eb769a635d" )
661
+ . unwrap ( ) ;
662
+ assert ! ( encrypt_and_store_seed( & seed, "password" ) . is_ok( ) ) ;
663
+ assert ! ( unlock( "password" ) . is_ok( ) ) ;
664
+ // Create new (different) seed.
665
+ assert ! ( encrypt_and_store_seed( & seed2, "password" ) . is_ok( ) ) ;
666
+ assert ! ( unlock( "password" ) . is_ok( ) ) ;
667
+ assert_eq ! ( copy_seed( ) . unwrap( ) . as_slice( ) , & seed2) ;
668
+ }
669
+
512
670
// Functional test to store seeds, unlock, retrieve seed.
513
671
#[ test]
514
672
fn test_seeds ( ) {
0 commit comments