Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions src/keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ static size_t _retained_seed_encrypted_len = 0;
// plaintext.
static uint8_t _retained_seed_hash[32] = {0};

// Change this ONLY via keystore_unlock_bip39().
// Change this ONLY via keystore_unlock_bip39_finalize().
static bool _is_unlocked_bip39 = false;
// Stores a random keyy after bip39-unlock which, after stretching, is used to encrypt the retained
// Stores a random key after bip39-unlock which, after stretching, is used to encrypt the retained
// bip39 seed.
static uint8_t _unstretched_retained_bip39_seed_encryption_key[32] = {0};
// Must be defined if _is_unlocked is true. ONLY ACCESS THIS WITH keystore_copy_bip39_seed().
Expand Down Expand Up @@ -485,16 +485,11 @@ keystore_error_t keystore_unlock(
return result;
}

bool keystore_unlock_bip39(
const uint8_t* seed,
size_t seed_length,
const char* mnemonic_passphrase,
uint8_t* root_fingerprint_out)
bool keystore_unlock_bip39_check(const uint8_t* seed, size_t seed_length)
{
if (!_is_unlocked_device) {
return false;
}
usb_processing_timeout_reset(LONG_TIMEOUT);

uint8_t seed_hashed[32] = {0};
UTIL_CLEANUP_32(seed_hashed);
Expand All @@ -505,14 +500,13 @@ bool keystore_unlock_bip39(
return false;
}

uint8_t bip39_seed[64] = {0};
UTIL_CLEANUP_64(bip39_seed);
rust_derive_bip39_seed(
rust_util_bytes(seed, seed_length),
mnemonic_passphrase,
rust_util_bytes_mut(bip39_seed, sizeof(bip39_seed)),
rust_util_bytes_mut(root_fingerprint_out, 4));
usb_processing_timeout_reset(LONG_TIMEOUT);

return true;
}

