Skip to content

Commit b153b88

Browse files
committed
Merge branch 'move-bip39'
2 parents fcf2d8b + 9cc0493 commit b153b88

File tree

9 files changed

+141
-175
lines changed

9 files changed

+141
-175
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ foreach(type ${RUST_LIBS})
400400
# We build the rust standard libs so that everything from the rust runtime
401401
# is included (like eh_personality).
402402
if(type STREQUAL "c-unit-tests")
403-
set(RUST_CARGO_FLAGS ${RUST_CARGO_FLAGS} -Zbuild-std)
403+
set(RUST_CARGO_FLAGS ${RUST_CARGO_FLAGS} -Zbuild-std=std,panic_abort)
404404
endif()
405405
set(lib ${RUST_BINARY_DIR}/feature-${type}/${RUST_TARGET_ARCH_DIR}/${RUST_PROFILE}/libbitbox02_rust_c.a)
406406
# The dummy output is to always trigger rebuild (cargo is clever enough to

src/rust/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/rust/bitbox02-rust-c/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ firmware = [
8888
testing = ["bitbox02-rust/testing", "bitbox02/testing"]
8989

9090
# Active when the Rust code is compiled to be linked into the C unit tests and simulator.
91-
c-unit-testing = ["bitbox02-rust/c-unit-testing", "bitbox02/c-unit-testing"]
91+
c-unit-testing = ["bitbox02-rust/c-unit-testing", "bitbox02/c-unit-testing", "util/c-unit-testing"]
9292

9393
app-ethereum = [
9494
# enable this feature in the deps

src/rust/bitbox02-rust-c/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ extern crate util;
3838
// Whenever execution reaches somewhere it isn't supposed to rust code will "panic". Our panic
3939
// handler will print the available information on the screen and over RTT. If we compile with
4040
// `panic=abort` this code will never get executed.
41-
#[cfg(not(test))]
42-
#[cfg(not(feature = "testing"))]
4341
#[cfg_attr(feature = "bootloader", allow(unused_variables))]
42+
#[cfg(not(any(test, feature = "testing", feature = "c-unit-testing")))]
4443
#[panic_handler]
4544
fn panic(info: &core::panic::PanicInfo) -> ! {
4645
#[cfg(feature = "firmware")]

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ pub fn mnemonic_to_seed(mnemonic: &str) -> Result<zeroize::Zeroizing<Vec<u8>>, (
4040
Ok(zeroize::Zeroizing::new(seed[..seed_len].to_vec()))
4141
}
4242

43+
/// Derives the bip39 seed and returns it and the bip32 root fingerprint.
44+
/// `mnemonic_passphrase` is the bip39 passphrase used in the derivation.
45+
/// `yield_now` is called in each of the 2048 bip39 pbkdf2 iterations.
46+
pub async fn derive_seed(
47+
seed: &[u8],
48+
mnemonic_passphrase: &str,
49+
yield_now: impl AsyncFn(),
50+
) -> (zeroize::Zeroizing<[u8; 64]>, [u8; 4]) {
51+
let mnemonic = bip39::Mnemonic::from_entropy_in(bip39::Language::English, seed).unwrap();
52+
let bip39_seed: zeroize::Zeroizing<[u8; 64]> = zeroize::Zeroizing::new(
53+
mnemonic
54+
.to_seed_normalized_async(mnemonic_passphrase, yield_now)
55+
.await,
56+
);
57+
let root_fingerprint: [u8; 4] =
58+
bitcoin::bip32::Xpriv::new_master(bitcoin::NetworkKind::Main, bip39_seed.as_ref())
59+
.unwrap()
60+
.fingerprint(crate::secp256k1::SECP256K1)
61+
.to_bytes();
62+
63+
(bip39_seed, root_fingerprint)
64+
}
65+
4366
// C API
4467

4568
#[unsafe(no_mangle)]
@@ -61,6 +84,7 @@ pub extern "C" fn rust_get_bip39_word(idx: u16, mut out: util::bytes::BytesMut)
6184
#[cfg(test)]
6285
mod tests {
6386
use super::*;
87+
use util::bb02_async::block_on;
6488

6589
#[test]
6690
fn test_rust_get_bip39_word() {
@@ -168,4 +192,67 @@ mod tests {
168192
b"\xae\x45\xd4\x02\x3a\xfa\x4a\x48\x68\x77\x51\x69\xfe\xa5\xf5\xe4\x97\xf7\xa1\xa4\xd6\x22\x9a\xd0\x23\x9e\x68\x9b\x48\x2e\xd3\x5e",
169193
);
170194
}
195+
196+
#[test]
197+
fn test_derive_bip39_seed() {
198+
struct Test {
199+
seed: &'static str,
200+
passphrase: &'static str,
201+
expected_bip39_seed: &'static str,
202+
expected_root_fingerprint: &'static str,
203+
}
204+
205+
let tests = &[
206+
// 16 byte seed
207+
Test {
208+
seed: "fb5cf00d5ea61059fa066e25a6be9544",
209+
passphrase: "",
210+
expected_bip39_seed: "f4577e463be595868060e5a763328153155b4167cd284998c8c6096d044742372020f5b052d0c41c1c5e6a6a7da2cb8a367aaaa074fab7773e8d5b2f684257ed",
211+
expected_root_fingerprint: "0b2fa4e5",
212+
},
213+
Test {
214+
seed: "fb5cf00d5ea61059fa066e25a6be9544",
215+
passphrase: "password",
216+
expected_bip39_seed: "5922fb7630bc7cb871af102f733b6bdb8f05945147cd4646a89056fde0bdad5c3a4ff5be3f9e7af535f570e7053b5b22472555b331bc89cb797c306f7eb6a5a1",
217+
expected_root_fingerprint: "c4062d44",
218+
},
219+
// 24 byte seed
220+
Test {
221+
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
222+
passphrase: "",
223+
expected_bip39_seed: "4a2a016a6d90eb3a79b7931ca0a172df5c5bfee3e5b47f0fd84bc0791ea3bbc9476c3d5de71cdb12c37e93c2aa3d5c303257f1992aed400fc5bbfc7da787bfa7",
224+
expected_root_fingerprint: "62fd19e0",
225+
},
226+
Test {
227+
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
228+
passphrase: "password",
229+
expected_bip39_seed: "bc317ee0f88870254be32274d63ec2b0e962bf09f3ca04287912bfc843f2fab7c556f8657cadc924f99a217b0daa91898303a8414102031a125c50023e45a80b",
230+
expected_root_fingerprint: "c745266d",
231+
},
232+
// 32 byte seed
233+
Test {
234+
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
235+
passphrase: "",
236+
expected_bip39_seed: "63f844e2c61ecfb20f9100de381a7a9ec875b085f5ac7735a2ba4d615a0f4147b87be402f65651969130683deeef752760c09e291604fe4b89d61ffee2630be8",
237+
expected_root_fingerprint: "93ba3a7b",
238+
},
239+
Test {
240+
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
241+
passphrase: "password",
242+
expected_bip39_seed: "42e90dacd61f3373542d212f0fb9c291dcea84a6d85034272372dde7188638a98527280d65e41599f30d3434d8ee3d4747dbb84801ff1a851d2306c7d1648374",
243+
expected_root_fingerprint: "b95c9318",
244+
},
245+
];
246+
247+
for test in tests {
248+
let seed = hex::decode(test.seed).unwrap();
249+
let (bip39_seed, root_fingerprint) =
250+
block_on(derive_seed(&seed, test.passphrase, async || {}));
251+
assert_eq!(hex::encode(bip39_seed).as_str(), test.expected_bip39_seed);
252+
assert_eq!(
253+
hex::encode(root_fingerprint).as_str(),
254+
test.expected_root_fingerprint
255+
);
256+
}
257+
}
171258
}

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub use bitbox02::keystore::SignResult;
2424
use bitbox02::{keystore, securechip};
2525

2626
use util::bip32::HARDENED;
27+
use util::cell::SyncCell;
2728

2829
use crate::secp256k1::SECP256K1;
2930

@@ -32,9 +33,12 @@ use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256, sha512};
3233
/// Length of a compressed secp256k1 pubkey.
3334
const EC_PUBLIC_KEY_LEN: usize = 33;
3435

36+
static ROOT_FINGERPRINT: SyncCell<Option<[u8; 4]>> = SyncCell::new(None);
37+
3538
/// Locks the keystore (resets to state before `unlock()`).
3639
pub fn lock() {
3740
keystore::_lock();
41+
ROOT_FINGERPRINT.write(None)
3842
}
3943

4044
/// Returns false if the keystore is unlocked (unlock() followed by unlock_bip39()), true otherwise.
@@ -55,7 +59,23 @@ pub async fn unlock_bip39(
5559
mnemonic_passphrase: &str,
5660
yield_now: impl AsyncFn(),
5761
) -> Result<(), Error> {
58-
keystore::_unlock_bip39(SECP256K1, seed, mnemonic_passphrase, yield_now).await
62+
keystore::unlock_bip39_check(seed)?;
63+
64+
let (bip39_seed, root_fingerprint) =
65+
crate::bip39::derive_seed(seed, mnemonic_passphrase, &yield_now).await;
66+
67+
let (bip39_seed_2, root_fingerprint_2) =
68+
crate::bip39::derive_seed(seed, mnemonic_passphrase, &yield_now).await;
69+
70+
if bip39_seed != bip39_seed_2 || root_fingerprint != root_fingerprint_2 {
71+
return Err(Error::Memory);
72+
}
73+
74+
keystore::unlock_bip39_finalize(bip39_seed.as_slice().try_into().unwrap())?;
75+
76+
// Store root fingerprint.
77+
ROOT_FINGERPRINT.write(Some(root_fingerprint));
78+
Ok(())
5979
}
6080

6181
/// Returns a copy of the retained seed. Errors if the keystore is locked.
@@ -183,7 +203,10 @@ pub fn get_xpubs_twice(keypaths: &[&[u32]]) -> Result<Vec<bip32::Xpub>, ()> {
183203
/// according to:
184204
/// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format
185205
pub fn root_fingerprint() -> Result<Vec<u8>, ()> {
186-
keystore::root_fingerprint()
206+
if is_locked() {
207+
return Err(());
208+
}
209+
ROOT_FINGERPRINT.read().ok_or(()).map(|fp| fp.to_vec())
187210
}
188211

189212
/// Stretches the given encryption_key using the securechip. The resulting key is used to encrypt

src/rust/bitbox02/src/keystore.rs

Lines changed: 2 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ extern crate alloc;
1717
use alloc::vec;
1818
use alloc::vec::Vec;
1919

20-
use util::cell::SyncUnsafeCell;
21-
2220
use bitcoin::secp256k1::{All, Secp256k1};
2321

2422
use core::convert::TryInto;
@@ -29,8 +27,6 @@ use bitbox02_sys::keystore_error_t;
2927
const EC_PUBLIC_KEY_LEN: usize = 33;
3028
pub const MAX_SEED_LENGTH: usize = bitbox02_sys::KEYSTORE_MAX_SEED_LENGTH as usize;
3129

32-
static ROOT_FINGERPRINT: SyncUnsafeCell<Option<[u8; 4]>> = SyncUnsafeCell::new(None);
33-
3430
pub fn _is_locked() -> bool {
3531
unsafe { bitbox02_sys::keystore_is_locked() }
3632
}
@@ -97,47 +93,24 @@ pub fn _unlock(password: &str) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
9793

9894
pub fn _lock() {
9995
unsafe { bitbox02_sys::keystore_lock() }
100-
101-
unsafe { ROOT_FINGERPRINT.write(None) }
10296
}
10397

104-
fn unlock_bip39_check(seed: &[u8]) -> Result<(), Error> {
98+
pub fn unlock_bip39_check(seed: &[u8]) -> Result<(), Error> {
10599
if unsafe { bitbox02_sys::keystore_unlock_bip39_check(seed.as_ptr(), seed.len()) } {
106100
Ok(())
107101
} else {
108102
Err(Error::CannotUnlockBIP39)
109103
}
110104
}
111105

112-
fn unlock_bip39_finalize(bip39_seed: &[u8; 64]) -> Result<(), Error> {
106+
pub fn unlock_bip39_finalize(bip39_seed: &[u8; 64]) -> Result<(), Error> {
113107
if unsafe { bitbox02_sys::keystore_unlock_bip39_finalize(bip39_seed.as_ptr()) } {
114108
Ok(())
115109
} else {
116110
Err(Error::CannotUnlockBIP39)
117111
}
118112
}
119113

120-
async fn derive_bip39_seed(
121-
secp: &Secp256k1<All>,
122-
seed: &[u8],
123-
mnemonic_passphrase: &str,
124-
yield_now: impl AsyncFn(),
125-
) -> (zeroize::Zeroizing<[u8; 64]>, [u8; 4]) {
126-
let mnemonic = bip39::Mnemonic::from_entropy_in(bip39::Language::English, seed).unwrap();
127-
let bip39_seed: zeroize::Zeroizing<[u8; 64]> = zeroize::Zeroizing::new(
128-
mnemonic
129-
.to_seed_normalized_async(mnemonic_passphrase, yield_now)
130-
.await,
131-
);
132-
let root_fingerprint: [u8; 4] =
133-
bitcoin::bip32::Xpriv::new_master(bitcoin::NetworkKind::Main, bip39_seed.as_ref())
134-
.unwrap()
135-
.fingerprint(secp)
136-
.to_bytes();
137-
138-
(bip39_seed, root_fingerprint)
139-
}
140-
141114
#[cfg(feature = "testing")]
142115
pub fn test_get_retained_seed_encrypted() -> &'static [u8] {
143116
unsafe {
@@ -156,44 +129,6 @@ pub fn test_get_retained_bip39_seed_encrypted() -> &'static [u8] {
156129
}
157130
}
158131

159-
/// Unlocks the bip39 seed. The input seed must be the keystore seed (i.e. must match the output
160-
/// of `keystore_copy_seed()`).
161-
/// `mnemonic_passphrase` is the bip39 passphrase used in the derivation. Use the empty string if no
162-
/// passphrase is needed or provided.
163-
pub async fn _unlock_bip39(
164-
secp: &Secp256k1<All>,
165-
seed: &[u8],
166-
mnemonic_passphrase: &str,
167-
yield_now: impl AsyncFn(),
168-
) -> Result<(), Error> {
169-
unlock_bip39_check(seed)?;
170-
171-
let (bip39_seed, root_fingerprint) =
172-
derive_bip39_seed(secp, seed, mnemonic_passphrase, &yield_now).await;
173-
174-
let (bip39_seed_2, root_fingerprint_2) =
175-
derive_bip39_seed(secp, seed, mnemonic_passphrase, &yield_now).await;
176-
177-
if bip39_seed != bip39_seed_2 || root_fingerprint != root_fingerprint_2 {
178-
return Err(Error::Memory);
179-
}
180-
181-
unlock_bip39_finalize(bip39_seed.as_slice().try_into().unwrap())?;
182-
183-
// Store root fingerprint.
184-
unsafe {
185-
ROOT_FINGERPRINT.write(Some(root_fingerprint));
186-
}
187-
Ok(())
188-
}
189-
190-
pub fn root_fingerprint() -> Result<Vec<u8>, ()> {
191-
if _is_locked() {
192-
return Err(());
193-
}
194-
unsafe { ROOT_FINGERPRINT.read().ok_or(()).map(|fp| fp.to_vec()) }
195-
}
196-
197132
pub fn _create_and_store_seed(password: &str, host_entropy: &[u8]) -> Result<(), Error> {
198133
match unsafe {
199134
bitbox02_sys::keystore_create_and_store_seed(
@@ -304,78 +239,3 @@ pub fn mock_unlocked(seed: &[u8]) {
304239
bitbox02_sys::keystore_mock_unlocked(seed.as_ptr(), seed.len() as _, core::ptr::null())
305240
}
306241
}
307-
308-
#[cfg(test)]
309-
mod tests {
310-
use super::*;
311-
use bitcoin::secp256k1;
312-
use util::bb02_async::block_on;
313-
314-
#[test]
315-
fn test_derive_bip39_seed() {
316-
struct Test {
317-
seed: &'static str,
318-
passphrase: &'static str,
319-
expected_bip39_seed: &'static str,
320-
expected_root_fingerprint: &'static str,
321-
}
322-
323-
let tests = &[
324-
// 16 byte seed
325-
Test {
326-
seed: "fb5cf00d5ea61059fa066e25a6be9544",
327-
passphrase: "",
328-
expected_bip39_seed: "f4577e463be595868060e5a763328153155b4167cd284998c8c6096d044742372020f5b052d0c41c1c5e6a6a7da2cb8a367aaaa074fab7773e8d5b2f684257ed",
329-
expected_root_fingerprint: "0b2fa4e5",
330-
},
331-
Test {
332-
seed: "fb5cf00d5ea61059fa066e25a6be9544",
333-
passphrase: "password",
334-
expected_bip39_seed: "5922fb7630bc7cb871af102f733b6bdb8f05945147cd4646a89056fde0bdad5c3a4ff5be3f9e7af535f570e7053b5b22472555b331bc89cb797c306f7eb6a5a1",
335-
expected_root_fingerprint: "c4062d44",
336-
},
337-
// 24 byte seed
338-
Test {
339-
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
340-
passphrase: "",
341-
expected_bip39_seed: "4a2a016a6d90eb3a79b7931ca0a172df5c5bfee3e5b47f0fd84bc0791ea3bbc9476c3d5de71cdb12c37e93c2aa3d5c303257f1992aed400fc5bbfc7da787bfa7",
342-
expected_root_fingerprint: "62fd19e0",
343-
},
344-
Test {
345-
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
346-
passphrase: "password",
347-
expected_bip39_seed: "bc317ee0f88870254be32274d63ec2b0e962bf09f3ca04287912bfc843f2fab7c556f8657cadc924f99a217b0daa91898303a8414102031a125c50023e45a80b",
348-
expected_root_fingerprint: "c745266d",
349-
},
350-
// 32 byte seed
351-
Test {
352-
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
353-
passphrase: "",
354-
expected_bip39_seed: "63f844e2c61ecfb20f9100de381a7a9ec875b085f5ac7735a2ba4d615a0f4147b87be402f65651969130683deeef752760c09e291604fe4b89d61ffee2630be8",
355-
expected_root_fingerprint: "93ba3a7b",
356-
},
357-
Test {
358-
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
359-
passphrase: "password",
360-
expected_bip39_seed: "42e90dacd61f3373542d212f0fb9c291dcea84a6d85034272372dde7188638a98527280d65e41599f30d3434d8ee3d4747dbb84801ff1a851d2306c7d1648374",
361-
expected_root_fingerprint: "b95c9318",
362-
},
363-
];
364-
365-
let secp = secp256k1::Secp256k1::new();
366-
for test in tests {
367-
let seed = hex::decode(test.seed).unwrap();
368-
let (bip39_seed, root_fingerprint) = block_on(derive_bip39_seed(
369-
&secp,
370-
&seed,
371-
test.passphrase,
372-
async || {},
373-
));
374-
assert_eq!(hex::encode(bip39_seed).as_str(), test.expected_bip39_seed);
375-
assert_eq!(
376-
hex::encode(root_fingerprint).as_str(),
377-
test.expected_root_fingerprint
378-
);
379-
}
380-
}
381-
}

0 commit comments

Comments
 (0)