Skip to content

Commit 99755d2

Browse files
committed
keystore: move unlock_bip39() to Rust
With the goal of making it async.
1 parent 984fe31 commit 99755d2

File tree

8 files changed

+126
-170
lines changed

8 files changed

+126
-170
lines changed

src/keystore.c

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ static size_t _retained_seed_encrypted_len = 0;
4141
// plaintext.
4242
static uint8_t _retained_seed_hash[32] = {0};
4343

44-
// Change this ONLY via keystore_unlock_bip39().
44+
// Change this ONLY via keystore_unlock_bip39_finalize().
4545
static bool _is_unlocked_bip39 = false;
46-
// Stores a random keyy after bip39-unlock which, after stretching, is used to encrypt the retained
46+
// Stores a random key after bip39-unlock which, after stretching, is used to encrypt the retained
4747
// bip39 seed.
4848
static uint8_t _unstretched_retained_bip39_seed_encryption_key[32] = {0};
4949
// Must be defined if _is_unlocked is true. ONLY ACCESS THIS WITH keystore_copy_bip39_seed().
@@ -514,27 +514,6 @@ bool keystore_unlock_bip39_finalize(const uint8_t* bip39_seed)
514514
return true;
515515
}
516516

517-
bool keystore_unlock_bip39(
518-
const uint8_t* seed,
519-
size_t seed_length,
520-
const char* mnemonic_passphrase,
521-
uint8_t* root_fingerprint_out)
522-
{
523-
if (!keystore_unlock_bip39_check(seed, seed_length)) {
524-
return false;
525-
}
526-
527-
uint8_t bip39_seed[64] = {0};
528-
UTIL_CLEANUP_64(bip39_seed);
529-
rust_derive_bip39_seed(
530-
rust_util_bytes(seed, seed_length),
531-
mnemonic_passphrase,
532-
rust_util_bytes_mut(bip39_seed, sizeof(bip39_seed)),
533-
rust_util_bytes_mut(root_fingerprint_out, 4));
534-
535-
return keystore_unlock_bip39_finalize(bip39_seed);
536-
}
537-
538517
void keystore_lock(void)
539518
{
540519
_is_unlocked_device = false;

src/keystore.h

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,22 +113,6 @@ keystore_unlock(const char* password, uint8_t* remaining_attempts_out, int* secu
113113
*/
114114
USE_RESULT bool keystore_unlock_bip39_check(const uint8_t* seed, size_t seed_length);
115115

116-
/** Unlocks the bip39 seed. The input seed must be the keystore seed (i.e. must match the output
117-
* of `keystore_copy_seed()`).
118-
* @param[in] seed the input seed to BIP39.
119-
* @param[in] seed_length the size of the seed
120-
* @param[in] mnemonic_passphrase bip39 passphrase used in the derivation. Use the
121-
* empty string if no passphrase is needed or provided.
122-
* @param[out] root_fingerprint_out must be 4 bytes long and will contain the root fingerprint of
123-
* the wallet.
124-
* @return returns false if there was a critital memory error, otherwise true.
125-
*/
126-
USE_RESULT bool keystore_unlock_bip39(
127-
const uint8_t* seed,
128-
size_t seed_length,
129-
const char* mnemonic_passphrase,
130-
uint8_t* root_fingerprint_out);
131-
132116
/**
133117
* Retains the given bip39 seed and marks the keystore as unlocked.
134118
* @param[in] bip39_seed 64 byte bip39 seed.

src/rust/bitbox02-rust/src/bip39.rs

Lines changed: 0 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -27,37 +27,6 @@ pub fn get_word(idx: u16) -> Result<zeroize::Zeroizing<String>, ()> {
2727

2828
// C API
2929

30-
/// # Safety
31-
///
32-
/// The passphrase must be not NULL and null-terminated.
33-
///
34-
/// `seed` must be 16, 24 or 32 bytes long.
35-
/// `bip39_seed_out` must be exactly 64 bytes long.
36-
/// `root_fingerprint_out` must be exactly 4 bytes long.
37-
#[unsafe(no_mangle)]
38-
pub unsafe extern "C" fn rust_derive_bip39_seed(
39-
seed: util::bytes::Bytes,
40-
passphrase: *const core::ffi::c_char,
41-
mut bip39_seed_out: util::bytes::BytesMut,
42-
mut root_fingerprint_out: util::bytes::BytesMut,
43-
) {
44-
let mnemonic =
45-
bip39::Mnemonic::from_entropy_in(bip39::Language::English, seed.as_ref()).unwrap();
46-
let passphrase = unsafe { core::ffi::CStr::from_ptr(passphrase) };
47-
let bip39_seed: zeroize::Zeroizing<[u8; 64]> =
48-
zeroize::Zeroizing::new(mnemonic.to_seed_normalized(passphrase.to_str().unwrap()));
49-
bip39_seed_out.as_mut().clone_from_slice(&bip39_seed[..]);
50-
51-
let root_fingerprint: [u8; 4] =
52-
bitcoin::bip32::Xpriv::new_master(bitcoin::NetworkKind::Main, bip39_seed.as_ref())
53-
.unwrap()
54-
.fingerprint(crate::secp256k1::SECP256K1)
55-
.to_bytes();
56-
root_fingerprint_out
57-
.as_mut()
58-
.clone_from_slice(&root_fingerprint);
59-
}
60-
6130
#[unsafe(no_mangle)]
6231
pub extern "C" fn rust_get_bip39_word(idx: u16, mut out: util::bytes::BytesMut) -> bool {
6332
let word = match get_word(idx) {
@@ -78,80 +47,6 @@ pub extern "C" fn rust_get_bip39_word(idx: u16, mut out: util::bytes::BytesMut)
7847
mod tests {
7948
use super::*;
8049

81-
#[test]
82-
fn test_rust_derive_bip39_seed() {
83-
struct Test {
84-
seed: &'static str,
85-
passphrase: &'static core::ffi::CStr,
86-
expected_bip39_seed: &'static str,
87-
expected_root_fingerprint: &'static str,
88-
}
89-
90-
let tests = &[
91-
// 16 byte seed
92-
Test {
93-
seed: "fb5cf00d5ea61059fa066e25a6be9544",
94-
passphrase: c"",
95-
expected_bip39_seed: "f4577e463be595868060e5a763328153155b4167cd284998c8c6096d044742372020f5b052d0c41c1c5e6a6a7da2cb8a367aaaa074fab7773e8d5b2f684257ed",
96-
expected_root_fingerprint: "0b2fa4e5",
97-
},
98-
Test {
99-
seed: "fb5cf00d5ea61059fa066e25a6be9544",
100-
passphrase: c"password",
101-
expected_bip39_seed: "5922fb7630bc7cb871af102f733b6bdb8f05945147cd4646a89056fde0bdad5c3a4ff5be3f9e7af535f570e7053b5b22472555b331bc89cb797c306f7eb6a5a1",
102-
expected_root_fingerprint: "c4062d44",
103-
},
104-
// 24 byte seed
105-
Test {
106-
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
107-
passphrase: c"",
108-
expected_bip39_seed: "4a2a016a6d90eb3a79b7931ca0a172df5c5bfee3e5b47f0fd84bc0791ea3bbc9476c3d5de71cdb12c37e93c2aa3d5c303257f1992aed400fc5bbfc7da787bfa7",
109-
expected_root_fingerprint: "62fd19e0",
110-
},
111-
Test {
112-
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
113-
passphrase: c"password",
114-
expected_bip39_seed: "bc317ee0f88870254be32274d63ec2b0e962bf09f3ca04287912bfc843f2fab7c556f8657cadc924f99a217b0daa91898303a8414102031a125c50023e45a80b",
115-
expected_root_fingerprint: "c745266d",
116-
},
117-
// 32 byte seed
118-
Test {
119-
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
120-
passphrase: c"",
121-
expected_bip39_seed: "63f844e2c61ecfb20f9100de381a7a9ec875b085f5ac7735a2ba4d615a0f4147b87be402f65651969130683deeef752760c09e291604fe4b89d61ffee2630be8",
122-
expected_root_fingerprint: "93ba3a7b",
123-
},
124-
Test {
125-
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
126-
passphrase: c"password",
127-
expected_bip39_seed: "42e90dacd61f3373542d212f0fb9c291dcea84a6d85034272372dde7188638a98527280d65e41599f30d3434d8ee3d4747dbb84801ff1a851d2306c7d1648374",
128-
expected_root_fingerprint: "b95c9318",
129-
},
130-
];
131-
132-
for test in tests {
133-
let seed = hex::decode(test.seed).unwrap();
134-
let mut bip39_seed = [0u8; 64];
135-
let mut root_fingerprint = [0u8; 4];
136-
unsafe {
137-
rust_derive_bip39_seed(
138-
util::bytes::rust_util_bytes(seed.as_ptr(), seed.len()),
139-
test.passphrase.as_ptr(),
140-
util::bytes::rust_util_bytes_mut(bip39_seed.as_mut_ptr(), bip39_seed.len()),
141-
util::bytes::rust_util_bytes_mut(
142-
root_fingerprint.as_mut_ptr(),
143-
root_fingerprint.len(),
144-
),
145-
);
146-
}
147-
assert_eq!(hex::encode(bip39_seed).as_str(), test.expected_bip39_seed);
148-
assert_eq!(
149-
hex::encode(root_fingerprint).as_str(),
150-
test.expected_root_fingerprint
151-
);
152-
}
153-
}
154-
15550
#[test]
15651
fn test_rust_get_bip39_word() {
15752
let mut word = [1u8; 10];

src/rust/bitbox02-rust/src/keystore.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ mod tests {
573573
keystore::lock();
574574
let seed = &seed[..test.seed_len];
575575

576-
assert!(keystore::unlock_bip39(seed, test.mnemonic_passphrase).is_err());
576+
assert!(keystore::unlock_bip39(SECP256K1, seed, test.mnemonic_passphrase).is_err());
577577

578578
bitbox02::securechip::fake_event_counter_reset();
579579
assert!(keystore::encrypt_and_store_seed(seed, "foo").is_ok());
@@ -582,7 +582,7 @@ mod tests {
582582
assert!(keystore::is_locked());
583583

584584
bitbox02::securechip::fake_event_counter_reset();
585-
assert!(keystore::unlock_bip39(seed, test.mnemonic_passphrase).is_ok());
585+
assert!(keystore::unlock_bip39(SECP256K1, seed, test.mnemonic_passphrase).is_ok());
586586
assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
587587

588588
assert!(!keystore::is_locked());

src/rust/bitbox02-rust/src/workflow/unlock.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,9 @@ pub async fn unlock_bip39(hal: &mut impl crate::hal::Hal, seed: &[u8]) {
133133
}
134134
}
135135

136-
let result =
137-
bitbox02::ui::with_lock_animation(|| keystore::unlock_bip39(seed, &mnemonic_passphrase));
136+
let result = bitbox02::ui::with_lock_animation(|| {
137+
keystore::unlock_bip39(crate::secp256k1::SECP256K1, seed, &mnemonic_passphrase)
138+
});
138139
if result.is_err() {
139140
abort("bip39 unlock failed");
140141
}

src/rust/bitbox02-sys/build.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ const ALLOWLIST_FNS: &[&str] = &[
7979
"keystore_secp256k1_nonce_commit",
8080
"keystore_secp256k1_sign",
8181
"keystore_unlock",
82-
"keystore_unlock_bip39",
82+
"keystore_unlock_bip39_check",
83+
"keystore_unlock_bip39_finalize",
8384
"keystore_test_get_retained_seed_encrypted",
8485
"keystore_test_get_retained_bip39_seed_encrypted",
8586
"label_create",

src/rust/bitbox02/src/keystore.rs

Lines changed: 116 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -95,30 +95,61 @@ pub fn lock() {
9595
unsafe { ROOT_FINGERPRINT.write(None) }
9696
}
9797

98-
pub fn unlock_bip39(seed: &[u8], mnemonic_passphrase: &str) -> Result<(), Error> {
99-
let mut root_fingerprint = [0u8; 4];
100-
if unsafe {
101-
bitbox02_sys::keystore_unlock_bip39(
102-
seed.as_ptr(),
103-
seed.len(),
104-
crate::util::str_to_cstr_vec(mnemonic_passphrase)
105-
.unwrap()
106-
.as_ptr()
107-
.cast(),
108-
root_fingerprint.as_mut_ptr(),
109-
)
110-
} {
111-
// Store root fingerprint.
112-
unsafe {
113-
ROOT_FINGERPRINT.write(Some(root_fingerprint));
114-
}
98+
fn unlock_bip39_check(seed: &[u8]) -> Result<(), Error> {
99+
if unsafe { bitbox02_sys::keystore_unlock_bip39_check(seed.as_ptr(), seed.len()) } {
100+
Ok(())
101+
} else {
102+
Err(Error::CannotUnlockBIP39)
103+
}
104+
}
115105

106+
fn unlock_bip39_finalize(bip39_seed: &[u8; 64]) -> Result<(), Error> {
107+
if unsafe { bitbox02_sys::keystore_unlock_bip39_finalize(bip39_seed.as_ptr()) } {
116108
Ok(())
117109
} else {
118110
Err(Error::CannotUnlockBIP39)
119111
}
120112
}
121113

114+
fn derive_bip39_seed(
115+
secp: &Secp256k1<All>,
116+
seed: &[u8],
117+
mnemonic_passphrase: &str,
118+
) -> (zeroize::Zeroizing<[u8; 64]>, [u8; 4]) {
119+
let mnemonic = bip39::Mnemonic::from_entropy_in(bip39::Language::English, seed).unwrap();
120+
let bip39_seed: zeroize::Zeroizing<[u8; 64]> =
121+
zeroize::Zeroizing::new(mnemonic.to_seed_normalized(mnemonic_passphrase));
122+
let root_fingerprint: [u8; 4] =
123+
bitcoin::bip32::Xpriv::new_master(bitcoin::NetworkKind::Main, bip39_seed.as_ref())
124+
.unwrap()
125+
.fingerprint(secp)
126+
.to_bytes();
127+
128+
(bip39_seed, root_fingerprint)
129+
}
130+
131+
/// Unlocks the bip39 seed. The input seed must be the keystore seed (i.e. must match the output
132+
/// of `keystore_copy_seed()`).
133+
/// `mnemonic_passphrase` is the bip39 passphrase used in the derivation. Use the empty string if no
134+
/// passphrase is needed or provided.
135+
pub fn unlock_bip39(
136+
secp: &Secp256k1<All>,
137+
seed: &[u8],
138+
mnemonic_passphrase: &str,
139+
) -> Result<(), Error> {
140+
unlock_bip39_check(seed)?;
141+
142+
let (bip39_seed, root_fingerprint) = derive_bip39_seed(secp, seed, mnemonic_passphrase);
143+
144+
unlock_bip39_finalize(bip39_seed.as_slice().try_into().unwrap())?;
145+
146+
// Store root fingerprint.
147+
unsafe {
148+
ROOT_FINGERPRINT.write(Some(root_fingerprint));
149+
}
150+
Ok(())
151+
}
152+
122153
pub fn root_fingerprint() -> Result<Vec<u8>, ()> {
123154
if is_locked() {
124155
return Err(());
@@ -411,7 +442,7 @@ mod tests {
411442
.unwrap();
412443
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
413444
assert!(is_locked()); // still locked, it is only unlocked after unlock_bip39.
414-
assert!(unlock_bip39(&seed, "foo").is_ok());
445+
assert!(unlock_bip39(&secp256k1::Secp256k1::new(), &seed, "foo").is_ok());
415446
assert!(!is_locked());
416447
lock();
417448
assert!(is_locked());
@@ -484,6 +515,69 @@ mod tests {
484515
assert!(matches!(unlock("password"), Err(Error::Unseeded)));
485516
}
486517

518+
#[test]
519+
fn test_derive_bip39_seed() {
520+
struct Test {
521+
seed: &'static str,
522+
passphrase: &'static str,
523+
expected_bip39_seed: &'static str,
524+
expected_root_fingerprint: &'static str,
525+
}
526+
527+
let tests = &[
528+
// 16 byte seed
529+
Test {
530+
seed: "fb5cf00d5ea61059fa066e25a6be9544",
531+
passphrase: "",
532+
expected_bip39_seed: "f4577e463be595868060e5a763328153155b4167cd284998c8c6096d044742372020f5b052d0c41c1c5e6a6a7da2cb8a367aaaa074fab7773e8d5b2f684257ed",
533+
expected_root_fingerprint: "0b2fa4e5",
534+
},
535+
Test {
536+
seed: "fb5cf00d5ea61059fa066e25a6be9544",
537+
passphrase: "password",
538+
expected_bip39_seed: "5922fb7630bc7cb871af102f733b6bdb8f05945147cd4646a89056fde0bdad5c3a4ff5be3f9e7af535f570e7053b5b22472555b331bc89cb797c306f7eb6a5a1",
539+
expected_root_fingerprint: "c4062d44",
540+
},
541+
// 24 byte seed
542+
Test {
543+
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
544+
passphrase: "",
545+
expected_bip39_seed: "4a2a016a6d90eb3a79b7931ca0a172df5c5bfee3e5b47f0fd84bc0791ea3bbc9476c3d5de71cdb12c37e93c2aa3d5c303257f1992aed400fc5bbfc7da787bfa7",
546+
expected_root_fingerprint: "62fd19e0",
547+
},
548+
Test {
549+
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
550+
passphrase: "password",
551+
expected_bip39_seed: "bc317ee0f88870254be32274d63ec2b0e962bf09f3ca04287912bfc843f2fab7c556f8657cadc924f99a217b0daa91898303a8414102031a125c50023e45a80b",
552+
expected_root_fingerprint: "c745266d",
553+
},
554+
// 32 byte seed
555+
Test {
556+
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
557+
passphrase: "",
558+
expected_bip39_seed: "63f844e2c61ecfb20f9100de381a7a9ec875b085f5ac7735a2ba4d615a0f4147b87be402f65651969130683deeef752760c09e291604fe4b89d61ffee2630be8",
559+
expected_root_fingerprint: "93ba3a7b",
560+
},
561+
Test {
562+
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
563+
passphrase: "password",
564+
expected_bip39_seed: "42e90dacd61f3373542d212f0fb9c291dcea84a6d85034272372dde7188638a98527280d65e41599f30d3434d8ee3d4747dbb84801ff1a851d2306c7d1648374",
565+
expected_root_fingerprint: "b95c9318",
566+
},
567+
];
568+
569+
let secp = secp256k1::Secp256k1::new();
570+
for test in tests {
571+
let seed = hex::decode(test.seed).unwrap();
572+
let (bip39_seed, root_fingerprint) = derive_bip39_seed(&secp, &seed, test.passphrase);
573+
assert_eq!(hex::encode(bip39_seed).as_str(), test.expected_bip39_seed);
574+
assert_eq!(
575+
hex::encode(root_fingerprint).as_str(),
576+
test.expected_root_fingerprint
577+
);
578+
}
579+
}
580+
487581
#[test]
488582
fn test_unlock_bip39() {
489583
mock_memory();
@@ -497,13 +591,15 @@ mod tests {
497591
.unwrap();
498592
crate::memory::set_salt_root(mock_salt_root.as_slice().try_into().unwrap()).unwrap();
499593

594+
let secp = secp256k1::Secp256k1::new();
595+
500596
assert!(root_fingerprint().is_err());
501597
assert!(encrypt_and_store_seed(&seed, "password").is_ok());
502598
assert!(root_fingerprint().is_err());
503599
// Incorrect seed passed
504-
assert!(unlock_bip39(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "foo").is_err());
600+
assert!(unlock_bip39(&secp, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "foo").is_err());
505601
// Correct seed passed.
506-
assert!(unlock_bip39(&seed, "foo").is_ok());
602+
assert!(unlock_bip39(&secp, &seed, "foo").is_ok());
507603
assert_eq!(root_fingerprint(), Ok(vec![0xf1, 0xbc, 0x3c, 0x46]),);
508604

509605
let expected_bip39_seed = hex::decode("2b3c63de86f0f2b13cc6a36c1ba2314fbc1b40c77ab9cb64e96ba4d5c62fc204748ca6626a9f035e7d431bce8c9210ec0bdffc2e7db873dee56c8ac2153eee9a").unwrap();

0 commit comments

Comments
 (0)