bool keystore_unlock_bip39_finalize(const uint8_t* bip39_seed)
{
if (!_retain_bip39_seed(bip39_seed)) {
return false;
}
Expand Down
21 changes: 10 additions & 11 deletions src/keystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,21 +104,20 @@ USE_RESULT keystore_error_t keystore_create_and_store_seed(
USE_RESULT keystore_error_t
keystore_unlock(const char* password, uint8_t* remaining_attempts_out, int* securechip_result_out);

/** Unlocks the bip39 seed. The input seed must be the keystore seed (i.e. must match the output
/**
* Checks if bip39 unlocking can be performed. It can be performed if `keystore_unlock()`
* successfully and the input seed matches the keystore seed (i.e. must match the output
* of `keystore_copy_seed()`).
* @param[in] seed the input seed to BIP39.
* @param[in] seed_length the size of the seed
* @param[in] mnemonic_passphrase bip39 passphrase used in the derivation. Use the
* empty string if no passphrase is needed or provided.
* @param[out] root_fingerprint_out must be 4 bytes long and will contain the root fingerprint of
* the wallet.
* @return returns false if there was a critital memory error, otherwise true.
*/
USE_RESULT bool keystore_unlock_bip39(
const uint8_t* seed,
size_t seed_length,
const char* mnemonic_passphrase,
uint8_t* root_fingerprint_out);
USE_RESULT bool keystore_unlock_bip39_check(const uint8_t* seed, size_t seed_length);

/**
* Retains the given bip39 seed and marks the keystore as unlocked.
* @param[in] bip39_seed 64 byte bip39 seed.
*/
USE_RESULT bool keystore_unlock_bip39_finalize(const uint8_t* bip39_seed);

/**
* Locks the keystore (resets to state before `keystore_unlock()`).
Expand Down
105 changes: 0 additions & 105 deletions src/rust/bitbox02-rust/src/bip39.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,37 +27,6 @@ pub fn get_word(idx: u16) -> Result<zeroize::Zeroizing<String>, ()> {

// C API

/// # Safety
///
/// The passphrase must be not NULL and null-terminated.
///
/// `seed` must be 16, 24 or 32 bytes long.
/// `bip39_seed_out` must be exactly 64 bytes long.
/// `root_fingerprint_out` must be exactly 4 bytes long.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_derive_bip39_seed(
seed: util::bytes::Bytes,
passphrase: *const core::ffi::c_char,
mut bip39_seed_out: util::bytes::BytesMut,
mut root_fingerprint_out: util::bytes::BytesMut,
) {
let mnemonic =
bip39::Mnemonic::from_entropy_in(bip39::Language::English, seed.as_ref()).unwrap();
let passphrase = unsafe { core::ffi::CStr::from_ptr(passphrase) };
let bip39_seed: zeroize::Zeroizing<[u8; 64]> =
zeroize::Zeroizing::new(mnemonic.to_seed_normalized(passphrase.to_str().unwrap()));
bip39_seed_out.as_mut().clone_from_slice(&bip39_seed[..]);

let root_fingerprint: [u8; 4] =
bitcoin::bip32::Xpriv::new_master(bitcoin::NetworkKind::Main, bip39_seed.as_ref())
.unwrap()
.fingerprint(crate::secp256k1::SECP256K1)
.to_bytes();
root_fingerprint_out
.as_mut()
.clone_from_slice(&root_fingerprint);
}

#[unsafe(no_mangle)]
pub extern "C" fn rust_get_bip39_word(idx: u16, mut out: util::bytes::BytesMut) -> bool {
let word = match get_word(idx) {
Expand All @@ -78,80 +47,6 @@ pub extern "C" fn rust_get_bip39_word(idx: u16, mut out: util::bytes::BytesMut)
mod tests {
use super::*;

#[test]
fn test_rust_derive_bip39_seed() {
struct Test {
seed: &'static str,
passphrase: &'static core::ffi::CStr,
expected_bip39_seed: &'static str,
expected_root_fingerprint: &'static str,
}

let tests = &[
// 16 byte seed
Test {
seed: "fb5cf00d5ea61059fa066e25a6be9544",
passphrase: c"",
expected_bip39_seed: "f4577e463be595868060e5a763328153155b4167cd284998c8c6096d044742372020f5b052d0c41c1c5e6a6a7da2cb8a367aaaa074fab7773e8d5b2f684257ed",
expected_root_fingerprint: "0b2fa4e5",
},
Test {
seed: "fb5cf00d5ea61059fa066e25a6be9544",
passphrase: c"password",
expected_bip39_seed: "5922fb7630bc7cb871af102f733b6bdb8f05945147cd4646a89056fde0bdad5c3a4ff5be3f9e7af535f570e7053b5b22472555b331bc89cb797c306f7eb6a5a1",
expected_root_fingerprint: "c4062d44",
},
// 24 byte seed
Test {
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
passphrase: c"",
expected_bip39_seed: "4a2a016a6d90eb3a79b7931ca0a172df5c5bfee3e5b47f0fd84bc0791ea3bbc9476c3d5de71cdb12c37e93c2aa3d5c303257f1992aed400fc5bbfc7da787bfa7",
expected_root_fingerprint: "62fd19e0",
},
Test {
seed: "23705a91b177b49822f28b3f1a60072d113fcaff4f250191",
passphrase: c"password",
expected_bip39_seed: "bc317ee0f88870254be32274d63ec2b0e962bf09f3ca04287912bfc843f2fab7c556f8657cadc924f99a217b0daa91898303a8414102031a125c50023e45a80b",
expected_root_fingerprint: "c745266d",
},
// 32 byte seed
Test {
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
passphrase: c"",
expected_bip39_seed: "63f844e2c61ecfb20f9100de381a7a9ec875b085f5ac7735a2ba4d615a0f4147b87be402f65651969130683deeef752760c09e291604fe4b89d61ffee2630be8",
expected_root_fingerprint: "93ba3a7b",
},
Test {
seed: "bd83a008b3b78c8cc56c678d1b7bfc651cc5be8242f44b5c0db96a34ee297833",
passphrase: c"password",
expected_bip39_seed: "42e90dacd61f3373542d212f0fb9c291dcea84a6d85034272372dde7188638a98527280d65e41599f30d3434d8ee3d4747dbb84801ff1a851d2306c7d1648374",
expected_root_fingerprint: "b95c9318",
},
];

for test in tests {
let seed = hex::decode(test.seed).unwrap();
let mut bip39_seed = [0u8; 64];
let mut root_fingerprint = [0u8; 4];
unsafe {
rust_derive_bip39_seed(
util::bytes::rust_util_bytes(seed.as_ptr(), seed.len()),
test.passphrase.as_ptr(),
util::bytes::rust_util_bytes_mut(bip39_seed.as_mut_ptr(), bip39_seed.len()),
util::bytes::rust_util_bytes_mut(
root_fingerprint.as_mut_ptr(),
root_fingerprint.len(),
),
);
}
assert_eq!(hex::encode(bip39_seed).as_str(), test.expected_bip39_seed);
assert_eq!(
hex::encode(root_fingerprint).as_str(),
test.expected_root_fingerprint
);
}
}

#[test]
fn test_rust_get_bip39_word() {
let mut word = [1u8; 10];
Expand Down
4 changes: 2 additions & 2 deletions src/rust/bitbox02-rust/src/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ mod tests {
keystore::lock();
let seed = &seed[..test.seed_len];

assert!(keystore::unlock_bip39(seed, test.mnemonic_passphrase).is_err());
assert!(keystore::unlock_bip39(SECP256K1, seed, test.mnemonic_passphrase).is_err());

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

bitbox02::securechip::fake_event_counter_reset();
assert!(keystore::unlock_bip39(seed, test.mnemonic_passphrase).is_ok());
assert!(keystore::unlock_bip39(SECP256K1, seed, test.mnemonic_passphrase).is_ok());
assert_eq!(bitbox02::securechip::fake_event_counter(), 1);

assert!(!keystore::is_locked());
Expand Down
5 changes: 3 additions & 2 deletions src/rust/bitbox02-rust/src/workflow/unlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,9 @@ pub async fn unlock_bip39(hal: &mut impl crate::hal::Hal, seed: &[u8]) {
}
}

let result =
bitbox02::ui::with_lock_animation(|| keystore::unlock_bip39(seed, &mnemonic_passphrase));
let result = bitbox02::ui::with_lock_animation(|| {
keystore::unlock_bip39(crate::secp256k1::SECP256K1, seed, &mnemonic_passphrase)
});
if result.is_err() {
abort("bip39 unlock failed");
}
Expand Down
3 changes: 2 additions & 1 deletion src/rust/bitbox02-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ const ALLOWLIST_FNS: &[&str] = &[
"keystore_secp256k1_nonce_commit",
"keystore_secp256k1_sign",
"keystore_unlock",
"keystore_unlock_bip39",
"keystore_unlock_bip39_check",
"keystore_unlock_bip39_finalize",
"keystore_test_get_retained_seed_encrypted",
"keystore_test_get_retained_bip39_seed_encrypted",
"label_create",
Expand Down
Loading