diff --git a/messages/shamir.proto b/messages/shamir.proto index 5dcc33ea8..0fe092c86 100644 --- a/messages/shamir.proto +++ b/messages/shamir.proto @@ -18,4 +18,6 @@ package shiftcrypto.bitbox02; message ShowShamirRequest { } message RestoreFromShamirRequest { + uint32 timestamp = 1; + int32 timezone_offset = 2; } diff --git a/py/bitbox02/bitbox02/bitbox02/bitbox02.py b/py/bitbox02/bitbox02/bitbox02/bitbox02.py index 97094a5b7..38223240b 100644 --- a/py/bitbox02/bitbox02/bitbox02/bitbox02.py +++ b/py/bitbox02/bitbox02/bitbox02/bitbox02.py @@ -1148,6 +1148,19 @@ def restore_from_mnemonic(self) -> None: ) self._msg_query(request) + def restore_from_shamir(self) -> None: + """ + Restore from shamir backup. Raises a Bitbox02Exception on failure. + """ + request = hww.Request() + # pylint: disable=no-member + request.restore_from_shamir.CopyFrom( + shamir.RestoreFromShamirRequest( + timestamp=int(time.time()), timezone_offset=time.localtime().tm_gmtoff + ) + ) + self._msg_query(request) + def _cardano_msg_query( self, cardano_request: cardano.CardanoRequest, expected_response: Optional[str] = None ) -> cardano.CardanoResponse: diff --git a/py/bitbox02/bitbox02/communication/generated/shamir_pb2.py b/py/bitbox02/bitbox02/communication/generated/shamir_pb2.py index be4e9da95..d41322f7c 100644 --- a/py/bitbox02/bitbox02/communication/generated/shamir_pb2.py +++ b/py/bitbox02/bitbox02/communication/generated/shamir_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cshamir.proto\x12\x14shiftcrypto.bitbox02\"\x13\n\x11ShowShamirRequest\"\x1a\n\x18RestoreFromShamirRequestb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cshamir.proto\x12\x14shiftcrypto.bitbox02\"\x13\n\x11ShowShamirRequest\"F\n\x18RestoreFromShamirRequest\x12\x11\n\ttimestamp\x18\x01 \x01(\r\x12\x17\n\x0ftimezone_offset\x18\x02 \x01(\x05\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'shamir_pb2', globals()) @@ -23,5 +23,5 @@ _SHOWSHAMIRREQUEST._serialized_start=38 _SHOWSHAMIRREQUEST._serialized_end=57 _RESTOREFROMSHAMIRREQUEST._serialized_start=59 - _RESTOREFROMSHAMIRREQUEST._serialized_end=85 + _RESTOREFROMSHAMIRREQUEST._serialized_end=129 # @@protoc_insertion_point(module_scope) diff --git a/py/bitbox02/bitbox02/communication/generated/shamir_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/shamir_pb2.pyi index b7719da6d..8ce22be8c 100644 --- a/py/bitbox02/bitbox02/communication/generated/shamir_pb2.pyi +++ b/py/bitbox02/bitbox02/communication/generated/shamir_pb2.pyi @@ -2,8 +2,10 @@ @generated by mypy-protobuf. Do not edit manually! isort:skip_file """ +import builtins import google.protobuf.descriptor import google.protobuf.message +import typing_extensions DESCRIPTOR: google.protobuf.descriptor.FileDescriptor @@ -15,6 +17,14 @@ global___ShowShamirRequest = ShowShamirRequest class RestoreFromShamirRequest(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor + TIMESTAMP_FIELD_NUMBER: builtins.int + TIMEZONE_OFFSET_FIELD_NUMBER: builtins.int + timestamp: builtins.int + timezone_offset: builtins.int def __init__(self, + *, + timestamp: builtins.int = ..., + timezone_offset: builtins.int = ..., ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["timestamp",b"timestamp","timezone_offset",b"timezone_offset"]) -> None: ... global___RestoreFromShamirRequest = RestoreFromShamirRequest diff --git a/py/send_message.py b/py/send_message.py index e488aaa4b..da53e3e99 100755 --- a/py/send_message.py +++ b/py/send_message.py @@ -291,6 +291,13 @@ def _restore_from_mnemonic(self) -> None: except UserAbortException: print("Aborted by user") + def _restore_from_shamir(self) -> None: + try: + self._device.restore_from_shamir() + print("Restore successful") + except UserAbortException: + print("Aborted by user") + def _list_device_info(self) -> None: print(f"All info: {self._device.device_info()}") @@ -1397,6 +1404,7 @@ def _menu_notinit(self) -> None: ("Set up a new wallet", self._setup_workflow), ("Restore from backup", self._restore_backup_workflow), ("Restore from mnemonic", self._restore_from_mnemonic), + ("Restore from shamir", self._restore_from_shamir), ("List device info", self._list_device_info), ("Reboot into bootloader", self._reboot), ("Check if SD card inserted", self._check_sd_presence), diff --git a/src/keystore.c b/src/keystore.c index 2b87d304a..710f73c84 100644 --- a/src/keystore.c +++ b/src/keystore.c @@ -570,9 +570,9 @@ bool keystore_get_bip39_mnemonic_from_bytes(const uint8_t* bytes, size_t len, ch return false; } - if (len > KEYSTORE_MAX_SEED_LENGTH) { - return false; - } + /* if (len > KEYSTORE_MAX_SEED_LENGTH) { */ + /* return false; */ + /* } */ char* mnemonic = NULL; if (bip39_mnemonic_from_bytes(NULL, bytes, len, &mnemonic) != WALLY_OK) { return false; @@ -590,6 +590,11 @@ bool keystore_bip39_mnemonic_to_seed(const char* mnemonic, uint8_t* seed_out, si return bip39_mnemonic_to_bytes(NULL, mnemonic, seed_out, 32, seed_len_out) == WALLY_OK; } +bool keystore_bip39_mnemonic_to_bytes(const char* mnemonic, uint8_t* bytes_out, size_t bytes_len, size_t* bytes_len_out) +{ + return bip39_mnemonic_to_bytes(NULL, mnemonic, bytes_out, bytes_len, bytes_len_out) == WALLY_OK; +} + static bool _get_xprv(const uint32_t* keypath, const size_t keypath_len, struct ext_key* xprv_out) { if (keystore_is_locked()) { diff --git a/src/keystore.h b/src/keystore.h index e7d5d1bb8..bdfb8c992 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -147,6 +147,15 @@ USE_RESULT bool keystore_bip39_mnemonic_to_seed( uint8_t* seed_out, size_t* seed_len_out); +/** + * Turn a bip39 mnemonic into a byte array. Make sure to use UTIL_CLEANUP_32 to destroy it. + * @param[in] mnemonic 12/18/24 word bip39 mnemonic + * @param[in] bytes_len size of the bytes array + * @param[out] bytes_out + * @param[out] bytes_len_out will be the size of the seed + */ +USE_RESULT bool keystore_bip39_mnemonic_to_bytes(const char* mnemonic, uint8_t* bytes_out, size_t bytes_len, size_t* bytes_len_out); + /** * Can be used only if the keystore is unlocked. Returns the derived xpub, * using bip32 derivation. Derivation is done from the xprv master, so hardened diff --git a/src/rust/bitbox02-rust/src/hww/api.rs b/src/rust/bitbox02-rust/src/hww/api.rs index 00554f1ec..39e2c200c 100644 --- a/src/rust/bitbox02-rust/src/hww/api.rs +++ b/src/rust/bitbox02-rust/src/hww/api.rs @@ -119,6 +119,7 @@ fn can_call(request: &Request) -> bool { Request::SetPassword(_) => matches!(state, State::Uninitialized | State::Seeded), Request::RestoreBackup(_) => matches!(state, State::Uninitialized | State::Seeded), Request::RestoreFromMnemonic(_) => matches!(state, State::Uninitialized | State::Seeded), + Request::RestoreFromShamir(_) => matches!(state, State::Uninitialized | State::Seeded), Request::CreateBackup(_) => matches!(state, State::Seeded | State::Initialized), Request::ShowMnemonic(_) => matches!(state, State::Seeded | State::Initialized), Request::ShowShamir(_) => matches!(state, State::Seeded | State::Initialized), @@ -163,6 +164,7 @@ async fn process_api(request: &Request) -> Result { Request::RestoreFromMnemonic(ref request) => restore::from_mnemonic(request).await, Request::ElectrumEncryptionKey(ref request) => electrum::process(request).await, Request::ShowShamir(_) => show_shamir::process().await, + Request::RestoreFromShamir(ref request) => restore::from_shamir(request).await, #[cfg(feature = "app-ethereum")] Request::Eth(pb::EthRequest { diff --git a/src/rust/bitbox02-rust/src/hww/api/restore.rs b/src/rust/bitbox02-rust/src/hww/api/restore.rs index c0a43f42f..8d333d4df 100644 --- a/src/rust/bitbox02-rust/src/hww/api/restore.rs +++ b/src/rust/bitbox02-rust/src/hww/api/restore.rs @@ -14,6 +14,7 @@ use super::Error; use crate::pb; +use alloc::vec::Vec; use pb::response::Response; @@ -150,3 +151,95 @@ pub async fn from_mnemonic( unlock::unlock_bip39().await; Ok(Response::Success(pb::Success {})) } + +pub async fn from_shamir( + #[cfg_attr(not(feature = "app-u2f"), allow(unused_variables))] &pb::RestoreFromShamirRequest { + timestamp, + timezone_offset, + }: &pb::RestoreFromShamirRequest, +) -> Result { + #[cfg(feature = "app-u2f")] + { + let datetime_string = bitbox02::format_datetime(timestamp, timezone_offset, false) + .map_err(|_| Error::InvalidInput)?; + confirm::confirm(&confirm::Params { + title: "Is now?", + body: &datetime_string, + accept_is_nextarrow: true, + ..Default::default() + }) + .await?; + } + + let mnemonics = mnemonic::get_shamir().await?; + let mut shares: Vec = Vec::new(); + for mnemonic in mnemonics { + let bytes = match bitbox02::keystore::bip39_mnemonic_to_bytes(&mnemonic, 36) { + Ok(bytes) => bytes, + Err(()) => { + status::status("Recovery words\ninvalid", false).await; + return Err(Error::Generic); + } + }; + let s = sharks::Share::try_from(&bytes[3..]).unwrap(); + shares.push(s); + // let share = Vec::from(&s); + // bitbox02::print_stdout(&format!("share: {}, len: {}\n", hex::encode(share.clone()), share.len())); + } + + let sharks = sharks::Sharks(2); + let seed = match sharks.recover(shares.as_slice()) { + Ok(seed) => seed, + Err(err_str) => { + status::status("Recovery words\ninvalid", false).await; + bitbox02::print_stdout(format!("Err: {}\n", err_str).as_str()); + return Err(Error::Generic); + } + }; + + status::status("Recovery words\nvalid", true).await; + + bitbox02::print_stdout(&format!( + "seed: {}, len: {}\n", + hex::encode(seed.clone()), + seed.len() + )); + // let mnemonic_sentence = bitbox02::keystore::get_bip39_mnemonic_from_bytes(seed.as_ptr(), seed.len())?; + // bitbox02::print_stdout(format!("mnemonic: {}\n", mnemonic_sentence.as_str()).as_str()); + + // If entering password fails (repeat password does not match the first), we don't want to abort + // the process immediately. We break out only if the user confirms. + let password = loop { + match password::enter_twice().await { + Err(password::EnterTwiceError::DoNotMatch) => { + confirm::confirm(&confirm::Params { + title: "", + body: "Passwords\ndo not match.\nTry again?", + ..Default::default() + }) + .await?; + } + Err(password::EnterTwiceError::Cancelled) => return Err(Error::UserAbort), + Ok(password) => break password, + } + }; + + if let Err(err) = bitbox02::keystore::encrypt_and_store_seed(seed.as_slice(), &password) { + status::status(&format!("Could not\nrestore backup\n{:?}", err), false).await; + return Err(Error::Generic); + }; + + #[cfg(feature = "app-u2f")] + { + // Ignore error - the U2f counter not being set can lead to problems with U2F, but it should + // not fail the recovery, so the user can access their coins. + let _ = bitbox02::securechip::u2f_counter_set(timestamp); + } + + bitbox02::memory::set_initialized().or(Err(Error::Memory))?; + + // This should never fail. + bitbox02::keystore::unlock(&password).expect("restore_from_mnemonic: unlock failed"); + unlock::unlock_bip39().await; + Ok(Response::Success(pb::Success {})) +} diff --git a/src/rust/bitbox02-rust/src/hww/api/show_shamir.rs b/src/rust/bitbox02-rust/src/hww/api/show_shamir.rs index 3b1162304..219d74344 100644 --- a/src/rust/bitbox02-rust/src/hww/api/show_shamir.rs +++ b/src/rust/bitbox02-rust/src/hww/api/show_shamir.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Shift Crypto AG +// Copyright 2024 Shift Crypto AG // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,45 +22,27 @@ use pb::response::Response; use crate::workflow::{mnemonic, status, unlock}; use bitbox02::keystore; -use sharks::{ Sharks, Share }; use rand_chacha::rand_core::SeedableRng; +use sharks::{Share, Sharks}; /// Handle the ShowShamir API call. This shows the seed shards encoded as -/// 12/18/24 BIP39 English words. Afterwards, for each word, the user +/// 15/27 BIP39 English words. Afterwards, for each word, the user /// is asked to pick the right word among 5 words, to check if they /// wrote it down correctly. pub async fn process() -> Result { if bitbox02::memory::is_initialized() { unlock::unlock_keystore("Unlock device", unlock::CanCancel::Yes).await?; } - // Set a minimum threshold of 10 shares - let sharks = Sharks(3); + // Set a minimum threshold of 2 shares + const SHARES_THRESHOLD: u8 = 2; + const SHARES_MAX: usize = 3; + let sharks = Sharks(SHARES_THRESHOLD); - // // Obtain an iterator over the shares for secret [1, 2, 3, 4] - // // TODO: use RNG from SE? + // FIXME: seed RNG from SE? let mut rng = rand_chacha::ChaCha8Rng::from_seed([0x90; 32]); - let seed = bitbox02::keystore::copy_seed()?; let dealer = sharks.dealer_rng(&seed, &mut rng); - // let dealer = sharks.dealer_rng(&[1,2,3,4], &mut rng); - // Get 3 shares - let mut shares: Vec = dealer.take(3).collect(); - for s in shares { - - // shares.remove(1); - // shares.remove(0); - // Recover the original secret! - // bitbox02::print_stdout("Recovering...\n"); - // let secret = sharks.recover(shares.as_slice()); - // match secret { - // Err(e) => bitbox02::print_stdout(&format!("Error {}\n", e)), - // Ok(_) => bitbox02::print_stdout("***test ok\n"), - // } - // assert_eq!(*secret.unwrap(), *seed); - let mnemonic_sentence = keystore::get_bip39_mnemonic_from_bytes(Vec::from(&s).as_ptr(), seed.len())?; - - // let mnemonic_sentence = keystore::get_bip39_mnemonic()?; - + // bitbox02::print_stdout(&format!("seed: {}, len: {}\n", hex::encode(seed.clone()), seed.len())); confirm::confirm(&confirm::Params { title: "Warning", body: "DO NOT share your\nrecovery words with\nanyone!", @@ -69,19 +51,29 @@ pub async fn process() -> Result { }) .await?; - confirm::confirm(&confirm::Params { - title: "Recovery\nwords", - body: "Please write down\nthe following words", - accept_is_nextarrow: true, - ..Default::default() - }) - .await?; - - let words: Vec<&str> = mnemonic_sentence.split(' ').collect(); + // Get 3 shares + let shares: Vec = dealer.take(SHARES_MAX).collect(); + for (i, s) in shares.iter().enumerate() { + let share_slice = Vec::from(s); + // Sharks add a single byte to enumerate the shard. We add three bytes in front of it to + // get an additional 4 bytes to the seed and be compliant with BIP39. + let mut share_extended = vec![0, 0, 0]; + share_extended.extend_from_slice(&share_slice); + // bitbox02::print_stdout(&format!("Share: {}, len: {}\n", hex::encode(share_extended.clone()), share_extended.len())); + let mnemonic_sentence = keystore::get_bip39_mnemonic_from_bytes(share_extended)?; - mnemonic::show_and_confirm_mnemonic(&words).await?; + confirm::confirm(&confirm::Params { + title: &format!("Recovery\nwords {}/{}", i + 1, SHARES_MAX), + body: "Please write down\nthe following words", + accept_is_nextarrow: true, + ..Default::default() + }) + .await?; + let words: Vec<&str> = mnemonic_sentence.split(' ').collect(); + mnemonic::show_and_confirm_mnemonic(&words).await?; } + bitbox02::memory::set_initialized().or(Err(Error::Memory))?; status::status("Backup created", true).await; diff --git a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs index 177d4d20a..174c82df5 100644 --- a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs +++ b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs @@ -1627,7 +1627,12 @@ pub struct SetMnemonicPassphraseEnabledRequest { pub struct ShowShamirRequest {} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct RestoreFromShamirRequest {} +pub struct RestoreFromShamirRequest { + #[prost(uint32, tag = "1")] + pub timestamp: u32, + #[prost(int32, tag = "2")] + pub timezone_offset: i32, +} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct RebootRequest { @@ -1702,7 +1707,7 @@ pub struct Success {} pub struct Request { #[prost( oneof = "request::Request", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30" )] pub request: ::core::option::Option, } @@ -1767,6 +1772,8 @@ pub mod request { Bip85(super::Bip85Request), #[prost(message, tag = "29")] ShowShamir(super::ShowShamirRequest), + #[prost(message, tag = "30")] + RestoreFromShamir(super::RestoreFromShamirRequest), } } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/src/rust/bitbox02-rust/src/workflow/mnemonic.rs b/src/rust/bitbox02-rust/src/workflow/mnemonic.rs index 075b28384..c4c981fe3 100644 --- a/src/rust/bitbox02-rust/src/workflow/mnemonic.rs +++ b/src/rust/bitbox02-rust/src/workflow/mnemonic.rs @@ -289,14 +289,7 @@ async fn get_12th_18th_word( } } -/// Retrieve a BIP39 mnemonic sentence of 12, 18 or 24 words from the user. -pub async fn get() -> Result, CancelError> { - let num_words: usize = match choose("How many words?", "12", "18", "24").await { - TrinaryChoice::TRINARY_CHOICE_LEFT => 12, - TrinaryChoice::TRINARY_CHOICE_MIDDLE => 18, - TrinaryChoice::TRINARY_CHOICE_RIGHT => 24, - }; - +async fn get_mnemonic_phrase(num_words: usize) -> Result, CancelError> { status(&format!("Enter {} words", num_words), true).await; // Provide all bip39 words to restrict the keyboard entry. @@ -403,6 +396,37 @@ pub async fn get() -> Result, CancelError> { )) } + +/// Retrieve a BIP39 mnemonic sentence of 12, 18 or 24 words from the user. +pub async fn get() -> Result, CancelError> { + let num_words: usize = match choose("How many words?", "12", "18", "24").await { + TrinaryChoice::TRINARY_CHOICE_LEFT => 12, + TrinaryChoice::TRINARY_CHOICE_MIDDLE => 18, + TrinaryChoice::TRINARY_CHOICE_RIGHT => 24, + }; + get_mnemonic_phrase(num_words); +} +/// +/// Retrieve a BIP39 mnemonic sentence of 12, 18 or 24 words from the user. +pub async fn get_shamir() -> Result>, CancelError> { + let num_words: usize = match choose("How many words?", "15", "21", "27").await { + TrinaryChoice::TRINARY_CHOICE_LEFT => 15, + TrinaryChoice::TRINARY_CHOICE_MIDDLE => 21, + TrinaryChoice::TRINARY_CHOICE_RIGHT => 27, + }; + + //FIXME have a proper constant + let num_shards: usize = 2; + let result: Vec> = Vec::new(); + for s in 0..num_shards { + match get_mnemonic_phrase(num_words) { + OK(shard) => result.push(shard), + Err(e) => return e, + } + } + Ok(result) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs b/src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs index c435daeb8..28a6b3d57 100644 --- a/src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs +++ b/src/rust/bitbox02-rust/src/workflow/mnemonic_c_unit_tests.rs @@ -16,6 +16,7 @@ pub use super::cancel::Error as CancelError; use alloc::string::String; use alloc::string::ToString; +use alloc::vec::Vec; pub async fn show_and_confirm_mnemonic(words: &[&str]) -> Result<(), CancelError> { for word in words.iter() { @@ -33,3 +34,24 @@ pub async fn get() -> Result, CancelError> { Ok(zeroize::Zeroizing::new(words.to_string())) } + +pub async fn get_shamir() -> Result>, CancelError> { + let mnemonics:Vec<&str> = [ + // "abandon abandon able farm crazy beauty install coral elder heavy wage love recipe aspect draft lonely strong garment penalty material surge woman such peanut donkey adjust amount", + "abandon abandon above venue fix program swift believe fence waste flavor profit bottom ostrich include scale between able focus bus approve note mercy guitar stay flee faith", + "abandon abandon absorb naive illegal shiver myself warm forest kitten divide radar snack hazard glide accident patch sniff snow kitchen album couch night forget point size pull", + + // 15 words options for the main mnemonic "spawn nest ability mammal beyond stay wish dragon retreat calm index trap" + // "abandon abandon about script abandon horror dynamic sight pond invest toddler hidden north awful tattoo", + // "abandon abandon absent genius hello sting tuna uncover remain trial evil dynamic distance license upper", + // "abandon abandon abstract ability duck return physical august roof gospel click garden trophy injury wisdom" + ].to_vec(); + let mut result: Vec> = Vec::new(); + bitbox02::println_stdout("Restored from recovery words below:"); + for mnemonic in mnemonics { + bitbox02::println_stdout(mnemonic); + result.push(zeroize::Zeroizing::new(mnemonic.to_string())); + } + + Ok(result) +} diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs index a01eb02ee..141bd0fb2 100644 --- a/src/rust/bitbox02-sys/build.rs +++ b/src/rust/bitbox02-sys/build.rs @@ -61,6 +61,7 @@ const ALLOWLIST_FNS: &[&str] = &[ "delay_us", "empty_create", "keystore_bip39_mnemonic_to_seed", + "keystore_bip39_mnemonic_to_bytes", "keystore_bip85_bip39", "keystore_bip85_ln", "keystore_copy_seed", diff --git a/src/rust/bitbox02/src/keystore.rs b/src/rust/bitbox02/src/keystore.rs index 6fcfa1cfa..15759a9a3 100644 --- a/src/rust/bitbox02/src/keystore.rs +++ b/src/rust/bitbox02/src/keystore.rs @@ -132,15 +132,12 @@ pub fn get_bip39_mnemonic() -> Result, ()> { } } -pub fn get_bip39_mnemonic_from_bytes( - bytes: *const u8, - len: usize, -) -> Result, ()> { +pub fn get_bip39_mnemonic_from_bytes(bytes: Vec) -> Result, ()> { let mut mnemonic = zeroize::Zeroizing::new([0u8; 256]); match unsafe { bitbox02_sys::keystore_get_bip39_mnemonic_from_bytes( - bytes, - len, + bytes.as_ptr(), + bytes.len(), mnemonic.as_mut_ptr(), mnemonic.len() as _, ) @@ -326,6 +323,26 @@ pub fn bip39_mnemonic_to_seed(mnemonic: &str) -> Result Result>, ()> { + let mnemonic = zeroize::Zeroizing::new(crate::util::str_to_cstr_vec(mnemonic)?); + let mut seed = zeroize::Zeroizing::new(vec![0u8; bytes_len]); + let mut seed_len: usize = 0; + match unsafe { + bitbox02_sys::keystore_bip39_mnemonic_to_bytes( + mnemonic.as_ptr(), + seed.as_mut_ptr(), + bytes_len, + &mut seed_len, + ) + } { + true => Ok(zeroize::Zeroizing::new(seed[..seed_len].to_vec())), + false => Err(()), + } +} + pub fn encrypt_and_store_seed(seed: &[u8], password: &SafeInputString) -> Result<(), Error> { match unsafe { bitbox02_sys::keystore_encrypt_and_store_seed(seed.as_ptr(), seed.len(), password.as_cstr())