What changed, and why it matters
This commit rewrites how the BitBox02 hardware wallet performs a key-derivation operation inside its secure chip (OPTIGA). Previously the operation was synchronous and wrapped in C; now it is asynchronous and called from Rust through the existing async command bridge. The change touches many files because every caller of the KDF/keystore functions had to be updated to `await` the result. The diff itself is a large refactoring with no obvious security bug, but it introduces new async machinery around a sensitive cryptographic operation, so it warrants careful review for memory-safety and concurrency issues.
Treat this as a high-priority architectural change requiring focused review of the new async KDF path: verify that the static `INPUT`/`MAC` buffers cannot be re-entered concurrently, that zeroization covers all error and cancellation paths, that the Rust future cannot be dropped while OPTIGA still holds the raw pointers, and that the async command bridge correctly serializes OPTIGA commands. Regression tests should cover KDF failure, cancellation, and concurrent command scenarios.
Security signals we found
Sensitive operation (KDF) moved from synchronous C wrapper to async Rust bridge
Raw pointers to static buffers passed to OPTIGA library during async operation
Static mutable buffers used across await points; zeroization added on error paths
Large blast radius: keystore, signing, address derivation, backup/restore all made async
No explicit security bug visible in the diff, but async state machine and static buffers are a traditional source of use-after-free or lifetime bugs
Old `optiga_kdf_external` removed; new path uses `optiga_crypt_hmac` with OID_HMAC
Evidence from the diff
The patch exposes optiga_crypt_t and optiga_crypt_hmac to Rust, adds an async crypt_hmac helper in bitbox-securechip/src/optiga/ops.rs, and changes SecureChip::kdf from a synchronous function to an async fn. The KDF implementation now uses optiga_crypt_hmac with OID_HMAC instead of the old optiga_kdf_external C wrapper. Because the OPTIGA library keeps raw pointers to input/output buffers until an async callback fires, the code uses static StaticBytes buffers and zeroizes them on both success and error. All higher-level Rust callers (keystore, Bitcoin/Cardano/Ethereum APIs, backup/restore, etc.) are updated to .await the now-async KDF and keystore functions. The change is architectural, not a direct vulnerability fix.
Changed components
src/optiga/optiga.csrc/optiga/optiga.hsrc/rust/bitbox-hal/src/securechip.rssrc/rust/bitbox-securechip/src/optiga.rssrc/rust/bitbox-securechip/src/optiga/ops.rssrc/rust/bitbox02-rust/src/keystore.rssrc/rust/bitbox02-rust/src/hww/api/bitcoin/*src/rust/bitbox02-rust/src/hww/api/cardano/*src/rust/bitbox02-rust/src/hww/api/ethereum/*src/rust/bitbox02-rust/src/hww/api/backup.rssrc/rust/bitbox02-rust/src/hww/api/restore.rssrc/rust/bitbox02-rust/src/hww/api/set_password.rssrc/rust/bitbox02-rust/src/hww/api/change_password.rssrc/rust/bitbox02-rust/src/hww/api/show_mnemonic.rssrc/rust/bitbox02-rust/src/hww/api/bip85.rssrc/rust/bitbox02-rust/src/hww/api/payment_request.rsInspect captured patch +1089 / −533
diff --git a/src/optiga/optiga.c b/src/optiga/optiga.c
index 18e051d..e4e1923 100644
--- a/src/optiga/optiga.c
+++ b/src/optiga/optiga.c
@@ -1850,6 +1850,11 @@ optiga_util_t* optiga_util_instance(void)
return _util;
}
+optiga_crypt_t* optiga_crypt_instance(void)
+{
+ return _crypt;
+}
+
// rand_out must be 32 bytes
int optiga_random(uint8_t* rand_out)
{
diff --git a/src/optiga/optiga.h b/src/optiga/optiga.h
index 5928146..90f0ad9 100644
--- a/src/optiga/optiga.h
+++ b/src/optiga/optiga.h
@@ -13,6 +13,7 @@
#include <stdint.h>
typedef struct optiga_util optiga_util_t;
+typedef struct optiga_crypt optiga_crypt_t;
// Keep in sync with MAX_UNLOCK_ATTEMPTS in keystore.rs.
#ifndef MAX_UNLOCK_ATTEMPTS
@@ -104,6 +105,7 @@ USE_RESULT bool optiga_reset_keys(void);
USE_RESULT bool optiga_gen_attestation_key(uint8_t* pubkey_out);
USE_RESULT bool optiga_attestation_sign(const uint8_t* challenge, uint8_t* signature_out);
USE_RESULT optiga_util_t* optiga_util_instance(void);
+USE_RESULT optiga_crypt_t* optiga_crypt_instance(void);
USE_RESULT int optiga_random(uint8_t* rand_out);
#if APP_U2F == 1 || FACTORYSETUP == 1
USE_RESULT bool optiga_u2f_counter_set(uint32_t counter);
diff --git a/src/rust/bitbox-hal/src/securechip.rs b/src/rust/bitbox-hal/src/securechip.rs
index 8c13822..e5ed864 100644
--- a/src/rust/bitbox-hal/src/securechip.rs
+++ b/src/rust/bitbox-hal/src/securechip.rs
@@ -78,7 +78,7 @@ pub trait SecureChip {
/// This must not increment a monotonic counter.
///
/// `msg` must be 32 bytes long.
- fn kdf(&mut self, msg: &[u8; 32]) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error>;
+ async fn kdf(&mut self, msg: &[u8; 32]) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error>;
/// Signs a 32-byte attestation challenge and writes the raw 64-byte P-256 signature to
/// `signature`.
diff --git a/src/rust/bitbox-platform-host/src/securechip.rs b/src/rust/bitbox-platform-host/src/securechip.rs
index 7c06d8a..a785d25 100644
--- a/src/rust/bitbox-platform-host/src/securechip.rs
+++ b/src/rust/bitbox-platform-host/src/securechip.rs
@@ -119,7 +119,7 @@ impl bitbox_hal::SecureChip for FakeSecureChip {
))
}
- fn kdf(&mut self, msg: &[u8; 32]) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
+ async fn kdf(&mut self, msg: &[u8; 32]) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
self.event_counter += 1;
use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
diff --git a/src/rust/bitbox-securechip-sys/build.rs b/src/rust/bitbox-securechip-sys/build.rs
index 273270c..af5bb7f 100644
--- a/src/rust/bitbox-securechip-sys/build.rs
+++ b/src/rust/bitbox-securechip-sys/build.rs
@@ -6,6 +6,8 @@ use std::path::PathBuf;
use std::process::{Command, Output};
const ALLOWLIST_TYPES: &[&str] = &[
+ "optiga_crypt_t",
+ "optiga_hmac_type_t",
"optiga_lib_status_t",
"optiga_util_t",
"securechip_error_t",
@@ -28,9 +30,10 @@ const ALLOWLIST_FNS: &[&str] = &[
"atecc_u2f_counter_inc",
"atecc_u2f_counter_set",
"optiga_attestation_sign",
+ "optiga_crypt_hmac",
+ "optiga_crypt_instance",
"optiga_gen_attestation_key",
"optiga_init_new_password",
- "optiga_kdf_external",
"optiga_ops_get_status",
"optiga_ops_set_status_busy",
"optiga_random",
@@ -47,12 +50,14 @@ const ALLOWLIST_VARS: &[&str] = &[
"ARBITRARY_DATA_OBJECT_TYPE_3_MAX_SIZE",
"MONOTONIC_COUNTER_MAX_USE",
"OID_COUNTER",
+ "OID_HMAC",
"OPTIGA_LIB_BUSY",
"OPTIGA_LIB_SUCCESS",
"OPTIGA_UTIL_SUCCESS",
];
const RUSTIFIED_ENUMS: &[&str] = &[
+ "optiga_hmac_type",
"securechip_password_stretch_algo_t",
"securechip_error_t",
"securechip_model_t",
diff --git a/src/rust/bitbox-securechip/src/optiga.rs b/src/rust/bitbox-securechip/src/optiga.rs
index 5f3d567..8d658b8 100644
--- a/src/rust/bitbox-securechip/src/optiga.rs
+++ b/src/rust/bitbox-securechip/src/optiga.rs
@@ -8,6 +8,10 @@ mod ops;
const OID_COUNTER: u16 = bitbox_securechip_sys::OID_COUNTER as u16;
const MONOTONIC_COUNTER_MAX_USE: u32 = bitbox_securechip_sys::MONOTONIC_COUNTER_MAX_USE;
+const OID_HMAC: u16 = bitbox_securechip_sys::OID_HMAC as u16;
+const KDF_LEN: usize = 32;
+const OPTIGA_HMAC_SHA_256: bitbox_securechip_sys::optiga_hmac_type_t =
+ bitbox_securechip_sys::optiga_hmac_type::OPTIGA_HMAC_SHA_256;
pub fn attestation_sign(challenge: &[u8; 32], signature: &mut [u8; 64]) -> Result<(), ()> {
match unsafe {
@@ -27,6 +31,7 @@ pub fn random() -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
Err(Error::from_status(status))
}
}
+
pub async fn monotonic_increments_remaining() -> Result<u32, ()> {
let mut counter_buf = [0; 4];
ops::util_read_data(OID_COUNTER, 0, &mut counter_buf)
@@ -88,16 +93,10 @@ pub fn stretch_password(
}
}
-pub fn kdf(msg: &[u8; 32]) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
+pub async fn kdf(msg: &[u8; KDF_LEN]) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
let mut result = Box::new(Zeroizing::new([0u8; 32]));
- let status = unsafe {
- bitbox_securechip_sys::optiga_kdf_external(msg.as_ptr(), msg.len(), result.as_mut_ptr())
- };
- if status == 0 {
- Ok(result)
- } else {
- Err(Error::from_status(status))
- }
+ ops::crypt_hmac(OPTIGA_HMAC_SHA_256, OID_HMAC, msg, result.as_mut()).await?;
+ Ok(result)
}
#[cfg(feature = "app-u2f")]
diff --git a/src/rust/bitbox-securechip/src/optiga/ops.rs b/src/rust/bitbox-securechip/src/optiga/ops.rs
index 6c9e7f1..2be864b 100644
--- a/src/rust/bitbox-securechip/src/optiga/ops.rs
+++ b/src/rust/bitbox-securechip/src/optiga/ops.rs
@@ -82,6 +82,12 @@ impl<const N: usize> StaticBytes<N> {
}
}
+ fn copy_from_slice(&self, data: &[u8]) {
+ unsafe {
+ core::ptr::copy_nonoverlapping(data.as_ptr(), self.as_mut_ptr(), data.len());
+ }
+ }
+
fn copy_to_slice(&self, out: &mut [u8]) {
unsafe {
core::ptr::copy_nonoverlapping(self.as_mut_ptr(), out.as_mut_ptr(), out.len());
@@ -290,3 +296,54 @@ pub(super) async fn util_read_data(oid: u16, offset: u16, out: &mut [u8]) -> Res
BUF.zeroize();
Ok(())
}
+
+pub(super) async fn crypt_hmac(
+ hmac_type: bitbox_securechip_sys::optiga_hmac_type_t,
+ secret: u16,
+ msg: &[u8; super::KDF_LEN],
+ mac_out: &mut [u8; super::KDF_LEN],
+) -> Result<(), Error> {
+ // Static because the Optiga library keeps raw pointers to the input, output and length until
+ // the async callback completes, and the Rust future may be dropped before that happens.
+ static INPUT: StaticBytes<{ super::KDF_LEN }> = StaticBytes::const_init();
+ static MAC: StaticBytes<{ super::KDF_LEN }> = StaticBytes::const_init();
+ static MAC_LEN: GroundedCell<u32> = GroundedCell::const_init();
+
+ let crypt = unsafe { bitbox_securechip_sys::optiga_crypt_instance() };
+
+ INPUT.copy_from_slice(msg);
+ MAC.clear();
+ unsafe {
+ MAC_LEN.get().write(super::KDF_LEN as u32);
+ }
+ let result = run_async_op(|| unsafe {
+ bitbox_securechip_sys::optiga_crypt_hmac(
+ crypt,
+ hmac_type,
+ secret,
+ INPUT.as_mut_ptr(),
+ super::KDF_LEN as u32,
+ MAC.as_mut_ptr(),
+ MAC_LEN.get(),
+ )
+ })
+ .await
+ .map_err(|status| Error::from_status(status as i32));
+ if let Err(err) = result {
+ INPUT.zeroize();
+ MAC.zeroize();
+ return Err(err);
+ }
+
+ if unsafe { MAC_LEN.get().read() as usize } != super::KDF_LEN {
+ INPUT.zeroize();
+ MAC.zeroize();
+ return Err(Error::SecureChip(
+ SecureChipError::SC_OPTIGA_ERR_UNEXPECTED_LEN,
+ ));
+ }
+ MAC.copy_to_slice(mac_out);
+ INPUT.zeroize();
+ MAC.zeroize();
+ Ok(())
+}
diff --git a/src/rust/bitbox02-rust-c/src/firmware_c_api.rs b/src/rust/bitbox02-rust-c/src/firmware_c_api.rs
index b89e3ef..ffee389 100644
--- a/src/rust/bitbox02-rust-c/src/firmware_c_api.rs
+++ b/src/rust/bitbox02-rust-c/src/firmware_c_api.rs
@@ -97,7 +97,9 @@ pub extern "C" fn rust_memory_get_io_protection_key(mut key_out: BytesMut) {
#[cfg(feature = "app-u2f")]
#[unsafe(no_mangle)]
pub extern "C" fn rust_keystore_get_u2f_seed(mut seed_out: util::bytes::BytesMut) -> bool {
- match bitbox02_rust::keystore::get_u2f_seed(&mut crate::HalImpl::new()) {
+ match util::bb02_async::block_on(bitbox02_rust::keystore::get_u2f_seed(
+ &mut crate::HalImpl::new(),
+ )) {
Ok(seed) => {
seed_out.as_mut().copy_from_slice(&seed);
true
diff --git a/src/rust/bitbox02-rust/src/hww.rs b/src/rust/bitbox02-rust/src/hww.rs
index 8b9c19e..9476628 100644
--- a/src/rust/bitbox02-rust/src/hww.rs
+++ b/src/rust/bitbox02-rust/src/hww.rs
@@ -468,8 +468,8 @@ mod tests {
}
/// Test creating a seed, backing it up on SD, checking the backup, and restoring from the that backup.
- #[test]
- fn test_backup_create_check_list_restore() {
+ #[async_test::test]
+ async fn test_backup_create_check_list_restore() {
// Test everything with a 32 and 16 byte seed (determined by the host entropy when creating the seed).
for host_entropy in &[
&b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"[..],
@@ -536,7 +536,7 @@ mod tests {
]
);
- let seed = crate::keystore::copy_seed(&mut mock_hal).unwrap();
+ let seed = crate::keystore::copy_seed(&mut mock_hal).await.unwrap();
assert_eq!(seed.len(), host_entropy.len());
mock_hal.ui = crate::hal::testing::TestingUi::new();
assert!(matches!(
@@ -708,7 +708,10 @@ mod tests {
);
// Restored seed is the same as the seed that was backed up.
- assert_eq!(seed, crate::keystore::copy_seed(&mut mock_hal).unwrap());
+ assert_eq!(
+ seed,
+ crate::keystore::copy_seed(&mut mock_hal).await.unwrap()
+ );
}
}
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/backup.rs b/src/rust/bitbox02-rust/src/hww/api/backup.rs
index db4c43e..c2b82da 100644
--- a/src/rust/bitbox02-rust/src/hww/api/backup.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/backup.rs
@@ -19,7 +19,7 @@ pub async fn check(
return Err(Error::InvalidInput);
}
- let seed = crate::keystore::copy_seed(hal)?;
+ let seed = crate::keystore::copy_seed(hal).await?;
let id = backup::id(&seed);
let (backup_data, metadata) = backup::load(hal, &id).await?;
if seed.as_slice() != backup_data.get_seed() {
@@ -91,7 +91,7 @@ pub async fn create(
let seed = if is_initialized {
unlock::unlock_keystore(hal, "Unlock device", CanCancel::Yes).await?
} else {
- let seed = crate::keystore::copy_seed(hal)?;
+ let seed = crate::keystore::copy_seed(hal).await?;
// Yield now to give executor a chance to process USB/BLE communication, as copy_seed() causes
// some delay.
futures_lite::future::yield_now().await;
@@ -216,7 +216,9 @@ mod tests {
let mut mock_hal = TestingHal::new();
let seed = hex::decode("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044")
.unwrap();
- crate::keystore::encrypt_and_store_seed(&mut mock_hal, &seed, "password").unwrap();
+ crate::keystore::encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .unwrap();
mock_hal.memory.set_initialized().unwrap();
mock_hal.sd.inserted = Some(true);
diff --git a/src/rust/bitbox02-rust/src/hww/api/bip85.rs b/src/rust/bitbox02-rust/src/hww/api/bip85.rs
index 0a9e554..8661ba9 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bip85.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bip85.rs
@@ -108,7 +108,7 @@ async fn process_bip39(hal: &mut impl crate::hal::Hal) -> Result<(), Error> {
})
.await?;
- let mnemonic = keystore::bip85_bip39(hal, num_words, index)?;
+ let mnemonic = keystore::bip85_bip39(hal, num_words, index).await?;
let words: Vec<&str> = mnemonic.split(' ').collect();
{
let crate::hal::HalSubsystems { ui, random, .. } = hal.as_mut();
@@ -142,6 +142,7 @@ async fn process_ln(
.await?;
Ok(keystore::bip85_ln(hal, account_number)
+ .await
.map_err(|_| Error::Generic)?
.to_vec())
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
index 4da97e0..1376498 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
@@ -113,6 +113,7 @@ async fn xpub(
.await?
}
let xpub = keystore::get_xpub_twice(hal, keypath)
+ .await
.or(Err(Error::InvalidInput))?
.serialize_str(xpub_type)?;
if display {
@@ -135,7 +136,7 @@ async fn xpub(
Ok(Response::Pub(pb::PubResponse { r#pub: xpub }))
}
-pub fn derive_address_simple(
+pub async fn derive_address_simple(
hal: &mut impl crate::hal::Hal,
coin: BtcCoin,
simple_type: SimpleType,
@@ -156,7 +157,8 @@ pub fn derive_address_simple(
coin_params,
simple_type,
keypath,
- )?
+ )
+ .await?
.address(coin_params)?)
}
@@ -168,7 +170,7 @@ async fn address_simple(
keypath: &[u32],
display: bool,
) -> Result<Response, Error> {
- let address = derive_address_simple(hal, coin, simple_type, keypath)?;
+ let address = derive_address_simple(hal, coin, simple_type, keypath).await?;
if display {
let address_formatted = util::strings::format_address(&address);
let confirm_params = ConfirmParams {
@@ -194,7 +196,7 @@ pub async fn address_multisig(
keypath::validate_address_policy(keypath, keypath::ReceiveSpend::Receive)
.or(Err(Error::InvalidInput))?;
let account_keypath = &keypath[..keypath.len() - 2];
- multisig::validate(hal, multisig, account_keypath)?;
+ multisig::validate(hal, multisig, account_keypath).await?;
let name = match multisig::get_name(hal, coin, multisig, account_keypath)? {
Some(name) => name,
None => return Err(Error::InvalidInput),
@@ -237,7 +239,7 @@ async fn address_policy(
keypath::validate_address_policy(keypath, keypath::ReceiveSpend::Receive)
.or(Err(Error::InvalidInput))?;
- let parsed = policies::parse(hal, policy, coin)?;
+ let parsed = policies::parse(hal, policy, coin).await?;
let name = parsed.name(hal, coin_params)?.ok_or(Error::InvalidInput)?;
@@ -1108,6 +1110,7 @@ mod tests {
keypath: KEYPATH_ACCOUNT_TESTNET.to_vec(),
xpub: Some(
crate::keystore::get_xpub_once(&mut TestingHal::new(), KEYPATH_ACCOUNT_TESTNET)
+ .await
.unwrap()
.into(),
),
@@ -1117,6 +1120,7 @@ mod tests {
keypath: KEYPATH_ACCOUNT_MAINNET.to_vec(),
xpub: Some(
crate::keystore::get_xpub_once(&mut TestingHal::new(), KEYPATH_ACCOUNT_MAINNET)
+ .await
.unwrap()
.into(),
),
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
index 5d404ff..92d629a 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
@@ -5,6 +5,7 @@ use super::pb;
use crate::xpubcache::Bip32XpubCache;
+use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
@@ -68,7 +69,7 @@ pub struct Payload {
}
impl Payload {
- pub fn from_simple(
+ pub async fn from_simple(
hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
params: &Params,
@@ -77,12 +78,19 @@ impl Payload {
) -> Result<Self, Error> {
match simple_type {
SimpleType::P2wpkh => Ok(Payload {
- data: xpub_cache.get_xpub(hal, keypath)?.pubkey_hash160(),
+ data: xpub_cache.get_xpub(hal, keypath).await?.pubkey_hash160(),
output_type: BtcOutputType::P2wpkh,
}),
SimpleType::P2wpkhP2sh => {
- let payload_p2wpkh =
- Payload::from_simple(hal, xpub_cache, params, SimpleType::P2wpkh, keypath)?;
+ // Box::pin gives the recursive async call a finite size, allowing recursion here.
+ let payload_p2wpkh = Box::pin(Payload::from_simple(
+ hal,
+ xpub_cache,
+ params,
+ SimpleType::P2wpkh,
+ keypath,
+ ))
+ .await?;
let pkscript_p2wpkh = payload_p2wpkh.pk_script(params)?;
Ok(Payload {
data: bitcoin::hashes::hash160::Hash::hash(&pkscript_p2wpkh)
@@ -95,7 +103,8 @@ impl Payload {
if params.taproot_support {
Ok(Payload {
data: xpub_cache
- .get_xpub(hal, keypath)?
+ .get_xpub(hal, keypath)
+ .await?
.schnorr_bip86_pubkey()?
.to_vec(),
output_type: BtcOutputType::P2tr,
@@ -178,16 +187,16 @@ impl Payload {
/// Computes the payload data from a script config. The payload can then be used generate a
/// pkScript or an address.
- pub fn from(
+ pub async fn from(
hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
params: &Params,
keypath: &[u32],
- script_config_account: &ValidatedScriptConfigWithKeypath,
+ script_config_account: &ValidatedScriptConfigWithKeypath<'_>,
) -> Result<Self, Error> {
match &script_config_account.config {
ValidatedScriptConfig::SimpleType(simple_type) => {
- Self::from_simple(hal, xpub_cache, params, *simple_type, keypath)
+ Self::from_simple(hal, xpub_cache, params, *simple_type, keypath).await
}
ValidatedScriptConfig::Multisig { multisig, .. } => Self::from_multisig(
params,
@@ -561,8 +570,8 @@ mod tests {
}
}
- #[test]
- fn test_payload_simple() {
+ #[async_test::test]
+ async fn test_payload_simple() {
mock_unlocked_using_mnemonic(
"sudden tenant fault inject concert weather maid people chunk youth stumble grit",
"",
@@ -578,6 +587,7 @@ mod tests {
SimpleType::P2wpkh,
&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0]
)
+ .await
.unwrap()
.data
.as_slice(),
@@ -593,6 +603,7 @@ mod tests {
SimpleType::P2wpkhP2sh,
&[49 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0]
)
+ .await
.unwrap()
.data
.as_slice(),
@@ -608,6 +619,7 @@ mod tests {
SimpleType::P2tr,
&[86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0]
)
+ .await
.unwrap()
.data
.as_slice(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/multisig.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/multisig.rs
index cd42f7d..089215b 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/multisig.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/multisig.rs
@@ -247,7 +247,7 @@ pub async fn confirm_extended(
/// - no two xpubs are the same.
///
/// keypath: account-level keypath, e.g. m/48'/0'/10'/2'
-pub fn validate(
+pub async fn validate(
hal: &mut impl crate::hal::Hal,
multisig: &Multisig,
keypath: &[u32],
@@ -262,7 +262,9 @@ pub fn validate(
return Err(Error::InvalidInput);
}
- let our_xpub = crate::keystore::get_xpub_once(hal, keypath)?.serialize(None)?;
+ let our_xpub = crate::keystore::get_xpub_once(hal, keypath)
+ .await?
+ .serialize(None)?;
let maybe_our_xpub =
bip32::Xpub::from(&multisig.xpubs[multisig.our_xpub_index as usize]).serialize(None)?;
if our_xpub != maybe_our_xpub {
@@ -560,8 +562,8 @@ mod tests {
}
}
- #[test]
- fn test_validate() {
+ #[async_test::test]
+ async fn test_validate() {
let keypath = &[48 + HARDENED, 1 + HARDENED, 0 + HARDENED, 2 + HARDENED];
let our_xpub_str = "xpub6EMfjyGVUvwhpc3WKN1zXhMFGKJGMaSBPqbja4tbGoYvRBSXeTBCaqrRDjcuGTcaY95JrrAnQvDG3pdQPdtnYUCugjeksHSbyZT7rq38VQF";
let multisig = Multisig {
@@ -579,14 +581,14 @@ mod tests {
// Keystore locked.
crate::keystore::lock();
- assert!(validate(&mut mock_hal, &multisig, keypath).is_err());
+ assert!(validate(&mut mock_hal, &multisig, keypath).await.is_err());
// Ok.
mock_unlocked_using_mnemonic(
"sudden tenant fault inject concert weather maid people chunk youth stumble grit",
"",
);
- assert!(validate(&mut mock_hal, &multisig, keypath).is_ok());
+ assert!(validate(&mut mock_hal, &multisig, keypath).await.is_ok());
// Ok at arbitrary keypath.
assert!(validate(&mut mock_hal,&Multisig {
threshold: 1,
@@ -597,7 +599,7 @@ mod tests {
],
our_xpub_index: 1,
script_type: ScriptType::P2wsh as _,
- }, &[45 + HARDENED]).is_ok());
+ }, &[45 + HARDENED]).await.is_ok());
{
// number of cosigners too large
@@ -621,7 +623,7 @@ mod tests {
"xpub6ECHc4kmTC2tQg2ZoAoazwyag9C4V6yFsZEhjwMJixdVNsUibot6uEvsZY38ZLVqWCtyc9gbzFEwHQLHCT8EiDDKSNNsFAB8NQYRgkiAQwu",
"xpub6F7CaxXzBCtvXwpRi61KYyhBRkgT1856ujHV5AbJK6ySCUYoDruBH6Pnsi6eHkDiuKuAJ2tSc9x3emP7aax9Dc3u7nP7RCQXEjLKihQu6w1",
].iter().map(|s| parse_xpub(s).unwrap()).collect();
- assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).await.is_err());
}
{
@@ -629,21 +631,21 @@ mod tests {
let mut invalid = multisig.clone();
invalid.xpubs = vec![];
- assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).await.is_err());
invalid.our_xpub_index = 0;
invalid.xpubs = vec![parse_xpub(our_xpub_str).unwrap()];
- assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).await.is_err());
}
{
// threshold larger than number of cosigners
let mut invalid = multisig.clone();
invalid.threshold = 3;
- assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).await.is_err());
// threshold zero
invalid.threshold = 0;
- assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).await.is_err());
}
{
@@ -651,7 +653,7 @@ mod tests {
// bounds).
let mut invalid = multisig.clone();
invalid.our_xpub_index = 2;
- assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).await.is_err());
}
{
@@ -659,7 +661,7 @@ mod tests {
let mut invalid = multisig.clone();
invalid.xpubs[1] = parse_xpub("xpub6FNT7x2ZEBMhs4jvZJSEBV2qBCBnRidNsyqe7inT9V2wmEn4sqidTEudB4dVSvEjXz2NytcymwWJb8PPYExRycNf9SH8fAHzPWUsQJAmbR3").unwrap();
- assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).await.is_err());
}
{
@@ -667,7 +669,7 @@ mod tests {
let mut invalid = multisig.clone();
invalid.xpubs[0] = invalid.xpubs[1].clone();
- assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).await.is_err());
}
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs
index 0fe280f..934a3bb 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs
@@ -36,7 +36,7 @@ fn check_enabled(coin: BtcCoin) -> Result<(), Error> {
/// Checks if the key is our key by comparing the root fingerprints
/// and deriving and comparing the xpub at the keypath.
-fn is_our_key(
+async fn is_our_key(
hal: &mut impl crate::hal::Hal,
key: &pb::KeyOriginInfo,
our_root_fingerprint: &[u8],
@@ -48,7 +48,9 @@ fn is_our_key(
xpub: Some(xpub),
..
} if root_fingerprint.as_slice() == our_root_fingerprint => {
- let our_xpub = crate::keystore::get_xpub_once(hal, keypath)?.serialize(None)?;
+ let our_xpub = crate::keystore::get_xpub_once(hal, keypath)
+ .await?
+ .serialize(None)?;
let maybe_our_xpub = bip32::Xpub::from(xpub).serialize(None)?;
Ok(our_xpub == maybe_our_xpub)
}
@@ -557,7 +559,7 @@ impl ParsedPolicy<'_> {
/// This works because all keypaths are distinct per BIP-388, and checked by `validate_keys()`,
/// so they keypath alone is sufficient to figure out if we are using key path or script
/// path, and if the latter, which leaf exactly.
- pub fn taproot_spend_info(
+ pub async fn taproot_spend_info(
&self,
hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
@@ -565,7 +567,7 @@ impl ParsedPolicy<'_> {
) -> Result<TaprootSpendInfo, Error> {
match self.derive_at_keypath(keypath)? {
Descriptor::Tr(tr) => {
- let xpub = xpub_cache.get_xpub(hal, keypath)?;
+ let xpub = xpub_cache.get_xpub(hal, keypath).await?;
let is_keypath_spend =
xpub.public_key() == tr.inner.internal_key().inner.serialize();
@@ -670,7 +672,7 @@ impl ParsedPolicy<'_> {
///
/// The parsed output keeps the key strings as is (e.g. "@0/**"). They will be processed and
/// replaced with actual pubkeys in a later step.
-pub fn parse<'a>(
+pub async fn parse<'a>(
hal: &mut impl crate::hal::Hal,
policy: &'a Policy,
coin: BtcCoin,
@@ -683,11 +685,10 @@ pub fn parse<'a>(
let desc = policy.policy.as_str();
let our_root_fingerprint = crate::keystore::root_fingerprint()?;
- let is_our_key: Vec<bool> = policy
- .keys
- .iter()
- .map(|key| is_our_key(hal, key, &our_root_fingerprint))
- .collect::<Result<Vec<bool>, ()>>()?;
+ let mut is_our_key_result = Vec::with_capacity(policy.keys.len());
+ for key in policy.keys.iter() {
+ is_our_key_result.push(is_our_key(hal, key, &our_root_fingerprint).await?);
+ }
let parsed = match desc.as_bytes() {
// Match wsh(...).
@@ -703,7 +704,7 @@ pub fn parse<'a>(
.map_err(|_| Error::InvalidInput)?;
ParsedPolicy {
policy,
- is_our_key,
+ is_our_key: is_our_key_result.clone(),
descriptor: Descriptor::Wsh(Wsh { miniscript_expr }),
}
}
@@ -717,7 +718,7 @@ pub fn parse<'a>(
ParsedPolicy {
policy,
- is_our_key,
+ is_our_key: is_our_key_result,
descriptor: Descriptor::Tr(Tr { inner: tr }),
}
}
@@ -808,9 +809,10 @@ mod tests {
}
// Creates a policy for one of our own keys at keypath.
- fn make_our_key(keypath: &[u32]) -> pb::KeyOriginInfo {
+ async fn make_our_key(keypath: &[u32]) -> pb::KeyOriginInfo {
let our_xpub =
crate::keystore::get_xpub_once(&mut crate::hal::testing::TestingHal::new(), keypath)
+ .await
.unwrap();
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
@@ -858,8 +860,8 @@ mod tests {
// Tests that iter_pk() iterates the pubkeys from left to right as they appear in the
// descriptor.
- #[test]
- fn test_iter_pk_left_to_right() {
+ #[async_test::test]
+ async fn test_iter_pk_left_to_right() {
mock_unlocked();
struct Test {
policy: &'static str,
@@ -887,7 +889,7 @@ mod tests {
&[
make_key(SOME_XPUB_1),
make_key(SOME_XPUB_2),
- make_our_key(KEYPATH_ACCOUNT),
+ make_our_key(KEYPATH_ACCOUNT).await,
],
);
let pks: Vec<String> = parse(
@@ -895,6 +897,7 @@ mod tests {
&policy,
BtcCoin::Tbtc,
)
+ .await
.unwrap()
.iter_pk()
.collect();
@@ -902,14 +905,18 @@ mod tests {
}
}
- #[test]
- fn test_parse_wsh_miniscript() {
+ #[async_test::test]
+ async fn test_parse_wsh_miniscript() {
let coin = BtcCoin::Tbtc;
let mut mock_hal = crate::hal::testing::TestingHal::new();
- let our_key = make_our_key(KEYPATH_ACCOUNT);
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
// Parse a valid example and check that the keys are collected as is as strings.
let policy = make_policy("wsh(pk(@0/**))", core::slice::from_ref(&our_key));
- match &parse(&mut mock_hal, &policy, coin).unwrap().descriptor {
+ match &parse(&mut mock_hal, &policy, coin)
+ .await
+ .unwrap()
+ .descriptor
+ {
Descriptor::Wsh(Wsh {
miniscript_expr, ..
}) => {
@@ -926,7 +933,11 @@ mod tests {
"wsh(or_b(pk(@0/**),s:pk(@1/**)))",
&[our_key.clone(), make_key(SOME_XPUB_1)],
);
- match &parse(&mut mock_hal, &policy, coin).unwrap().descriptor {
+ match &parse(&mut mock_hal, &policy, coin)
+ .await
+ .unwrap()
+ .descriptor
+ {
Descriptor::Wsh(Wsh {
miniscript_expr, ..
}) => {
@@ -945,6 +956,7 @@ mod tests {
&make_policy("unknown(pk(@0/**))", core::slice::from_ref(&our_key)),
coin
)
+ .await
.unwrap_err(),
Error::InvalidInput,
);
@@ -956,6 +968,7 @@ mod tests {
&make_policy("wsh(unknown(@0/**))", core::slice::from_ref(&our_key)),
coin
)
+ .await
.unwrap_err(),
Error::InvalidInput,
);
@@ -970,16 +983,17 @@ mod tests {
),
coin
)
+ .await
.unwrap_err(),
Error::InvalidInput,
);
}
- #[test]
- fn test_parse() {
+ #[async_test::test]
+ async fn test_parse() {
mock_unlocked();
- let our_key = make_our_key(KEYPATH_ACCOUNT);
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
let coin = BtcCoin::Tbtc;
// All good.
@@ -989,6 +1003,7 @@ mod tests {
&make_policy("wsh(pk(@0/**))", core::slice::from_ref(&our_key)),
coin
)
+ .await
.is_ok()
);
@@ -1006,6 +1021,7 @@ mod tests {
),
coin
)
+ .await
.is_ok()
);
@@ -1016,21 +1032,26 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", core::slice::from_ref(&our_key)),
coin
- ),
+ )
+ .await,
Err(Error::InvalidInput)
));
}
// Too many keys.
- let many_keys: Vec<pb::KeyOriginInfo> = (0..=20)
- .map(|i| make_our_key(&[48 + HARDENED, 1 + HARDENED, i + HARDENED, 3 + HARDENED]))
- .collect();
+ let mut many_keys = Vec::new();
+ for i in 0..=20 {
+ many_keys.push(
+ make_our_key(&[48 + HARDENED, 1 + HARDENED, i + HARDENED, 3 + HARDENED]).await,
+ );
+ }
assert!(matches!(
parse(
&mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", &many_keys),
coin
- ),
+ )
+ .await,
Err(Error::InvalidInput)
));
@@ -1040,7 +1061,8 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", &[make_key(SOME_XPUB_1)]),
coin
- ),
+ )
+ .await,
Err(Error::InvalidInput)
));
@@ -1052,7 +1074,8 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", &[wrong_key]),
coin
- ),
+ )
+ .await,
Err(Error::InvalidInput)
));
@@ -1069,7 +1092,8 @@ mod tests {
]
),
coin
- ),
+ )
+ .await,
Err(Error::InvalidInput)
));
@@ -1089,7 +1113,8 @@ mod tests {
]
),
coin
- ),
+ )
+ .await,
Err(Error::InvalidInput)
));
@@ -1099,7 +1124,8 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", &[our_key.clone(), make_key(SOME_XPUB_1)]),
coin
- ),
+ )
+ .await,
Err(Error::InvalidInput)
));
@@ -1109,35 +1135,48 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@1/**))", core::slice::from_ref(&our_key)),
coin
- ),
+ )
+ .await,
Err(Error::InvalidInput)
));
}
- #[test]
- fn test_parse_check_dups_in_policy_wsh() {
+ #[async_test::test]
+ async fn test_parse_check_dups_in_policy_wsh() {
mock_unlocked();
let coin = BtcCoin::Tbtc;
- let our_key = make_our_key(KEYPATH_ACCOUNT);
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
// Ok, one key.
let pol = make_policy("wsh(pk(@0/**))", core::slice::from_ref(&our_key));
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_ok());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_ok()
+ );
// Ok, two keys.
let pol = make_policy(
"wsh(or_b(pk(@0/**),s:pk(@1/**)))",
&[our_key.clone(), make_key(SOME_XPUB_1)],
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_ok());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_ok()
+ );
// Ok, one key with different derivations
let pol = make_policy(
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<2;3>/*)))",
core::slice::from_ref(&our_key),
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_ok());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_ok()
+ );
// Duplicate path, one time in change, one time in receive. While the keys technically are
// never duplicate in the final miniscript with the pubkeys inserted, we still prohibit it,
@@ -1147,58 +1186,90 @@ mod tests {
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<1;2>/*)))",
core::slice::from_ref(&our_key),
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
// Duplicate key inside policy.
let pol = make_policy(
"wsh(or_b(pk(@0/**),s:pk(@0/**)))",
core::slice::from_ref(&our_key),
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
// Duplicate key inside policy (same change and receive).
let pol = make_policy("wsh(pk(@0/<0;0>/*))", core::slice::from_ref(&our_key));
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
// Duplicate key inside policy, using different notations for the same thing.
let pol = make_policy(
"wsh(or_b(pk(@0/**),s:pk(@0/<0;1>/*)))",
core::slice::from_ref(&our_key),
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
// Duplicate key inside policy, using same receive but different change.
let pol = make_policy(
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<0;2>/*)))",
core::slice::from_ref(&our_key),
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
// Duplicate key inside policy, using same change but different receive.
let pol = make_policy(
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<2;1>/*)))",
core::slice::from_ref(&our_key),
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
}
- #[test]
- fn test_parse_check_dups_in_policy_tr() {
+ #[async_test::test]
+ async fn test_parse_check_dups_in_policy_tr() {
mock_unlocked();
let coin = BtcCoin::Tbtc;
- let our_key = make_our_key(KEYPATH_ACCOUNT);
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
// Ok, only internal key.
let pol = make_policy("tr(@0/**)", core::slice::from_ref(&our_key));
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_ok());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_ok()
+ );
// Ok, one leaf with one key.
let pol = make_policy(
"tr(@0/**,pk(@1/**))",
&[our_key.clone(), make_key(SOME_XPUB_1)],
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_ok());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_ok()
+ );
// Ok, one leaf with two keys.
let pol = make_policy(
@@ -1209,32 +1280,48 @@ mod tests {
make_key(SOME_XPUB_2),
],
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_ok());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_ok()
+ );
// Duplicate keys across internal key and multiple leafs. Technically okay, but prohibited
// by BIP-388.
let pol = make_policy("tr(@0/**,pk(@0/**))", core::slice::from_ref(&our_key));
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
// Duplicate key in one leaf script.
let pol = make_policy(
"tr(@0/**,or_b(pk(@1/**),s:pk(@1/**)))",
&[our_key.clone(), make_key(SOME_XPUB_1)],
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
// Duplicate key inside one leaf script, using same receive but different change.
let pol = make_policy(
"tr(@0/**,or_b(pk(@1/<0;1>/*),s:pk(@1/<0;2>/*)))",
&[our_key.clone(), make_key(SOME_XPUB_1)],
);
- assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
+ assert!(
+ parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin)
+ .await
+ .is_err()
+ );
}
- #[test]
- fn test_get_change_and_address_index() {
+ #[async_test::test]
+ async fn test_get_change_and_address_index() {
mock_unlocked();
- let our_key = make_our_key(KEYPATH_ACCOUNT);
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
let some_key = make_key(SOME_XPUB_1);
assert_eq!(
@@ -1373,14 +1460,14 @@ mod tests {
);
}
- #[test]
- fn test_wsh_witness_script() {
+ #[async_test::test]
+ async fn test_wsh_witness_script() {
mock_unlocked_using_mnemonic(
"sudden tenant fault inject concert weather maid people chunk youth stumble grit",
"",
);
- let our_key = make_our_key(KEYPATH_ACCOUNT);
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
let our_xpub = bip32::Xpub::from(our_key.xpub.as_ref().unwrap());
let some_key = make_key(SOME_XPUB_1);
@@ -1388,12 +1475,19 @@ mod tests {
let address_index = 5;
let coin = BtcCoin::Tbtc;
- let witness_script = |pol: &str, keys: &[pb::KeyOriginInfo], is_change: bool| {
+ async fn witness_script(
+ pol: &str,
+ keys: &[pb::KeyOriginInfo],
+ is_change: bool,
+ address_index: u32,
+ coin: BtcCoin,
+ ) -> String {
let derived = parse(
&mut crate::hal::testing::TestingHal::new(),
&make_policy(pol, keys),
coin,
)
+ .await
.unwrap()
.derive(is_change, address_index)
.unwrap();
@@ -1401,13 +1495,19 @@ mod tests {
Descriptor::Wsh(wsh) => hex::encode(wsh.witness_script()),
_ => panic!("expected wsh"),
}
- };
- let witness_script_at_keypath = |pol: &str, keys: &[pb::KeyOriginInfo], keypath: &[u32]| {
+ }
+ async fn witness_script_at_keypath(
+ pol: &str,
+ keys: &[pb::KeyOriginInfo],
+ keypath: &[u32],
+ coin: BtcCoin,
+ ) -> String {
let derived = parse(
&mut crate::hal::testing::TestingHal::new(),
&make_policy(pol, keys),
coin,
)
+ .await
.unwrap()
.derive_at_keypath(keypath)
.unwrap();
@@ -1415,10 +1515,17 @@ mod tests {
Descriptor::Wsh(wsh) => hex::encode(wsh.witness_script()),
_ => panic!("expected wsh"),
}
- };
+ }
// pk(key) => <key> OP_CHECKSIG
- let result = witness_script("wsh(pk(@0/**))", core::slice::from_ref(&our_key), false);
+ let result = witness_script(
+ "wsh(pk(@0/**))",
+ core::slice::from_ref(&our_key),
+ false,
+ address_index,
+ coin,
+ )
+ .await;
let expected_derived_pubkey =
"039d626054b8fd7e8371ee7341549846cc7703b5530d6b7ddc08dc8a3b78455924";
assert_eq!(
@@ -1436,7 +1543,10 @@ mod tests {
"wsh(multi(1,@0/<10;11>/*,@1/<20;21>/*))",
&[our_key.clone(), some_key.clone()],
false,
- );
+ address_index,
+ coin,
+ )
+ .await;
let expected_derived_pubkey1 =
"0290ad738002018d6e9551603f1913983bd52145e3a026b79b133b9d36bacc7f25";
let expected_derived_pubkey2 =
@@ -1468,7 +1578,9 @@ mod tests {
10,
address_index,
],
- ),
+ coin,
+ )
+ .await,
expected_witness_script,
);
}
@@ -1478,7 +1590,10 @@ mod tests {
"wsh(multi(1,@0/<10;11>/*,@1/<20;21>/*))",
&[our_key.clone(), some_key.clone()],
true,
- );
+ address_index,
+ coin,
+ )
+ .await;
let expected_derived_pubkey1 =
"038294e6b0f046e869c3211b8c937ccb19ab0913e3170b7ec32d07d241d97d0e07";
let expected_derived_pubkey2 =
@@ -1510,7 +1625,9 @@ mod tests {
11,
address_index,
],
- ),
+ coin,
+ )
+ .await,
expected_witness_script,
);
}
@@ -1518,14 +1635,14 @@ mod tests {
// Test BIP-86 first test vector:
// https://github.com/bitcoin/bips/blob/85cda4e225b4d5fd7aff403f69d827f23f6afbbc/bip-0086.mediawiki#test-vectors
- #[test]
- fn test_tr_bip86() {
+ #[async_test::test]
+ async fn test_tr_bip86() {
mock_unlocked_using_mnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"",
);
let coin = BtcCoin::Tbtc;
- let our_key = make_our_key(&[86 + HARDENED, HARDENED, HARDENED]);
+ let our_key = make_our_key(&[86 + HARDENED, HARDENED, HARDENED]).await;
let (is_change, address_index) = (false, 0);
let derived = parse(
@@ -1533,6 +1650,7 @@ mod tests {
&make_policy("tr(@0/**)", core::slice::from_ref(&our_key)),
coin,
)
+ .await
.unwrap()
.derive(is_change, address_index)
.unwrap();
@@ -1547,37 +1665,49 @@ mod tests {
}
}
- #[test]
- fn test_tr_output_key() {
+ #[async_test::test]
+ async fn test_tr_output_key() {
mock_unlocked_using_mnemonic(
"sudden tenant fault inject concert weather maid people chunk youth stumble grit",
"",
);
let coin = BtcCoin::Tbtc;
- let our_key = make_our_key(KEYPATH_ACCOUNT);
-
- let output_key =
- |pol: &str, keys: &[pb::KeyOriginInfo], is_change: bool, address_index: u32| {
- let derived = parse(
- &mut crate::hal::testing::TestingHal::new(),
- &make_policy(pol, keys),
- coin,
- )
- .unwrap()
- .derive(is_change, address_index)
- .unwrap();
- match derived {
- Descriptor::Tr(tr) => hex::encode(tr.output_key()),
- _ => panic!("expected tr"),
- }
- };
- let output_key_at_keypath = |pol: &str, keys: &[pb::KeyOriginInfo], keypath: &[u32]| {
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
+
+ async fn output_key(
+ pol: &str,
+ keys: &[pb::KeyOriginInfo],
+ is_change: bool,
+ address_index: u32,
+ coin: BtcCoin,
+ ) -> String {
+ let derived = parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &make_policy(pol, keys),
+ coin,
+ )
+ .await
+ .unwrap()
+ .derive(is_change, address_index)
+ .unwrap();
+ match derived {
+ Descriptor::Tr(tr) => hex::encode(tr.output_key()),
+ _ => panic!("expected tr"),
+ }
+ }
+ async fn output_key_at_keypath(
+ pol: &str,
+ keys: &[pb::KeyOriginInfo],
+ keypath: &[u32],
+ coin: BtcCoin,
+ ) -> String {
let derived = parse(
&mut crate::hal::testing::TestingHal::new(),
&make_policy(pol, keys),
coin,
)
+ .await
.unwrap()
.derive_at_keypath(keypath)
.unwrap();
@@ -1585,7 +1715,7 @@ mod tests {
Descriptor::Tr(tr) => hex::encode(tr.output_key()),
_ => panic!("expected tr"),
}
- };
+ }
// Test receive path and change path using relative and full keypaths.
{
@@ -1596,11 +1726,25 @@ mod tests {
"b014ba52b642976b952dd028a763a05d039199e87e0c8e9559aa215793b77bd9";
let desc = "tr(@0/<10;11>/*,{pk(@0/<20;21>/*),pk(@0/<30;31>/*)})";
assert_eq!(
- output_key(desc, core::slice::from_ref(&our_key), false, ADDRESS_INDEX),
+ output_key(
+ desc,
+ core::slice::from_ref(&our_key),
+ false,
+ ADDRESS_INDEX,
+ coin,
+ )
+ .await,
expected_receive
);
assert_eq!(
- output_key(desc, core::slice::from_ref(&our_key), true, ADDRESS_INDEX),
+ output_key(
+ desc,
+ core::slice::from_ref(&our_key),
+ true,
+ ADDRESS_INDEX,
+ coin,
+ )
+ .await,
expected_change
);
for receive in [10, 20, 30] {
@@ -1616,7 +1760,9 @@ mod tests {
receive,
ADDRESS_INDEX,
],
- ),
+ coin,
+ )
+ .await,
expected_receive,
);
}
@@ -1633,19 +1779,21 @@ mod tests {
change,
ADDRESS_INDEX,
],
- ),
+ coin,
+ )
+ .await,
expected_change,
);
}
}
}
- #[test]
- fn test_get_leaf_hash_by_pubkey() {
+ #[async_test::test]
+ async fn test_get_leaf_hash_by_pubkey() {
mock_unlocked();
let coin = BtcCoin::Tbtc;
- let our_key = make_our_key(KEYPATH_ACCOUNT);
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
let policy = make_policy(
"tr(@0/**,{pk(@1/**),pk(@2/**)})",
&[
@@ -1655,6 +1803,7 @@ mod tests {
],
);
let derived = parse(&mut crate::hal::testing::TestingHal::new(), &policy, coin)
+ .await
.unwrap()
.derive(false, 0)
.unwrap();
@@ -1694,19 +1843,19 @@ mod tests {
assert_eq!(tr.get_leaf_hash_by_pubkey(&unknown_pk), None);
}
- #[test]
- fn test_taproot_spend_info() {
+ #[async_test::test]
+ async fn test_taproot_spend_info() {
mock_unlocked();
let coin = BtcCoin::Tbtc;
- let our_key = make_our_key(KEYPATH_ACCOUNT);
+ let our_key = make_our_key(KEYPATH_ACCOUNT).await;
let policy = make_policy(
"tr(@0/<0;1>/*,pk(@0/<2;3>/*))",
core::slice::from_ref(&our_key),
);
let mut hal = crate::hal::testing::TestingHal::new();
- let parsed_policy = parse(&mut hal, &policy, coin).unwrap();
+ let parsed_policy = parse(&mut hal, &policy, coin).await.unwrap();
const ADDRESS_INDEX: u32 = 5;
let mut xpub_cache = Bip32XpubCache::new(crate::xpubcache::Compute::Once);
@@ -1720,6 +1869,7 @@ mod tests {
.collect();
match parsed_policy
.taproot_spend_info(&mut hal, &mut xpub_cache, &keypath_internal)
+ .await
.unwrap()
{
TaprootSpendInfo::KeySpend(tweak) => {
@@ -1740,6 +1890,7 @@ mod tests {
.collect();
match parsed_policy
.taproot_spend_info(&mut hal, &mut xpub_cache, &keypath_leaf)
+ .await
.unwrap()
{
TaprootSpendInfo::ScriptSpend(leaf_hash) => {
@@ -1758,13 +1909,15 @@ mod tests {
.chain([4, ADDRESS_INDEX])
.collect();
assert!(matches!(
- parsed_policy.taproot_spend_info(&mut hal, &mut xpub_cache, &keypath_invalid),
+ parsed_policy
+ .taproot_spend_info(&mut hal, &mut xpub_cache, &keypath_invalid)
+ .await,
Err(Error::InvalidInput)
));
}
- #[test]
- fn test_get_hash() {
+ #[async_test::test]
+ async fn test_get_hash() {
// Fixture below verified with:
// import hashlib
// import base58
@@ -1794,7 +1947,7 @@ mod tests {
let pol = make_policy(
"wsh(multi(2,@0/**,@1/**))",
- &[make_our_key(KEYPATH_ACCOUNT), make_key(SOME_XPUB_1)],
+ &[make_our_key(KEYPATH_ACCOUNT).await, make_key(SOME_XPUB_1)],
);
assert_eq!(
@@ -1815,8 +1968,8 @@ mod tests {
);
}
- #[test]
- fn test_tr_unspendable_internal_key() {
+ #[async_test::test]
+ async fn test_tr_unspendable_internal_key() {
mock_unlocked_using_mnemonic(
"sudden tenant fault inject concert weather maid people chunk youth stumble grit",
"",
@@ -1832,7 +1985,7 @@ mod tests {
keypath: vec![48 + HARDENED, 1 + HARDENED, 0 + HARDENED, 2 + HARDENED],
xpub: Some(parse_xpub("tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N").unwrap()),
};
- let k2 = make_our_key(KEYPATH_ACCOUNT);
+ let k2 = make_our_key(KEYPATH_ACCOUNT).await;
{
let policy_str = "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})";
@@ -1842,6 +1995,7 @@ mod tests {
&policy,
BtcCoin::Tbtc,
)
+ .await
.unwrap();
assert_eq!(
parsed_policy.taproot_is_unspendable_internal_key(),
@@ -1861,6 +2015,7 @@ mod tests {
&policy,
BtcCoin::Tbtc,
)
+ .await
.unwrap();
assert_eq!(
parsed_policy.taproot_is_unspendable_internal_key(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/registration.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/registration.rs
index b727f49..e459e62 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/registration.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/registration.rs
@@ -114,7 +114,7 @@ pub async fn process_register_script_config(
let coin = BtcCoin::try_from(*coin)?;
let coin_params = params::get(coin);
let name = get_name(hal, request).await?;
- super::multisig::validate(hal, multisig, keypath)?;
+ super::multisig::validate(hal, multisig, keypath).await?;
let xpub_type = XPubType::try_from(request.xpub_type)?;
super::multisig::confirm_extended(
hal,
@@ -142,7 +142,7 @@ pub async fn process_register_script_config(
let coin = BtcCoin::try_from(*coin)?;
let coin_params = params::get(coin);
let name = get_name(hal, request).await?;
- let parsed = super::policies::parse(hal, policy, coin)?;
+ let parsed = super::policies::parse(hal, policy, coin).await?;
parsed
.confirm(
hal,
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/script_configs.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/script_configs.rs
index 758d7a9..a4356fe 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/script_configs.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/script_configs.rs
@@ -105,8 +105,8 @@ mod tests {
)
}
- #[test]
- fn test_self_transfer_representation_policy() {
+ #[async_test::test]
+ async fn test_self_transfer_representation_policy() {
let mut mock_hal = crate::hal::testing::TestingHal::new();
let keypath = &[48 + HARDENED, 1 + HARDENED, 0 + HARDENED, 3 + HARDENED];
let policy = pb::btc_script_config::Policy {
@@ -115,7 +115,12 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(&mut mock_hal,keypath).unwrap().into()),
+ xpub: Some(
+ crate::keystore::get_xpub_once(&mut mock_hal, keypath)
+ .await
+ .unwrap()
+ .into(),
+ ),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -125,8 +130,9 @@ mod tests {
],
};
- let parsed_policy =
- super::super::policies::parse(&mut mock_hal, &policy, pb::BtcCoin::Btc).unwrap();
+ let parsed_policy = super::super::policies::parse(&mut mock_hal, &policy, pb::BtcCoin::Btc)
+ .await
+ .unwrap();
let config = ValidatedScriptConfigWithKeypath {
keypath,
config: ValidatedScriptConfig::Policy {
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
index c5d83c2..3d78db8 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
@@ -51,7 +51,7 @@ pub async fn process(
}
// Keypath and script_config are validated in address_simple().
- let address = super::derive_address_simple(hal, coin, simple_type, keypath)?;
+ let address = super::derive_address_simple(hal, coin, simple_type, keypath).await?;
let address_formatted = util::strings::format_address(&address);
let basic_info = format!("Coin: {}", super::params::get(coin).name);
@@ -89,7 +89,8 @@ pub async fn process(
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
let signer_commitment = crate::secp256k1::secp256k1_nonce_commit(
- keystore::secp256k1_get_private_key(hal, keypath)?
+ keystore::secp256k1_get_private_key(hal, keypath)
+ .await?
.as_slice()
.try_into()
.unwrap(),
@@ -109,7 +110,8 @@ pub async fn process(
};
let sign_result = crate::secp256k1::secp256k1_sign(
- keystore::secp256k1_get_private_key(hal, keypath)?
+ keystore::secp256k1_get_private_key(hal, keypath)
+ .await?
.as_slice()
.try_into()
.unwrap(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
index 6c596b7..2bfeb52 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -232,7 +232,7 @@ fn validate_swap_source_account(
fn validate_keypath(
params: &super::params::Params,
- script_config_account: &ValidatedScriptConfigWithKeypath,
+ script_config_account: &ValidatedScriptConfigWithKeypath<'_>,
keypath: &[u32],
mode: keypath::ReceiveSpend,
) -> Result<(), Error> {
@@ -294,10 +294,10 @@ fn is_taproot(script_config_account: &ValidatedScriptConfigWithKeypath) -> bool
/// Generates the subscript (scriptCode without the length prefix) used in the bip143 sighash algo.
///
/// See https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification, item 5:
-fn sighash_script(
+async fn sighash_script(
hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
- script_config_account: &ValidatedScriptConfigWithKeypath,
+ script_config_account: &ValidatedScriptConfigWithKeypath<'_>,
keypath: &[u32],
) -> Result<Vec<u8>, Error> {
match script_config_account {
@@ -309,7 +309,7 @@ fn sighash_script(
SimpleType::P2wpkhP2sh | SimpleType::P2wpkh => {
// See https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification, item 5:
// > For P2WPKH witness program, the scriptCode is 0x1976a914{20-byte-pubkey-hash}88ac.
- let pubkey_hash160 = xpub_cache.get_xpub(hal, keypath)?.pubkey_hash160();
+ let pubkey_hash160 = xpub_cache.get_xpub(hal, keypath).await?.pubkey_hash160();
let mut result = Vec::<u8>::new();
result.extend_from_slice(b"\x76\xa9\x14");
result.extend_from_slice(&pubkey_hash160);
@@ -410,7 +410,7 @@ async fn handle_prevtx(
Ok(())
}
-fn validate_script_config<'a>(
+async fn validate_script_config<'a>(
hal: &mut impl crate::hal::Hal,
script_config: &'a pb::BtcScriptConfigWithKeypath,
coin_params: &super::params::Params,
@@ -423,7 +423,7 @@ fn validate_script_config<'a>(
}),
keypath,
} => {
- super::multisig::validate(hal, multisig, keypath)?;
+ super::multisig::validate(hal, multisig, keypath).await?;
let name = super::multisig::get_name(hal, coin_params.coin, multisig, keypath)?
.ok_or(Error::InvalidInput)?;
Ok(ValidatedScriptConfigWithKeypath {
@@ -438,7 +438,7 @@ fn validate_script_config<'a>(
}),
keypath,
} => {
- let parsed_policy = super::policies::parse(hal, policy, coin_params.coin)?;
+ let parsed_policy = super::policies::parse(hal, policy, coin_params.coin).await?;
let name = parsed_policy
.name(hal, coin_params)?
.ok_or(Error::InvalidInput)?;
@@ -474,15 +474,15 @@ fn validate_script_config<'a>(
}
}
-fn validate_script_configs<'a>(
+async fn validate_script_configs<'a>(
hal: &mut impl crate::hal::Hal,
coin_params: &super::params::Params,
script_configs: &'a [pb::BtcScriptConfigWithKeypath],
) -> Result<Vec<ValidatedScriptConfigWithKeypath<'a>>, Error> {
- let validated: Vec<ValidatedScriptConfigWithKeypath> = script_configs
- .iter()
- .map(|config| validate_script_config(hal, config, coin_params))
- .collect::<Result<Vec<ValidatedScriptConfigWithKeypath>, Error>>()?;
+ let mut validated = Vec::with_capacity(script_configs.len());
+ for config in script_configs.iter() {
+ validated.push(validate_script_config(hal, config, coin_params).await?);
+ }
Ok(validated)
}
@@ -495,7 +495,7 @@ async fn validate_input_script_configs<'a>(
return Err(Error::InvalidInput);
}
- let script_configs = validate_script_configs(hal, coin_params, script_configs)?;
+ let script_configs = validate_script_configs(hal, coin_params, script_configs).await?;
// If there are multiple script configs, only SimpleType (single sig, no additional inputs)
// configs are allowed, so e.g. mixing p2wpkh and pw2wpkh-p2sh is okay, but mixing p2wpkh with
@@ -716,7 +716,7 @@ async fn _process(
let validated_script_configs =
validate_input_script_configs(hal, coin_params, &request.script_configs).await?;
let validated_output_script_configs =
- validate_script_configs(hal, coin_params, &request.output_script_configs)?;
+ validate_script_configs(hal, coin_params, &request.output_script_configs).await?;
let mut xpub_cache = Bip32XpubCache::new(Compute::Once);
setup_xpub_cache(&mut xpub_cache, &request.script_configs);
@@ -799,7 +799,8 @@ async fn _process(
coin_params,
&tx_input.keypath,
script_config_account,
- )?
+ )
+ .await?
.pk_script(coin_params)?;
hasher_scriptpubkeys.update(serialize(&VarInt(pk_script.len() as u64)));
hasher_scriptpubkeys.update(pk_script.as_slice());
@@ -818,7 +819,7 @@ async fn _process(
if let Some(ref mut silent_payment) = silent_payment {
let keypair = bitcoin::key::UntweakedKeypair::from_seckey_slice(
SECP256K1,
- &crate::keystore::secp256k1_get_private_key(hal, &tx_input.keypath)?,
+ &crate::keystore::secp256k1_get_private_key(hal, &tx_input.keypath).await?,
)
.unwrap();
// For Taproot, only key path spends are allowed in silent payments, and we need to
@@ -918,7 +919,8 @@ async fn _process(
coin_params,
&tx_output.keypath,
script_config_account,
- )?
+ )
+ .await?
} else {
// Take payload from provided output.
@@ -1016,7 +1018,9 @@ async fn _process(
&payment_request,
total_value,
&address()?,
- ) {
+ )
+ .await
+ {
Ok(()) => {}
#[cfg(not(feature = "app-ethereum"))]
Err(payment_request::ValidationError::Disabled) => {
@@ -1198,7 +1202,7 @@ async fn _process(
ValidatedScriptConfig::SimpleType(SimpleType::P2tr) => {
// This is a BIP-86 spend, so we tweak the private key by the hash of the public
// key only, as there is no Taproot merkle root.
- let xpub = xpub_cache.get_xpub(hal, &tx_input.keypath)?;
+ let xpub = xpub_cache.get_xpub(hal, &tx_input.keypath).await?;
let pubkey = bitcoin::PublicKey::from_slice(xpub.public_key())
.map_err(|_| Error::Generic)?;
TaprootSpendInfo::KeySpend(bitcoin::TapTweakHash::from_key_and_tweak(
@@ -1212,7 +1216,9 @@ async fn _process(
// first tweak the private key to match the Taproot output key. For leaf
// scripts, we do not tweak.
- parsed_policy.taproot_spend_info(hal, &mut xpub_cache, &tx_input.keypath)?
+ parsed_policy
+ .taproot_spend_info(hal, &mut xpub_cache, &tx_input.keypath)
+ .await?
}
_ => return Err(Error::Generic),
};
@@ -1242,7 +1248,8 @@ async fn _process(
} else {
None
},
- )?
+ )
+ .await?
.to_vec();
} else {
// Sign all other supported inputs.
@@ -1259,7 +1266,8 @@ async fn _process(
&mut xpub_cache,
script_config_account,
&tx_input.keypath,
- )?,
+ )
+ .await?,
prevout_value: tx_input.prev_out_value,
sequence: tx_input.sequence,
hash_outputs: Sha256::digest(hash_outputs).into(),
@@ -1267,7 +1275,8 @@ async fn _process(
sighash_flags: SIGHASH_ALL,
});
- let private_key = crate::keystore::secp256k1_get_private_key(hal, &tx_input.keypath)?;
+ let private_key =
+ crate::keystore::secp256k1_get_private_key(hal, &tx_input.keypath).await?;
// Engage in the Anti-Klepto protocol if the host sends a host nonce commitment.
let host_nonce: [u8; 32] = match tx_input.host_nonce_commitment {
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
@@ -1866,7 +1875,10 @@ mod tests {
let multisig = pb::btc_script_config::Multisig {
threshold: 1,
xpubs: vec![
- crate::keystore::get_xpub_once(&mut TestingHal::new(), keypath).unwrap().into(),
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), keypath)
+ .await
+ .unwrap()
+ .into(),
parse_xpub("xpub6ERxBysTYfQyY4USv6c6J1HNVv9hpZFN9LHVPu47Ac4rK8fLy6NnAeeAHyEsMvG4G66ay5aFZii2VM7wT3KxLKX8Q8keZPd67kRGmrD1WJj").unwrap(),
],
our_xpub_index: 0,
@@ -1889,7 +1901,11 @@ mod tests {
.multisig_set_by_hash(&hash, "test name")
.unwrap();
- assert!(super::super::multisig::validate(&mut mock_hal, &multisig, keypath).is_ok());
+ assert!(
+ super::super::multisig::validate(&mut mock_hal, &multisig, keypath)
+ .await
+ .is_ok()
+ );
let mut init_req_invalid = init_req_valid.clone();
init_req_invalid.script_configs = vec![
@@ -3266,7 +3282,12 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(&mut mock_hal, keypath_account).unwrap().into()),
+ xpub: Some(
+ crate::keystore::get_xpub_once(&mut mock_hal, keypath_account)
+ .await
+ .unwrap()
+ .into(),
+ ),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -3386,7 +3407,12 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
+ xpub: Some(
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), keypath_account)
+ .await
+ .unwrap()
+ .into(),
+ ),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -3456,7 +3482,12 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
+ xpub: Some(
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), keypath_account)
+ .await
+ .unwrap()
+ .into(),
+ ),
},
],
};
@@ -3561,7 +3592,12 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
+ xpub: Some(
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), keypath_account)
+ .await
+ .unwrap()
+ .into(),
+ ),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -3619,7 +3655,12 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
+ xpub: Some(
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), keypath_account)
+ .await
+ .unwrap()
+ .into(),
+ ),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -3669,7 +3710,12 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
+ xpub: Some(
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), keypath_account)
+ .await
+ .unwrap()
+ .into(),
+ ),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/xpubs.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/xpubs.rs
index 856e664..2db7035 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/xpubs.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/xpubs.rs
@@ -47,7 +47,7 @@ pub async fn process_xpubs(
.map_err(|_| Error::InvalidInput)?;
}
- let xpubs = crate::keystore::get_xpubs_twice(hal, &keypaths)?;
+ let xpubs = crate::keystore::get_xpubs_twice(hal, &keypaths).await?;
let xpub_strings: Vec<String> = xpubs
.iter()
.map(|xpub| xpub.serialize_str(xpub_type))
diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano.rs b/src/rust/bitbox02-rust/src/hww/api/cardano.rs
index a079bf2..4910efe 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano.rs
@@ -21,7 +21,7 @@ pub async fn process_api(
request: &Request,
) -> Result<Response, Error> {
match request {
- Request::Xpubs(request) => xpubs::process(hal, request),
+ Request::Xpubs(request) => xpubs::process(hal, request).await,
Request::Address(request) => address::process(hal, request).await,
Request::SignTransaction(request) => sign_transaction::process(hal, request).await,
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs b/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs
index 735e6cb..8fe2abf 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs
@@ -301,11 +301,11 @@ pub fn decode_payment_address(params: ¶ms::Params, address: &str) -> Result<
}
/// Returns the hash of the pubkey at the keypath. Returns an error if the keystore is locked.
-pub fn pubkey_hash_at_keypath(
+pub async fn pubkey_hash_at_keypath(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
) -> Result<[u8; ADDRESS_HASH_SIZE], ()> {
- let xpub = crate::keystore::ed25519::get_xpub_twice(hal, keypath)?;
+ let xpub = crate::keystore::ed25519::get_xpub_twice(hal, keypath).await?;
let pubkey_bytes = xpub.pubkey_bytes();
let mut hasher = Blake2bVar::new(ADDRESS_HASH_SIZE).unwrap();
hasher.update(pubkey_bytes);
@@ -326,7 +326,7 @@ fn address_header(params: ¶ms::Params, script_config: &Config) -> u8 {
/// Encode the given address using bech32, validating that the keypaths are valid. If
/// `keypath_prefix` is provided, it is also validated that the address keypaths start with this
/// prefix.
-pub fn validate_and_encode_payment_address(
+pub async fn validate_and_encode_payment_address(
hal: &mut impl crate::hal::Hal,
params: ¶ms::Params,
script_config: &Config,
@@ -342,8 +342,8 @@ pub fn validate_and_encode_payment_address(
bip44_account,
)?;
- let payment_key_hash = pubkey_hash_at_keypath(hal, &config.keypath_payment)?;
- let stake_key_hash = pubkey_hash_at_keypath(hal, &config.keypath_stake)?;
+ let payment_key_hash = pubkey_hash_at_keypath(hal, &config.keypath_payment).await?;
+ let stake_key_hash = pubkey_hash_at_keypath(hal, &config.keypath_stake).await?;
let mut bytes: Vec<u8> = Vec::with_capacity(1 + 2 * ADDRESS_HASH_SIZE);
bytes.push(header);
@@ -391,7 +391,8 @@ pub async fn process(
.as_ref()
.ok_or(Error::InvalidInput)?;
- let encoded_address = validate_and_encode_payment_address(hal, params, script_config, None)?;
+ let encoded_address =
+ validate_and_encode_payment_address(hal, params, script_config, None).await?;
if request.display {
let displayed_address = format_display_address(&encoded_address);
@@ -517,14 +518,15 @@ mod tests {
.await
}
- #[test]
- fn test_pubkey_hash_at_keypath() {
+ #[async_test::test]
+ async fn test_pubkey_hash_at_keypath() {
crate::keystore::lock();
assert!(
pubkey_hash_at_keypath(
&mut crate::hal::testing::TestingHal::new(),
&[1852 + HARDENED, 1815 + HARDENED, HARDENED, 0, 0]
)
+ .await
.is_err()
);
@@ -532,7 +534,9 @@ mod tests {
assert_eq!(
pubkey_hash_at_keypath(
&mut crate::hal::testing::TestingHal::new(),
- &[1852 + HARDENED, 1815 + HARDENED, HARDENED, 0, 0]),
+ &[1852 + HARDENED, 1815 + HARDENED, HARDENED, 0, 0]
+ )
+ .await,
Ok(*b"\x5e\xbf\xc2\xcd\xae\xef\x4b\x4f\x1b\xe7\xfc\xc3\x1c\xfe\x94\x5e\xb9\x2d\x28\x67\x43\x49\xbd\x0f\x1a\x4a\x00\x63")
);
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs b/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs
index 80195f1..bcae3c4 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs
@@ -47,12 +47,12 @@ fn format_value(params: ¶ms::Params, value: u64) -> String {
)
}
-fn make_shelley_witness(
+async fn make_shelley_witness(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
tx_body_hash: &[u8; 32],
) -> Result<ShelleyWitness, ()> {
- let result = ed25519::sign(hal, keypath, tx_body_hash)?;
+ let result = ed25519::sign(hal, keypath, tx_body_hash).await?;
Ok(ShelleyWitness {
public_key: result.public_key.as_ref().to_vec(),
signature: result.signature.to_vec(),
@@ -230,7 +230,8 @@ async fn _process(
params,
config,
Some(bip44_account),
- )?;
+ )
+ .await?;
if encoded_address != output.encoded_address {
return Err(Error::InvalidInput);
}
@@ -291,7 +292,7 @@ async fn _process(
let tx_body_hash: [u8; 32] = {
let mut hasher = Blake2bVar::new(32).unwrap();
- cbor::encode_transaction_body(hal, request, cbor::HashedWriter::new(&mut hasher))?;
+ cbor::encode_transaction_body(hal, request, cbor::HashedWriter::new(&mut hasher)).await?;
let mut out = [0u8; 32];
hasher.finalize_variable(&mut out).or(Err(Error::Generic))?;
@@ -303,7 +304,7 @@ async fn _process(
let mut shelley_witnesses: Vec<ShelleyWitness> = Vec::with_capacity(signing_keypaths.len());
for keypath in signing_keypaths {
- shelley_witnesses.push(make_shelley_witness(hal, keypath, &tx_body_hash)?);
+ shelley_witnesses.push(make_shelley_witness(hal, keypath, &tx_body_hash).await?);
}
Ok(Response::SignTransaction(
diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction/cbor.rs b/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction/cbor.rs
index ca569c0..c4d6147 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction/cbor.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction/cbor.rs
@@ -32,12 +32,12 @@ impl<U: Update> Write for HashedWriter<'_, U> {
}
/// See https://github.com/input-output-hk/cardano-ledger-specs/blob/d0aa86ded0b973b09b629e5aa62aa1e71364d088/eras/alonzo/test-suite/cddl-files/alonzo.cddl#L176
-fn encode_stake_credential<W: Write>(
+async fn encode_stake_credential<W: Write>(
hal: &mut impl crate::hal::Hal,
encoder: &mut Encoder<W>,
keypath: &[u32],
) -> Result<(), Error> {
- let pubkey_hash = pubkey_hash_at_keypath(hal, keypath)?;
+ let pubkey_hash = pubkey_hash_at_keypath(hal, keypath).await?;
encoder.array(2)?.u8(0)?.bytes(&pubkey_hash)?;
Ok(())
}
@@ -45,12 +45,12 @@ fn encode_stake_credential<W: Write>(
/// Encode a withdrawal/reward address.
///
/// See https://github.com/input-output-hk/cardano-ledger-specs/blob/d0aa86ded0b973b09b629e5aa62aa1e71364d088/eras/alonzo/test-suite/cddl-files/alonzo.cddl#L130
-pub fn encode_withdrawal_address(
+pub async fn encode_withdrawal_address(
hal: &mut impl crate::hal::Hal,
params: ¶ms::Params,
keypath: &[u32],
) -> Result<Vec<u8>, Error> {
- let pubkey_hash = pubkey_hash_at_keypath(hal, keypath)?;
+ let pubkey_hash = pubkey_hash_at_keypath(hal, keypath).await?;
let mut encoded: Vec<u8> = Vec::with_capacity(1 + ADDRESS_HASH_SIZE);
let address_tag = 0b1110; // reward address using a stake keyhash.
let header = (address_tag << 4) | params.network_id;
@@ -81,7 +81,7 @@ fn encode_set_header<W: Write>(
/// References:
/// - Transaction body encoding spec: https://github.com/input-output-hk/cardano-ledger-specs/blob/d0aa86ded0b973b09b629e5aa62aa1e71364d088/eras/alonzo/test-suite/cddl-files/alonzo.cddl#L50
/// - Serialization implementation: https://github.com/input-output-hk/cardano-ledger-specs/blob/c6c4be1562e23a3dd48282387c4e48ff918fbab0/eras/shelley-ma/impl/src/Cardano/Ledger/ShelleyMA/TxBody.hs#L208
-pub fn encode_transaction_body<W: Write>(
+pub async fn encode_transaction_body<W: Write>(
hal: &mut impl crate::hal::Hal,
tx: &pb::CardanoSignTransactionRequest,
writer: W,
@@ -156,11 +156,11 @@ pub fn encode_transaction_body<W: Write>(
match cert.as_ref().ok_or(Error::InvalidInput)? {
certificate::Cert::StakeRegistration(pb::Keypath { keypath }) => {
encoder.array(2)?.u8(0)?;
- encode_stake_credential(hal, &mut encoder, keypath)?;
+ encode_stake_credential(hal, &mut encoder, keypath).await?;
}
certificate::Cert::StakeDeregistration(pb::Keypath { keypath }) => {
encoder.array(2)?.u8(1)?;
- encode_stake_credential(hal, &mut encoder, keypath)?;
+ encode_stake_credential(hal, &mut encoder, keypath).await?;
}
certificate::Cert::StakeDelegation(certificate::StakeDelegation {
keypath,
@@ -170,7 +170,7 @@ pub fn encode_transaction_body<W: Write>(
return Err(Error::InvalidInput);
}
encoder.array(3)?.u8(2)?;
- encode_stake_credential(hal, &mut encoder, keypath)?;
+ encode_stake_credential(hal, &mut encoder, keypath).await?;
encoder.bytes(pool_keyhash)?;
}
certificate::Cert::VoteDelegation(certificate::VoteDelegation {
@@ -179,7 +179,7 @@ pub fn encode_transaction_body<W: Write>(
drep_credhash,
}) => {
encoder.array(3)?.u8(9)?;
- encode_stake_credential(hal, &mut encoder, keypath)?;
+ encode_stake_credential(hal, &mut encoder, keypath).await?;
let drep_type =
certificate::vote_delegation::CardanoDRepType::try_from(*r#type)?;
match drep_type {
@@ -225,7 +225,7 @@ pub fn encode_transaction_body<W: Write>(
if !tx.withdrawals.is_empty() {
encoder.u8(5)?.map(tx.withdrawals.len() as _)?;
for Withdrawal { keypath, value } in tx.withdrawals.iter() {
- let withdrawal_address = encode_withdrawal_address(hal, params, keypath)?;
+ let withdrawal_address = encode_withdrawal_address(hal, params, keypath).await?;
encoder.bytes(&withdrawal_address)?.u64(*value)?;
}
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano/xpubs.rs b/src/rust/bitbox02-rust/src/hww/api/cardano/xpubs.rs
index 630da62..99f9980 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano/xpubs.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano/xpubs.rs
@@ -12,7 +12,7 @@ use super::keypath::validate_account_shelley;
/// Return the xpub at the request keypath.
///
/// 64 bytes: 32 bytes public key + 32 bytes chain code.
-pub fn process(
+pub async fn process(
hal: &mut impl crate::hal::Hal,
request: &pb::CardanoXpubsRequest,
) -> Result<Response, Error> {
@@ -20,7 +20,7 @@ pub fn process(
for pb::Keypath { keypath } in &request.keypaths {
validate_account_shelley(keypath)?;
- let xpub = crate::keystore::ed25519::get_xpub_twice(hal, keypath)?;
+ let xpub = crate::keystore::ed25519::get_xpub_twice(hal, keypath).await?;
let mut xpub_bytes = Vec::with_capacity(64);
xpub_bytes.extend_from_slice(xpub.pubkey_bytes());
xpub_bytes.extend_from_slice(xpub.chain_code());
@@ -37,14 +37,15 @@ mod tests {
use hex_lit::hex;
use util::bip32::HARDENED;
- #[test]
- fn test_process() {
+ #[async_test::test]
+ async fn test_process() {
crate::keystore::lock();
assert_eq!(
process(
&mut crate::hal::testing::TestingHal::new(),
&pb::CardanoXpubsRequest { keypaths: vec![] }
- ),
+ )
+ .await,
Ok(Response::Xpubs(pb::CardanoXpubsResponse { xpubs: vec![] })),
);
@@ -57,7 +58,8 @@ mod tests {
keypath: vec![1852 + HARDENED, 1815 + HARDENED, HARDENED]
}],
}
- ),
+ )
+ .await,
Err(Error::Generic),
);
@@ -75,7 +77,8 @@ mod tests {
}
],
}
- ),
+ )
+ .await,
Ok(Response::Xpubs(pb::CardanoXpubsResponse {
xpubs: vec![
hex!(
@@ -115,7 +118,8 @@ mod tests {
keypath: invalid_keypath.to_vec(),
},],
}
- ),
+ )
+ .await,
Err(Error::InvalidInput),
);
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/change_password.rs b/src/rust/bitbox02-rust/src/hww/api/change_password.rs
index c3e3f6b..e331527 100644
--- a/src/rust/bitbox02-rust/src/hww/api/change_password.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/change_password.rs
@@ -26,7 +26,7 @@ pub async fn process(hal: &mut impl crate::hal::Hal) -> Result<Response, Error>
let new_password = password::enter_twice(hal).await?;
// Re-encrypt seed with new password
- if let Err(err) = keystore::re_encrypt_seed(hal, &seed, &new_password) {
+ if let Err(err) = keystore::re_encrypt_seed(hal, &seed, &new_password).await {
hal.ui().status(&format!("Error\n{:?}", err), false).await;
return Err(Error::Generic);
}
@@ -56,7 +56,9 @@ mod tests {
let mut prompt_counter = 0u32;
let mut hal = TestingHal::new();
- keystore::encrypt_and_store_seed(&mut hal, &seed, old_password).unwrap();
+ keystore::encrypt_and_store_seed(&mut hal, &seed, old_password)
+ .await
+ .unwrap();
unlock::unlock_bip39(&mut hal, &seed).await;
hal.memory.set_initialized().unwrap();
@@ -136,7 +138,9 @@ mod tests {
let mut prompt_counter = 0u32;
let mut hal = TestingHal::new();
- keystore::encrypt_and_store_seed(&mut hal, &seed, correct_password).unwrap();
+ keystore::encrypt_and_store_seed(&mut hal, &seed, correct_password)
+ .await
+ .unwrap();
unlock::unlock_bip39(&mut hal, &seed).await;
hal.memory.set_initialized().unwrap();
keystore::lock();
@@ -193,7 +197,9 @@ mod tests {
let mut prompt_counter = 0u32;
let mut hal = TestingHal::new();
- keystore::encrypt_and_store_seed(&mut hal, &seed, old_password).unwrap();
+ keystore::encrypt_and_store_seed(&mut hal, &seed, old_password)
+ .await
+ .unwrap();
unlock::unlock_bip39(&mut hal, &seed).await;
hal.memory.set_initialized().unwrap();
keystore::lock();
diff --git a/src/rust/bitbox02-rust/src/hww/api/electrum.rs b/src/rust/bitbox02-rust/src/hww/api/electrum.rs
index 498d035..536ea58 100644
--- a/src/rust/bitbox02-rust/src/hww/api/electrum.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/electrum.rs
@@ -29,6 +29,7 @@ pub async fn process(
return Err(Error::InvalidInput);
}
let xpub = keystore::get_xpub_twice(hal, keypath)
+ .await
.or(Err(Error::InvalidInput))?
.serialize_str(bip32::XPubType::Xpub)?;
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum.rs
index a61a062..85b6d3a 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum.rs
@@ -41,7 +41,7 @@ fn truncating_hex_preview_byte_cap(prefix_len: usize, data_length: usize) -> usi
preview_bytes.min(data_length)
}
-pub(crate) fn derive_address(
+pub(crate) async fn derive_address(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
) -> Result<alloc::string::String, Error> {
@@ -49,6 +49,7 @@ pub(crate) fn derive_address(
return Err(Error::InvalidInput);
}
let pubkey = crate::keystore::get_xpub_twice(hal, keypath)
+ .await
.or(Err(Error::InvalidInput))?
.pubkey_uncompressed()?;
Ok(address::from_pubkey(&pubkey))
@@ -119,25 +120,25 @@ mod tests {
use crate::hal::testing::TestingHal;
use util::bip32::HARDENED;
- #[test]
- fn test_derive_address() {
+ #[async_test::test]
+ async fn test_derive_address() {
let mut hal = TestingHal::new();
// Standard Ethereum keypath
let keypath = vec![44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
- let address = derive_address(&mut hal, &keypath).unwrap();
+ let address = derive_address(&mut hal, &keypath).await.unwrap();
// This is the expected address for the mock keystore seed with this keypath
assert_eq!(address, "0x773A77b9D32589be03f9132AF759e294f7851be9");
}
- #[test]
- fn test_derive_address_invalid_keypath() {
+ #[async_test::test]
+ async fn test_derive_address_invalid_keypath() {
let mut hal = TestingHal::new();
// Invalid keypath (too short)
let keypath = vec![44 + HARDENED, 60 + HARDENED];
- let result = derive_address(&mut hal, &keypath);
+ let result = derive_address(&mut hal, &keypath).await;
assert!(matches!(result, Err(Error::InvalidInput)));
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs
index eff4a57..1b5e1ef 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs
@@ -30,7 +30,7 @@ async fn process_address(
Some(erc20_params::get(params.chain_id, address).ok_or(Error::InvalidInput)?)
};
- let address = super::derive_address(hal, &request.keypath)?;
+ let address = super::derive_address(hal, &request.keypath).await?;
if request.display {
let address_display = super::address::format_display_address(&address);
@@ -53,7 +53,7 @@ async fn process_address(
Ok(Response::Pub(pb::PubResponse { r#pub: address }))
}
-fn process_xpub(
+async fn process_xpub(
hal: &mut impl crate::hal::Hal,
request: &pb::EthPubRequest,
) -> Result<Response, Error> {
@@ -66,6 +66,7 @@ fn process_xpub(
return Err(Error::InvalidInput);
}
let xpub = keystore::get_xpub_twice(hal, &request.keypath)
+ .await
.or(Err(Error::InvalidInput))?
.serialize_str(bip32::XPubType::Xpub)?;
@@ -79,7 +80,7 @@ pub async fn process(
let output_type = OutputType::try_from(request.output_type)?;
match output_type {
OutputType::Address => process_address(hal, request).await,
- OutputType::Xpub => process_xpub(hal, request),
+ OutputType::Xpub => process_xpub(hal, request).await,
}
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
index 17c1b21..7098351 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
@@ -180,6 +180,7 @@ async fn verify_payment_request_recipient(
) -> Result<(), Error> {
payment_request::user_verify(hal, payment_request, displayed_source_amount).await?;
match payment_request::validate_eth(hal, params, payment_request, output_value, output_address)
+ .await
{
Ok(()) => Ok(()),
Err(_) => {
@@ -600,7 +601,8 @@ pub async fn _process(
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
Some(pb::AntiKleptoHostNonceCommitment { commitment }) => {
let signer_commitment = crate::secp256k1::secp256k1_nonce_commit(
- &keystore::secp256k1_get_private_key(hal, request.keypath())?
+ &keystore::secp256k1_get_private_key(hal, request.keypath())
+ .await?
.as_slice()
.try_into()
.unwrap(),
@@ -619,7 +621,8 @@ pub async fn _process(
None => [0; 32],
};
let sign_result = crate::secp256k1::secp256k1_sign(
- &keystore::secp256k1_get_private_key(hal, request.keypath())?
+ &keystore::secp256k1_get_private_key(hal, request.keypath())
+ .await?
.as_slice()
.try_into()
.unwrap(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
index 286f5d5..76f6dbc 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
@@ -625,7 +625,8 @@ pub async fn process(
let host_nonce = match request.host_nonce_commitment {
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
let signer_commitment = crate::secp256k1::secp256k1_nonce_commit(
- keystore::secp256k1_get_private_key(hal, &request.keypath)?
+ keystore::secp256k1_get_private_key(hal, &request.keypath)
+ .await?
.as_slice()
.try_into()
.unwrap(),
@@ -644,7 +645,8 @@ pub async fn process(
};
let sign_result = crate::secp256k1::secp256k1_sign(
- keystore::secp256k1_get_private_key(hal, &request.keypath)?
+ keystore::secp256k1_get_private_key(hal, &request.keypath)
+ .await?
.as_slice()
.try_into()
.unwrap(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
index 1073884..59f55cd 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
@@ -55,7 +55,8 @@ pub async fn process(
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
let signer_commitment = crate::secp256k1::secp256k1_nonce_commit(
- keystore::secp256k1_get_private_key(hal, &request.keypath)?
+ keystore::secp256k1_get_private_key(hal, &request.keypath)
+ .await?
.as_slice()
.try_into()
.unwrap(),
@@ -75,7 +76,8 @@ pub async fn process(
};
let sign_result = crate::secp256k1::secp256k1_sign(
- keystore::secp256k1_get_private_key(hal, &request.keypath)?
+ keystore::secp256k1_get_private_key(hal, &request.keypath)
+ .await?
.as_slice()
.try_into()
.unwrap(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/payment_request.rs b/src/rust/bitbox02-rust/src/hww/api/payment_request.rs
index 72fcc1f..648c002 100644
--- a/src/rust/bitbox02-rust/src/hww/api/payment_request.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/payment_request.rs
@@ -306,7 +306,7 @@ fn ecdsa_verify(sig64: &[u8], msg32: &[u8], pubkey33: &[u8]) -> Result<(), Valid
}
/// Validate a BTC payment request against the parsed BTC output total.
-pub fn validate_btc(
+pub async fn validate_btc(
#[cfg_attr(not(feature = "app-ethereum"), allow(unused_variables))] hal: &mut impl crate::hal::Hal,
coin_params: &super::bitcoin::params::Params,
payment_request: &pb::BtcPaymentRequestRequest,
@@ -324,11 +324,12 @@ pub fn validate_btc(
&total_value_bytes,
output_address,
)
+ .await
}
/// Validate an ETH/EVM payment request against the parsed source-side transaction.
#[cfg(feature = "app-ethereum")]
-pub fn validate_eth(
+pub async fn validate_eth(
hal: &mut impl crate::hal::Hal,
coin_params: &super::ethereum::params::Params,
payment_request: &pb::BtcPaymentRequestRequest,
@@ -348,6 +349,7 @@ pub fn validate_eth(
&output_value_padded,
output_address,
)
+ .await
}
/// Validate that the parsed source-side transaction matches the signed payment request.
@@ -359,7 +361,7 @@ pub fn validate_eth(
/// Destination ownership checks for `CoinPurchaseMemo.address` still happen
/// here, because they are derived from the memo's keypath metadata rather than
/// from the source transaction itself.
-fn validate_common(
+async fn validate_common(
#[cfg_attr(not(feature = "app-ethereum"), allow(unused_variables))] hal: &mut impl crate::hal::Hal,
source_coin_type: u32,
payment_request: &pb::BtcPaymentRequestRequest,
@@ -388,6 +390,7 @@ fn validate_common(
#[cfg(feature = "app-ethereum")]
{
let derived_address = super::ethereum::derive_address(hal, &_eth.keypath)
+ .await
.map_err(|_| ValidationError::Other)?;
if derived_address != coin_purchase_memo.address {
return Err(ValidationError::AddressMismatch);
@@ -427,6 +430,7 @@ fn validate_common(
simple_type,
&script_config.keypath,
)
+ .await
.map_err(|_| ValidationError::Other)?;
if derived_address != coin_purchase_memo.address {
@@ -656,8 +660,8 @@ mod tests {
}
}
- #[test]
- fn test_validate() {
+ #[async_test::test]
+ async fn test_validate() {
let source_coin_type = params::get(pb::BtcCoin::Tbtc).slip44();
let mut mock_hal = TestingHal::new();
@@ -687,6 +691,7 @@ mod tests {
&value_bytes,
address
)
+ .await
.is_ok()
);
@@ -723,6 +728,7 @@ mod tests {
&value_bytes,
address
)
+ .await
.is_ok()
);
}
@@ -743,6 +749,7 @@ mod tests {
pb::btc_script_config::SimpleType::P2wpkh,
&source_keypath,
)
+ .await
.unwrap();
let destination_keypath = [
@@ -758,6 +765,7 @@ mod tests {
pb::btc_script_config::SimpleType::P2wpkh,
&destination_keypath,
)
+ .await
.unwrap();
let source_coin_type = params::get(pb::BtcCoin::Btc).slip44();
@@ -793,6 +801,7 @@ mod tests {
&value_bytes,
&source_address,
)
+ .await
.is_ok()
);
}
@@ -813,6 +822,7 @@ mod tests {
pb::btc_script_config::SimpleType::P2wpkh,
&source_keypath,
)
+ .await
.unwrap();
let destination_keypath = [
@@ -828,6 +838,7 @@ mod tests {
pb::btc_script_config::SimpleType::P2wpkh,
&destination_keypath,
)
+ .await
.unwrap();
let source_coin_type = params::get(pb::BtcCoin::Ltc).slip44();
@@ -863,6 +874,7 @@ mod tests {
&value_bytes,
&source_address,
)
+ .await
.is_ok()
);
}
@@ -898,7 +910,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
}
@@ -931,7 +944,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::AddressMismatch)
));
}
@@ -964,7 +978,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
}
@@ -1022,7 +1037,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
}
@@ -1065,7 +1081,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::AddressMismatch)
));
}
@@ -1107,7 +1124,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
}
@@ -1149,7 +1167,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
}
@@ -1191,7 +1210,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
}
@@ -1249,7 +1269,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
}
@@ -1269,7 +1290,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::UnknownRecipient)
));
@@ -1288,7 +1310,8 @@ mod tests {
&payment_request,
value + 1,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
@@ -1307,7 +1330,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::Other)
));
@@ -1326,7 +1350,8 @@ mod tests {
&payment_request,
&value_bytes,
address
- ),
+ )
+ .await,
Err(ValidationError::InvalidSignature)
));
}
@@ -1460,8 +1485,8 @@ mod tests {
}
#[cfg(feature = "app-ethereum")]
- #[test]
- fn test_validate_eth() {
+ #[async_test::test]
+ async fn test_validate_eth() {
let mut mock_hal = TestingHal::new();
let params = eth_params::Params {
coin: Some(pb::EthCoin::Eth),
@@ -1500,6 +1525,7 @@ mod tests {
&BigUint::from_bytes_be(&output_value),
output_address,
)
+ .await
.is_ok()
);
@@ -1511,7 +1537,8 @@ mod tests {
&payment_request,
&BigUint::from_bytes_be(&output_value[24..]),
output_address,
- ),
+ )
+ .await,
Ok(())
));
@@ -1525,7 +1552,8 @@ mod tests {
&payment_request,
&BigUint::from_bytes_be(&wrong_output_value),
output_address,
- ),
+ )
+ .await,
Err(ValidationError::InvalidSignature)
));
@@ -1537,7 +1565,8 @@ mod tests {
&payment_request,
&BigUint::from_bytes_be(&output_value),
"0x1111111111111111111111111111111111111111",
- ),
+ )
+ .await,
Err(ValidationError::InvalidSignature)
));
@@ -1555,7 +1584,8 @@ mod tests {
&payment_request,
&BigUint::from_bytes_be(&output_value),
output_address,
- ),
+ )
+ .await,
Err(ValidationError::InvalidSignature)
));
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/restore.rs b/src/rust/bitbox02-rust/src/hww/api/restore.rs
index 118daf8..c7d35b6 100644
--- a/src/rust/bitbox02-rust/src/hww/api/restore.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/restore.rs
@@ -57,7 +57,7 @@ pub async fn from_file(
let password = password::enter_twice(hal).await?;
let seed = data.get_seed();
- if let Err(err) = crate::keystore::encrypt_and_store_seed(hal, seed, &password) {
+ if let Err(err) = crate::keystore::encrypt_and_store_seed(hal, seed, &password).await {
hal.ui()
.status(&format!("Could not\nrestore backup\n{:?}", err), false)
.await;
@@ -133,7 +133,7 @@ pub async fn from_mnemonic(
}
};
- if let Err(err) = crate::keystore::encrypt_and_store_seed(hal, &seed, &password) {
+ if let Err(err) = crate::keystore::encrypt_and_store_seed(hal, &seed, &password).await {
hal.ui()
.status(&format!("Could not\nrestore backup\n{:?}", err), false)
.await;
@@ -201,11 +201,15 @@ mod tests {
// Seed of hardcoded phrase used in unit tests:
// boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide
assert_eq!(
- hex::encode(crate::keystore::copy_seed(&mut mock_hal).unwrap()),
+ hex::encode(crate::keystore::copy_seed(&mut mock_hal).await.unwrap()),
"19f1bcfccf3e9d497cd245cf864ff0d42216625258d4f68d56b571aceb329257"
);
assert_eq!(
- hex::encode(crate::keystore::copy_bip39_seed(&mut mock_hal).unwrap()),
+ hex::encode(
+ crate::keystore::copy_bip39_seed(&mut mock_hal)
+ .await
+ .unwrap()
+ ),
"257724bccc8858cfe565b456b01263a4a6a45184fab4531f5c199649207a74e74c399a01d4f957258c05cee818369b31404c884a4b7a29ff6886bae6700fb56a"
);
diff --git a/src/rust/bitbox02-rust/src/hww/api/set_password.rs b/src/rust/bitbox02-rust/src/hww/api/set_password.rs
index 7788aff..614646f 100644
--- a/src/rust/bitbox02-rust/src/hww/api/set_password.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/set_password.rs
@@ -24,11 +24,11 @@ pub async fn process(
return Err(Error::InvalidInput);
}
let password = password::enter_twice(hal).await?;
- if let Err(err) = keystore::create_and_store_seed(hal, &password, entropy) {
+ if let Err(err) = keystore::create_and_store_seed(hal, &password, entropy).await {
hal.ui().status(&format!("Error\n{:?}", err), false).await;
return Err(Error::Generic);
}
- let seed = keystore::copy_seed(hal)?;
+ let seed = keystore::copy_seed(hal).await?;
unlock::unlock_bip39(hal, &seed).await;
Ok(Response::Success(pb::Success {}))
}
@@ -71,7 +71,7 @@ mod tests {
);
assert_eq!(mock_hal.securechip.get_event_counter(), 6);
assert!(!keystore::is_locked());
- assert!(keystore::copy_seed(&mut mock_hal).unwrap().len() == 32);
+ assert!(keystore::copy_seed(&mut mock_hal).await.unwrap().len() == 32);
drop(mock_hal); // to remove mutable borrow of counter
assert_eq!(counter, 2);
@@ -97,7 +97,7 @@ mod tests {
Ok(Response::Success(pb::Success {}))
);
assert!(!keystore::is_locked());
- assert!(keystore::copy_seed(&mut mock_hal).unwrap().len() == 16);
+ assert!(keystore::copy_seed(&mut mock_hal).await.unwrap().len() == 16);
}
/// Invalid host entropy size.
diff --git a/src/rust/bitbox02-rust/src/hww/api/show_mnemonic.rs b/src/rust/bitbox02-rust/src/hww/api/show_mnemonic.rs
index 08effe9..c3b882f 100644
--- a/src/rust/bitbox02-rust/src/hww/api/show_mnemonic.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/show_mnemonic.rs
@@ -20,7 +20,7 @@ pub async fn process(hal: &mut impl crate::hal::Hal) -> Result<Response, Error>
let seed = if hal.memory().is_initialized() {
unlock::unlock_keystore(hal, "Unlock device", CanCancel::Yes).await?
} else {
- crate::keystore::copy_seed(hal)?
+ crate::keystore::copy_seed(hal).await?
};
crate::bip39::mnemonic_from_seed(&seed)?
@@ -82,6 +82,7 @@ mod tests {
.as_slice(),
"password",
)
+ .await
.unwrap();
assert!(!mock_hal.memory.is_initialized());
@@ -141,6 +142,7 @@ mod tests {
.as_slice(),
"password",
)
+ .await
.unwrap();
mock_hal.memory.set_initialized().unwrap();
@@ -207,6 +209,7 @@ mod tests {
.as_slice(),
"password",
)
+ .await
.unwrap();
mock_hal.memory.set_initialized().unwrap();
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 2b14e6c..3bf4d9d 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -172,7 +172,7 @@ struct RetainedEncryptedBuffer {
}
impl RetainedEncryptedBuffer {
- fn from_buffer(
+ async fn from_buffer(
hal: &mut impl KeystoreHal,
data: &[u8],
purpose: &'static str,
@@ -183,7 +183,8 @@ impl RetainedEncryptedBuffer {
&rand,
&format!("{}_in", purpose),
&format!("{}_out", purpose),
- )?;
+ )
+ .await?;
let iv_rand = random_32_bytes(hal)?;
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
let encrypted = bitbox_aes::encrypt_with_hmac(iv, &encryption_key, data);
@@ -194,13 +195,17 @@ impl RetainedEncryptedBuffer {
})
}
- fn decrypt(&self, hal: &mut impl KeystoreHal) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ async fn decrypt(
+ &self,
+ hal: &mut impl KeystoreHal,
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
let encryption_key = stretch_retained_seed_encryption_key(
hal,
&self.unstretched_encryption_key,
&format!("{}_in", self.purpose),
&format!("{}_out", self.purpose),
- )?;
+ )
+ .await?;
bitbox_aes::decrypt_with_hmac(&encryption_key, self.encrypted_seed.as_slice())
.map_err(|_| Error::Decrypt)
}
@@ -265,22 +270,23 @@ fn hash_seed(hal: &mut impl KeystoreHal, seed: &[u8]) -> Result<[u8; 32], Error>
Ok(Hmac::<sha256::Hash>::from_engine(engine).to_byte_array())
}
-fn retain_seed(hal: &mut impl KeystoreHal, seed: &[u8]) -> Result<(), Error> {
- RETAINED_SEED.write(Some(RetainedEncryptedBuffer::from_buffer(
- hal,
- seed,
- "keystore_retained_seed_access",
- )?));
+async fn retain_seed(hal: &mut impl KeystoreHal, seed: &[u8]) -> Result<(), Error> {
+ RETAINED_SEED.write(Some(
+ RetainedEncryptedBuffer::from_buffer(hal, seed, "keystore_retained_seed_access").await?,
+ ));
RETAINED_SEED_HASH.write(Some(hash_seed(hal, seed)?));
Ok(())
}
-fn retain_bip39_seed(hal: &mut impl KeystoreHal, bip39_seed: &[u8]) -> Result<(), Error> {
- RETAINED_BIP39_SEED.write(Some(RetainedEncryptedBuffer::from_buffer(
- hal,
- bip39_seed,
- "keystore_retained_bip39_seed_access",
- )?));
+async fn retain_bip39_seed(hal: &mut impl KeystoreHal, bip39_seed: &[u8]) -> Result<(), Error> {
+ RETAINED_BIP39_SEED.write(Some(
+ RetainedEncryptedBuffer::from_buffer(
+ hal,
+ bip39_seed,
+ "keystore_retained_bip39_seed_access",
+ )
+ .await?,
+ ));
Ok(())
}
@@ -299,7 +305,7 @@ pub fn default_password_stretch_algo(
}
/// Internal helper to encrypt a seed with a password and store it on flash
-fn encrypt_and_store_seed_internal(
+async fn encrypt_and_store_seed_internal(
hal: &mut impl crate::hal::Hal,
seed: &[u8],
password: &str,
@@ -337,12 +343,12 @@ fn encrypt_and_store_seed_internal(
return Err(Error::Memory);
}
- retain_seed(&mut KeystoreHalImpl::from_hal(hal), seed)
+ retain_seed(&mut KeystoreHalImpl::from_hal(hal), seed).await
}
/// Restores a seed. This also unlocks the keystore with this seed.
/// `password` is the password with which we encrypt the seed.
-pub fn encrypt_and_store_seed(
+pub async fn encrypt_and_store_seed(
hal: &mut impl crate::hal::Hal,
seed: &[u8],
password: &str,
@@ -350,11 +356,11 @@ pub fn encrypt_and_store_seed(
if hal.memory().is_initialized() {
return Err(Error::Memory);
}
- encrypt_and_store_seed_internal(hal, seed, password)
+ encrypt_and_store_seed_internal(hal, seed, password).await
}
/// Re-encrypts the seed with a (new) password
-pub fn re_encrypt_seed(
+pub async fn re_encrypt_seed(
hal: &mut impl crate::hal::Hal,
seed: &[u8],
new_password: &str,
@@ -367,13 +373,15 @@ pub fn re_encrypt_seed(
// 1. The secure chip's internal keys are regenerated with the new password
// 2. encrypt_and_store_seed_internal calls lock() which clears BIP39 seed and root fingerprint
// 3. We want to avoid forcing the user to re-enter their BIP39 passphrase
- let bip39_seed = copy_bip39_seed(hal).map_err(|_| Error::InvalidState)?;
+ let bip39_seed = copy_bip39_seed(hal)
+ .await
+ .map_err(|_| Error::InvalidState)?;
let root_fingerprint = ROOT_FINGERPRINT.read().ok_or(Error::InvalidState)?;
- encrypt_and_store_seed_internal(hal, seed, new_password)?;
+ encrypt_and_store_seed_internal(hal, seed, new_password).await?;
// Re-retain the bip39 seed and root fingerprint
- retain_bip39_seed(&mut KeystoreHalImpl::from_hal(hal), bip39_seed.as_slice())?;
+ retain_bip39_seed(&mut KeystoreHalImpl::from_hal(hal), bip39_seed.as_slice()).await?;
ROOT_FINGERPRINT.write(Some(root_fingerprint));
Ok(())
@@ -382,7 +390,7 @@ pub fn re_encrypt_seed(
/// Re-encrypts the seed with the newest (default) password stretching algorithm if it is not
/// already using it. The seed is retained after this function finishes, regardless of whether a
/// migration was performed.
-fn migrate_password_algo_and_retain_seed(
+async fn migrate_password_algo_and_retain_seed(
hal: &mut impl crate::hal::Hal,
seed: &[u8],
password: &str,
@@ -393,9 +401,9 @@ fn migrate_password_algo_and_retain_seed(
.get_encrypted_seed_and_hmac()
.map_err(|_| Error::Memory)?;
if stored_algo != default_algo {
- encrypt_and_store_seed_internal(hal, seed, password)
+ encrypt_and_store_seed_internal(hal, seed, password).await
} else {
- retain_seed(&mut KeystoreHalImpl::from_hal(hal), seed)
+ retain_seed(&mut KeystoreHalImpl::from_hal(hal), seed).await
}
}
@@ -471,7 +479,7 @@ pub async fn unlock(
panic!("Seed has suddenly changed. This should never happen.");
}
} else {
- migrate_password_algo_and_retain_seed(hal, &seed, password)?;
+ migrate_password_algo_and_retain_seed(hal, &seed, password).await?;
}
hal.eeprom().reset_unlock_attempts();
Ok(seed)
@@ -506,7 +514,7 @@ pub async fn unlock_bip39(
return Err(Error::Memory);
}
- retain_bip39_seed(hal, bip39_seed.as_slice())?;
+ retain_bip39_seed(hal, bip39_seed.as_slice()).await?;
// Store root fingerprint.
ROOT_FINGERPRINT.write(Some(root_fingerprint));
@@ -514,20 +522,24 @@ pub async fn unlock_bip39(
}
/// Returns a copy of the retained seed. Errors if the keystore is locked.
-pub fn copy_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+pub async fn copy_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
RETAINED_SEED
.read()
.ok_or(())?
.decrypt(&mut KeystoreHalImpl::from_hal(hal))
+ .await
.map_err(|_| ())
}
/// Returns a copy of the retained bip39 seed. Errors if the keystore is locked.
-pub fn copy_bip39_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+pub async fn copy_bip39_seed(
+ hal: &mut impl crate::hal::Hal,
+) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
RETAINED_BIP39_SEED
.read()
.ok_or(())?
.decrypt(&mut KeystoreHalImpl::from_hal(hal))
+ .await
.map_err(|_| ())
}
@@ -535,7 +547,7 @@ pub fn copy_bip39_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroiz
/// password. The size of the host entropy determines the size of the seed. Can be either 16 or 32
/// bytes, resulting in 12 or 24 BIP39 recovery words.
/// This also unlocks the keystore with the new seed.
-pub fn create_and_store_seed(
+pub async fn create_and_store_seed(
hal: &mut impl crate::hal::Hal,
password: &str,
host_entropy: &[u8],
@@ -565,18 +577,18 @@ pub fn create_and_store_seed(
seed[i] ^= hash_byte;
}
- encrypt_and_store_seed(hal, seed, password)
+ encrypt_and_store_seed(hal, seed, password).await
}
/// Returns the keystore's seed encoded as a BIP-39 mnemonic.
-pub fn get_bip39_mnemonic(
+pub async fn get_bip39_mnemonic(
hal: &mut impl crate::hal::Hal,
) -> Result<zeroize::Zeroizing<String>, ()> {
- crate::bip39::mnemonic_from_seed(©_seed(hal)?)
+ crate::bip39::mnemonic_from_seed(©_seed(hal).await?)
}
-fn get_xprv(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<bip32::Xprv, ()> {
- let bip39_seed = copy_bip39_seed(hal)?;
+async fn get_xprv(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<bip32::Xprv, ()> {
+ let bip39_seed = copy_bip39_seed(hal).await?;
let xprv: bip32::Xprv =
bitcoin::bip32::Xpriv::new_master(bitcoin::NetworkKind::Main, &bip39_seed)
.map_err(|_| ())?
@@ -589,23 +601,23 @@ fn get_xprv(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<bip32::Xp
}
/// Get the private key at the keypath.
-pub fn secp256k1_get_private_key(
+pub async fn secp256k1_get_private_key(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let xprv = get_xprv(hal, keypath)?;
+ let xprv = get_xprv(hal, keypath).await?;
Ok(zeroize::Zeroizing::new(
xprv.xprv.private_key.secret_bytes().to_vec(),
))
}
/// Get the private key at the keypath, computed twice to mitigate the risk of bitflips.
-pub fn secp256k1_get_private_key_twice(
+pub async fn secp256k1_get_private_key_twice(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let privkey = secp256k1_get_private_key(hal, keypath)?;
- if privkey == secp256k1_get_private_key(hal, keypath)? {
+ let privkey = secp256k1_get_private_key(hal, keypath).await?;
+ if privkey == secp256k1_get_private_key(hal, keypath).await? {
Ok(privkey)
} else {
Err(())
@@ -615,8 +627,11 @@ pub fn secp256k1_get_private_key_twice(
/// 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
/// derivation is allowed.
-pub fn get_xpub_once(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<bip32::Xpub, ()> {
- let xpriv = get_xprv(hal, keypath)?;
+pub async fn get_xpub_once(
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+) -> Result<bip32::Xpub, ()> {
+ let xpriv = get_xprv(hal, keypath).await?;
let xpub = bitcoin::bip32::Xpub::from_priv(SECP256K1, &xpriv.xprv);
Ok(bip32::Xpub::from(xpub))
}
@@ -624,9 +639,12 @@ pub fn get_xpub_once(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<
/// 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
/// derivation is allowed.
-pub fn get_xpub_twice(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<bip32::Xpub, ()> {
- let res1 = get_xpub_once(hal, keypath)?;
- let res2 = get_xpub_once(hal, keypath)?;
+pub async fn get_xpub_twice(
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+) -> Result<bip32::Xpub, ()> {
+ let res1 = get_xpub_once(hal, keypath).await?;
+ let res2 = get_xpub_once(hal, keypath).await?;
if res1 != res2 {
return Err(());
}
@@ -635,7 +653,7 @@ pub fn get_xpub_twice(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result
/// Gets multiple xpubs at once. This is better than multiple calls to `get_xpub_twice()` as it only
/// uses two secure chip operations in total, instead of two per xpub.
-pub fn get_xpubs_twice(
+pub async fn get_xpubs_twice(
hal: &mut impl crate::hal::Hal,
keypaths: &[&[u32]],
) -> Result<Vec<bip32::Xpub>, ()> {
@@ -647,8 +665,8 @@ pub fn get_xpubs_twice(
}
// We get the root xprv as a starting point (twice to mitigate bitflips), afterwards we don't
// need the securechip anymore.
- let xprv = get_xprv(hal, &[])?;
- let xprv2 = get_xprv(hal, &[])?;
+ let xprv = get_xprv(hal, &[]).await?;
+ let xprv2 = get_xprv(hal, &[]).await?;
let mut out = Vec::with_capacity(keypaths.len());
for keypath in keypaths {
@@ -688,7 +706,7 @@ pub fn root_fingerprint() -> Result<Vec<u8>, ()> {
/// Stretches the given encryption_key using the securechip. The resulting key is used to encrypt
/// the retained seed or bip39 seed.
-pub fn stretch_retained_seed_encryption_key(
+pub async fn stretch_retained_seed_encryption_key(
hal: &mut impl KeystoreHal,
encryption_key: &[u8; 32],
purpose_in: &str,
@@ -697,7 +715,7 @@ pub fn stretch_retained_seed_encryption_key(
let salted_in = bitbox_core_utils::salt::hash_data(hal.memory(), encryption_key, purpose_in)
.map_err(|_| Error::Salt)?;
- let kdf = hal.securechip().kdf(&salted_in)?;
+ let kdf = hal.securechip().kdf(&salted_in).await?;
let salted_out = bitbox_core_utils::salt::hash_data(hal.memory(), encryption_key, purpose_out)
.map_err(|_| Error::Salt)?;
@@ -719,11 +737,11 @@ pub extern "C" fn rust_keystore_is_locked() -> bool {
is_locked()
}
-fn bip85_entropy(
+async fn bip85_entropy(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let priv_key = secp256k1_get_private_key_twice(hal, keypath)?;
+ let priv_key = secp256k1_get_private_key_twice(hal, keypath).await?;
let mut engine = HmacEngine::<sha512::Hash>::new(b"bip-entropy-from-k");
engine.input(&priv_key);
@@ -736,7 +754,7 @@ fn bip85_entropy(
/// https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39
/// `words` must be 12, 18 or 24.
/// `index` must be smaller than `bip32::HARDENED`.
-pub fn bip85_bip39(
+pub async fn bip85_bip39(
hal: &mut impl crate::hal::Hal,
words: u32,
index: u32,
@@ -760,7 +778,7 @@ pub fn bip85_bip39(
index + HARDENED,
];
- let entropy = bip85_entropy(hal, &keypath)?;
+ let entropy = bip85_entropy(hal, &keypath).await?;
crate::bip39::mnemonic_from_seed(&entropy[..seed_size])
}
@@ -769,7 +787,7 @@ pub fn bip85_bip39(
/// 'LN'). https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39
/// Restricted to 16 byte output entropy.
/// `index` must be smaller than `bip32::HARDENED`.
-pub fn bip85_ln(
+pub async fn bip85_ln(
hal: &mut impl crate::hal::Hal,
index: u32,
) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
@@ -784,7 +802,7 @@ pub fn bip85_ln(
index + HARDENED,
];
- let mut entropy = bip85_entropy(hal, &keypath)?;
+ let mut entropy = bip85_entropy(hal, &keypath).await?;
entropy.truncate(16);
Ok(entropy)
}
@@ -793,13 +811,13 @@ pub fn bip85_ln(
///
/// Sign a message using the private key at the keypath, which is optionally tweaked with the given
/// tweak.
-pub fn secp256k1_schnorr_sign(
+pub async fn secp256k1_schnorr_sign(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
msg: &[u8; 32],
tweak: Option<&[u8; 32]>,
) -> Result<[u8; 64], ()> {
- let private_key = secp256k1_get_private_key(hal, keypath)?;
+ let private_key = secp256k1_get_private_key(hal, keypath).await?;
let mut keypair =
bitcoin::secp256k1::Keypair::from_seckey_slice(SECP256K1, &private_key).map_err(|_| ())?;
@@ -823,8 +841,10 @@ pub fn secp256k1_schnorr_sign(
/// Get the seed to be used for u2f
#[cfg(feature = "app-u2f")]
-pub fn get_u2f_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let bip39_seed = copy_bip39_seed(hal)?;
+pub async fn get_u2f_seed(
+ hal: &mut impl crate::hal::Hal,
+) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+ let bip39_seed = copy_bip39_seed(hal).await?;
let mut engine = HmacEngine::<bitcoin::hashes::sha256::Hash>::new(&bip39_seed);
// Null-terminator for backwards compatibility from the time when this was coded in C.
@@ -840,7 +860,11 @@ pub mod testing {
pub fn mock_unlocked_using_mnemonic(mnemonic: &str, passphrase: &str) {
let mut mock_hal = crate::hal::testing::TestingHal::new();
let seed = crate::bip39::mnemonic_to_seed(mnemonic).unwrap();
- super::retain_seed(&mut super::KeystoreHalImpl::from_hal(&mut mock_hal), &seed).unwrap();
+ util::bb02_async::block_on(super::retain_seed(
+ &mut super::KeystoreHalImpl::from_hal(&mut mock_hal),
+ &seed,
+ ))
+ .unwrap();
util::bb02_async::block_on(super::unlock_bip39(
&mut super::KeystoreHalImpl::from_hal(&mut mock_hal),
&seed,
@@ -872,8 +896,8 @@ mod tests {
use bitcoin::secp256k1;
- #[test]
- fn test_copy_seed() {
+ #[async_test::test]
+ async fn test_copy_seed() {
let mut mock_hal = TestingHal::new();
// 12 words
mock_unlocked_using_mnemonic(
@@ -881,7 +905,7 @@ mod tests {
"",
);
assert_eq!(
- copy_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_seed(&mut mock_hal).await.unwrap().as_slice(),
b"\xe9\xa6\x3f\xcd\x3a\x4d\x48\x98\x20\xa6\x63\x79\x2b\xad\xf6\xdd",
);
@@ -891,7 +915,7 @@ mod tests {
"",
);
assert_eq!(
- copy_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_seed(&mut mock_hal).await.unwrap().as_slice(),
b"\xad\xf4\x07\x8e\x0e\x0c\xb1\x4c\x34\xd6\xd6\xf2\x82\x6a\x57\xc1\x82\x06\x6a\xbb\xcd\x95\x84\xcf",
);
@@ -900,23 +924,23 @@ mod tests {
"",
);
assert_eq!(
- copy_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_seed(&mut mock_hal).await.unwrap().as_slice(),
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",
);
}
- #[test]
- fn test_encrypt_and_store_seed_invalid_size() {
+ #[async_test::test]
+ async fn test_encrypt_and_store_seed_invalid_size() {
mock_memory();
lock();
assert!(matches!(
- encrypt_and_store_seed(&mut TestingHal::new(), &[0; 31], "foo"),
+ encrypt_and_store_seed(&mut TestingHal::new(), &[0; 31], "foo").await,
Err(Error::SeedSize)
));
}
- #[test]
- fn test_create_and_store_seed() {
+ #[async_test::test]
+ async fn test_create_and_store_seed() {
let mock_salt_root =
hex!("3333333333333333444444444444444411111111111111112222222222222222");
@@ -929,7 +953,7 @@ mod tests {
// Invalid seed lengths
for size in [8, 24, 40] {
assert!(matches!(
- create_and_store_seed(&mut hal, "password", &host_entropy[..size]),
+ create_and_store_seed(&mut hal, "password", &host_entropy[..size]).await,
Err(Error::SeedSize)
));
}
@@ -963,9 +987,13 @@ mod tests {
hal.random.mock_next(seed_random);
hal.securechip.mock_random(securechip_random);
- assert!(create_and_store_seed(&mut hal, "password", &host_entropy[..size]).is_ok());
+ assert!(
+ create_and_store_seed(&mut hal, "password", &host_entropy[..size])
+ .await
+ .is_ok()
+ );
assert_eq!(
- copy_seed(&mut hal).unwrap().as_slice(),
+ copy_seed(&mut hal).await.unwrap().as_slice(),
&expected_seed[..size]
);
// Check the seed has been stored encrypted with the expected encryption key.
@@ -984,8 +1012,8 @@ mod tests {
}
}
- #[test]
- fn test_re_encrypt_seed() {
+ #[async_test::test]
+ async fn test_re_encrypt_seed() {
mock_memory();
lock();
@@ -994,7 +1022,7 @@ mod tests {
// Try to re-encrypt without seeding first
assert!(matches!(
- re_encrypt_seed(&mut mock_hal, &seed, "new_password"),
+ re_encrypt_seed(&mut mock_hal, &seed, "new_password").await,
Err(Error::Unseeded)
));
}
@@ -1008,7 +1036,11 @@ mod tests {
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
// Step 1: Set up device with initial password
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "old_password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "old_password")
+ .await
+ .is_ok()
+ );
// Step 2: Unlock with initial password and set up BIP39
let unlocked_seed = unlock(&mut mock_hal, "old_password").await.unwrap();
@@ -1026,7 +1058,11 @@ mod tests {
);
// Step 3: Re-encrypt with new password
- assert!(re_encrypt_seed(&mut mock_hal, &seed, "new_password").is_ok());
+ assert!(
+ re_encrypt_seed(&mut mock_hal, &seed, "new_password")
+ .await
+ .is_ok()
+ );
// Step 4: Lock and verify old password no longer works
lock();
@@ -1049,7 +1085,11 @@ mod tests {
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
// Initial setup
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password1").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password1")
+ .await
+ .is_ok()
+ );
assert!(
unlock_bip39(
@@ -1062,22 +1102,26 @@ mod tests {
.is_ok()
);
- let seed_reference = copy_seed(&mut mock_hal).unwrap();
- let bip39_seed_reference = copy_bip39_seed(&mut mock_hal).unwrap();
+ let seed_reference = copy_seed(&mut mock_hal).await.unwrap();
+ let bip39_seed_reference = copy_bip39_seed(&mut mock_hal).await.unwrap();
let root_fingerprint_reference = root_fingerprint().unwrap();
// Re-encrypt multiple times
for new_password in ["password2", "password3", "password4"] {
// re-encrypt
- assert!(re_encrypt_seed(&mut mock_hal, &seed_reference, new_password).is_ok());
+ assert!(
+ re_encrypt_seed(&mut mock_hal, &seed_reference, new_password)
+ .await
+ .is_ok()
+ );
// Verify everything is still there and correct
assert_eq!(
- copy_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_seed(&mut mock_hal).await.unwrap().as_slice(),
seed_reference.as_slice()
);
assert_eq!(
- copy_bip39_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_bip39_seed(&mut mock_hal).await.unwrap().as_slice(),
bip39_seed_reference.as_slice()
);
assert_eq!(root_fingerprint().unwrap(), root_fingerprint_reference);
@@ -1093,7 +1137,11 @@ mod tests {
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
// Initial setup
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .is_ok()
+ );
unlock(&mut mock_hal, "password").await.unwrap();
assert!(
@@ -1109,13 +1157,13 @@ mod tests {
// Try to re-encrypt with invalid seed size
assert!(matches!(
- re_encrypt_seed(&mut mock_hal, &[0u8; 31], "new_password"),
+ re_encrypt_seed(&mut mock_hal, &[0u8; 31], "new_password").await,
Err(Error::SeedSize)
));
}
- #[test]
- fn test_retain_bip39_seed() {
+ #[async_test::test]
+ async fn test_retain_bip39_seed() {
mock_memory();
lock();
@@ -1125,20 +1173,22 @@ mod tests {
);
// Before retention, should not be available
- assert!(copy_bip39_seed(&mut mock_hal).is_err());
+ assert!(copy_bip39_seed(&mut mock_hal).await.is_err());
// Retain the BIP39 seed
assert!(
- retain_bip39_seed(&mut KeystoreHalImpl::from_hal(&mut mock_hal), &bip39_seed).is_ok()
+ retain_bip39_seed(&mut KeystoreHalImpl::from_hal(&mut mock_hal), &bip39_seed)
+ .await
+ .is_ok()
);
// Should now be available
- let retrieved = copy_bip39_seed(&mut mock_hal).unwrap();
+ let retrieved = copy_bip39_seed(&mut mock_hal).await.unwrap();
assert_eq!(retrieved.as_slice(), bip39_seed.as_slice());
}
- #[test]
- fn test_retain_bip39_seed_overwrites_previous() {
+ #[async_test::test]
+ async fn test_retain_bip39_seed_overwrites_previous() {
mock_memory();
lock();
let mut mock_hal = TestingHal::new();
@@ -1151,19 +1201,23 @@ mod tests {
// Retain first seed
assert!(
- retain_bip39_seed(&mut KeystoreHalImpl::from_hal(&mut mock_hal), &bip39_seed1).is_ok()
+ retain_bip39_seed(&mut KeystoreHalImpl::from_hal(&mut mock_hal), &bip39_seed1)
+ .await
+ .is_ok()
);
assert_eq!(
- copy_bip39_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_bip39_seed(&mut mock_hal).await.unwrap().as_slice(),
bip39_seed1.as_slice()
);
// Retain second seed (should overwrite)
assert!(
- retain_bip39_seed(&mut KeystoreHalImpl::from_hal(&mut mock_hal), &bip39_seed2).is_ok()
+ retain_bip39_seed(&mut KeystoreHalImpl::from_hal(&mut mock_hal), &bip39_seed2)
+ .await
+ .is_ok()
);
assert_eq!(
- copy_bip39_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_bip39_seed(&mut mock_hal).await.unwrap().as_slice(),
bip39_seed2.as_slice()
);
}
@@ -1171,18 +1225,26 @@ mod tests {
// This tests that you can create a keystore, unlock it, and then do this again. This is an
// expected workflow for when the wallet setup process is restarted after seeding and unlocking,
// but before creating a backup, in which case a new seed is created.
- #[test]
- fn test_create_and_unlock_twice() {
+ #[async_test::test]
+ async fn test_create_and_unlock_twice() {
mock_memory();
lock();
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
let seed2 = hex!("c28135734876aff9ccf4f1d60df8d19a0a38fd02085883f65fc608eb769a635d");
- assert!(encrypt_and_store_seed(&mut TestingHal::new(), &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut TestingHal::new(), &seed, "password")
+ .await
+ .is_ok()
+ );
// Create new (different) seed.
- assert!(encrypt_and_store_seed(&mut TestingHal::new(), &seed2, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut TestingHal::new(), &seed2, "password")
+ .await
+ .is_ok()
+ );
assert_eq!(
- copy_seed(&mut TestingHal::new()).unwrap().as_slice(),
+ copy_seed(&mut TestingHal::new()).await.unwrap().as_slice(),
&seed2
);
}
@@ -1194,7 +1256,11 @@ mod tests {
assert!(is_locked());
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .is_ok()
+ );
assert!(is_locked()); // still locked, it is only unlocked after unlock_bip39.
assert!(
unlock_bip39(
@@ -1229,7 +1295,11 @@ mod tests {
hex!("3333333333333333444444444444444411111111111111112222222222222222");
mock_hal.memory.set_salt_root(&mock_salt_root);
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .is_ok()
+ );
lock();
// Mock random value used for creating the unstretched seed.
@@ -1282,7 +1352,7 @@ mod tests {
// Still seeded.
assert!(mock_hal.memory.is_seeded());
// Wrong password does not lock the keystore again if already unlocked.
- assert!(copy_seed(&mut mock_hal).is_ok());
+ assert!(copy_seed(&mut mock_hal).await.is_ok());
}
// Last attempt, triggers reset.
assert!(matches!(
@@ -1291,7 +1361,7 @@ mod tests {
));
// Last wrong attempt locks & resets. There is no more seed.
assert!(!mock_hal.memory.is_seeded());
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
assert!(matches!(
unlock(&mut mock_hal, "password").await,
Err(Error::Unseeded)
@@ -1310,10 +1380,14 @@ mod tests {
hex!("3333333333333333444444444444444411111111111111112222222222222222");
mock_hal.memory.set_salt_root(&mock_salt_root);
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .is_ok()
+ );
lock();
assert!(is_locked());
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
for attempt in 1..MAX_UNLOCK_ATTEMPTS {
assert!(matches!(
@@ -1326,7 +1400,7 @@ mod tests {
MAX_UNLOCK_ATTEMPTS - attempt
);
assert!(is_locked());
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
assert!(mock_hal.memory.is_seeded());
}
@@ -1335,7 +1409,7 @@ mod tests {
Err(Error::MaxAttemptsExceeded)
));
assert!(is_locked());
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
assert!(!mock_hal.memory.is_seeded());
assert!(matches!(
unlock(&mut mock_hal, "password").await,
@@ -1358,7 +1432,11 @@ mod tests {
hex!("3333333333333333444444444444444411111111111111112222222222222222");
mock_hal.memory.set_salt_root(&mock_salt_root);
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .is_ok()
+ );
lock();
assert!(is_locked());
@@ -1374,7 +1452,7 @@ mod tests {
Err(Error::MaxAttemptsExceeded)
));
assert!(is_locked());
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
assert!(!mock_hal.memory.is_seeded());
}
@@ -1392,7 +1470,11 @@ mod tests {
hex!("3333333333333333444444444444444411111111111111112222222222222222");
mock_hal.memory.set_salt_root(&mock_salt_root);
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .is_ok()
+ );
lock();
async fn wrong_attempt(hal: &mut impl crate::hal::Hal) {
@@ -1404,19 +1486,19 @@ mod tests {
}
wrong_attempt(&mut mock_hal).await;
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
assert_eq!(
unlock(&mut mock_hal, "password").await.unwrap().as_slice(),
seed
);
- assert!(copy_seed(&mut mock_hal).is_ok());
+ assert!(copy_seed(&mut mock_hal).await.is_ok());
lock();
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
wrong_attempt(&mut mock_hal).await;
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
assert!(mock_hal.memory.is_seeded());
}
@@ -1434,14 +1516,18 @@ mod tests {
hex!("3333333333333333444444444444444411111111111111112222222222222222");
mock_hal.memory.set_salt_root(&mock_salt_root);
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .is_ok()
+ );
lock();
assert_eq!(
unlock(&mut mock_hal, "password").await.unwrap().as_slice(),
seed
);
- assert!(copy_seed(&mut mock_hal).is_ok());
+ assert!(copy_seed(&mut mock_hal).await.is_ok());
async fn wrong_attempt(hal: &mut impl crate::hal::Hal) {
assert!(matches!(
@@ -1452,16 +1538,16 @@ mod tests {
}
wrong_attempt(&mut mock_hal).await;
- assert!(copy_seed(&mut mock_hal).is_ok());
+ assert!(copy_seed(&mut mock_hal).await.is_ok());
assert_eq!(
unlock(&mut mock_hal, "password").await.unwrap().as_slice(),
seed
);
- assert!(copy_seed(&mut mock_hal).is_ok());
+ assert!(copy_seed(&mut mock_hal).await.is_ok());
wrong_attempt(&mut mock_hal).await;
- assert!(copy_seed(&mut mock_hal).is_ok());
+ assert!(copy_seed(&mut mock_hal).await.is_ok());
assert!(mock_hal.memory.is_seeded());
}
@@ -1511,7 +1597,7 @@ mod tests {
assert_eq!(mock_hal.securechip.get_event_counter(), 9);
// Check the seed was retained again after migration.
- assert_eq!(copy_seed(&mut mock_hal).unwrap().as_slice(), seed);
+ assert_eq!(copy_seed(&mut mock_hal).await.unwrap().as_slice(), seed);
// Check the seed now uses the new algo.
let (_, stored_algo) = mock_hal.memory.get_encrypted_seed_and_hmac().unwrap();
assert_eq!(stored_algo, memory::PasswordStretchAlgo::V1);
@@ -1543,7 +1629,11 @@ mod tests {
mock_hal.memory.set_salt_root(&mock_salt_root);
assert!(root_fingerprint().is_err());
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed, "password")
+ .await
+ .is_ok()
+ );
assert!(root_fingerprint().is_err());
// Incorrect seed passed
assert!(
@@ -1582,7 +1672,7 @@ mod tests {
);
assert_eq!(
- copy_bip39_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_bip39_seed(&mut mock_hal).await.unwrap().as_slice(),
expected_bip39_seed.as_slice()
);
@@ -1603,14 +1693,18 @@ mod tests {
assert_eq!(decrypted.as_slice(), expected_bip39_seed.as_slice());
}
- #[test]
- fn test_secp256k1_get_private_key() {
+ #[async_test::test]
+ async fn test_secp256k1_get_private_key() {
lock();
let mut mock_hal = TestingHal::new();
let keypath = &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0];
- assert!(secp256k1_get_private_key(&mut mock_hal, keypath).is_err());
+ assert!(
+ secp256k1_get_private_key(&mut mock_hal, keypath)
+ .await
+ .is_err()
+ );
mock_unlocked_using_mnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
@@ -1620,6 +1714,7 @@ mod tests {
mock_hal.securechip.event_counter_reset();
assert_eq!(
secp256k1_get_private_key(&mut mock_hal, keypath)
+ .await
.unwrap()
.as_slice(),
hex!("4604b4b710fe91f584fff084e1a9159fe4f8408fff380596a604948474ce4fa3"),
@@ -1627,14 +1722,18 @@ mod tests {
assert_eq!(mock_hal.securechip.get_event_counter(), 1);
}
- #[test]
- fn test_secp256k1_get_private_key_twice() {
+ #[async_test::test]
+ async fn test_secp256k1_get_private_key_twice() {
lock();
let mut mock_hal = TestingHal::new();
let keypath = &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0];
- assert!(secp256k1_get_private_key_twice(&mut mock_hal, keypath).is_err());
+ assert!(
+ secp256k1_get_private_key_twice(&mut mock_hal, keypath)
+ .await
+ .is_err()
+ );
mock_unlocked_using_mnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
@@ -1644,6 +1743,7 @@ mod tests {
mock_hal.securechip.event_counter_reset();
assert_eq!(
secp256k1_get_private_key_twice(&mut mock_hal, keypath)
+ .await
.unwrap()
.as_slice(),
hex!("4604b4b710fe91f584fff084e1a9159fe4f8408fff380596a604948474ce4fa3"),
@@ -1651,21 +1751,24 @@ mod tests {
assert_eq!(mock_hal.securechip.get_event_counter(), 2);
}
- #[test]
- fn test_get_bip39_mnemonic() {
+ #[async_test::test]
+ async fn test_get_bip39_mnemonic() {
lock();
- assert!(get_bip39_mnemonic(&mut TestingHal::new()).is_err());
+ assert!(get_bip39_mnemonic(&mut TestingHal::new()).await.is_err());
mock_unlocked();
assert_eq!(
- get_bip39_mnemonic(&mut TestingHal::new()).unwrap().as_str(),
+ get_bip39_mnemonic(&mut TestingHal::new())
+ .await
+ .unwrap()
+ .as_str(),
TEST_MNEMONIC
);
}
- #[test]
- fn test_get_xpub_twice() {
+ #[async_test::test]
+ async fn test_get_xpub_twice() {
let keypath = &[44 + HARDENED, 0 + HARDENED, 0 + HARDENED];
// Also test with unhardened and non-zero elements.
let keypath_5 = &[44 + HARDENED, 1 + HARDENED, 10 + HARDENED, 1, 100];
@@ -1673,7 +1776,7 @@ mod tests {
let mut mock_hal = TestingHal::new();
lock();
- assert!(get_xpub_twice(&mut mock_hal, keypath).is_err());
+ assert!(get_xpub_twice(&mut mock_hal, keypath).await.is_err());
// 24 words
mock_unlocked_using_mnemonic(
@@ -1685,6 +1788,7 @@ mod tests {
assert_eq!(
get_xpub_twice(&mut mock_hal, &[])
+ .await
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -1695,6 +1799,7 @@ mod tests {
assert_eq!(
get_xpub_twice(&mut mock_hal, keypath)
+ .await
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -1702,6 +1807,7 @@ mod tests {
);
assert_eq!(
get_xpub_twice(&mut mock_hal, keypath_5)
+ .await
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -1715,6 +1821,7 @@ mod tests {
);
assert_eq!(
get_xpub_twice(&mut mock_hal, keypath)
+ .await
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -1728,6 +1835,7 @@ mod tests {
);
assert_eq!(
get_xpub_twice(&mut mock_hal, keypath)
+ .await
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -1735,11 +1843,11 @@ mod tests {
)
}
- #[test]
- fn test_get_xpubs_twice() {
+ #[async_test::test]
+ async fn test_get_xpubs_twice() {
lock();
- assert!(get_xpubs_twice(&mut TestingHal::new(), &[]).is_err());
+ assert!(get_xpubs_twice(&mut TestingHal::new(), &[]).await.is_err());
mock_unlocked_using_mnemonic(
"sleep own lobster state clean thrive tail exist cactus bitter pass soccer clinic riot dream turkey before sport action praise tunnel hood donate man",
@@ -1747,8 +1855,9 @@ mod tests {
);
// Helper to convert to strings.
- fn get(hal: &mut impl crate::hal::Hal, keypaths: &[&[u32]]) -> Vec<String> {
+ async fn get(hal: &mut impl crate::hal::Hal, keypaths: &[&[u32]]) -> Vec<String> {
get_xpubs_twice(hal, keypaths)
+ .await
.unwrap()
.iter()
.map(|xpub| xpub.serialize_str(bip32::XPubType::Xpub).unwrap())
@@ -1758,7 +1867,12 @@ mod tests {
let mut mock_hal = TestingHal::new();
mock_hal.securechip.event_counter_reset();
- assert!(get_xpubs_twice(&mut mock_hal, &[]).unwrap().is_empty());
+ assert!(
+ get_xpubs_twice(&mut mock_hal, &[])
+ .await
+ .unwrap()
+ .is_empty()
+ );
assert_eq!(mock_hal.securechip.get_event_counter(), 0);
mock_hal.securechip.event_counter_reset();
@@ -1769,7 +1883,8 @@ mod tests {
&[84 + HARDENED, HARDENED, HARDENED],
&[86 + HARDENED, HARDENED, HARDENED],
]
- ),
+ )
+ .await,
vec![
"xpub6CNbmcHwZDudAvCAZVE5kejUoFD63mbkRbRMA2HoF9oNWsCofni87gJKp31qZJ9FsCMQR2vK9AS51mT8dgUMGsHW6SfaAKb4eSzpqJn7zwK",
"xpub6CGwpj8iQNuzSeeEKF4yuQt32fpLqfHj7sUfFH4uW34DoctWPksxAdjNYC9KwYgwA149B7SDdcLH1aFmucRcjBL4U6piN7HgaiFCBsToamH",
@@ -1800,8 +1915,8 @@ mod tests {
assert_eq!(root_fingerprint(), Err(()));
}
- #[test]
- fn test_stretch_retained_seed_encryption_key_success() {
+ #[async_test::test]
+ async fn test_stretch_retained_seed_encryption_key_success() {
mock_memory();
let mut mock_hal = TestingHal::new();
let salt_root = hex!("0000000000000000111111111111111122222222222222223333333333333333");
@@ -1816,14 +1931,15 @@ mod tests {
"keystore_retained_seed_access_in",
"keystore_retained_seed_access_out",
)
+ .await
.unwrap();
let expected = hex!("b6b20683810aee16b5603ae95d14eaae5ae2c8d9df9b66e1b67c698e627bb208");
assert_eq!(stretched.as_slice(), expected.as_slice());
}
- #[test]
- fn test_stretch_retained_seed_encryption_key_salt_error() {
+ #[async_test::test]
+ async fn test_stretch_retained_seed_encryption_key_salt_error() {
mock_memory();
let mut mock_hal = TestingHal::new();
mock_hal.memory.set_salt_root(&[0xffu8; 32]);
@@ -1834,14 +1950,15 @@ mod tests {
&encryption_key,
"purpose_in",
"purpose_out",
- );
+ )
+ .await;
assert!(matches!(result, Err(Error::Salt)));
}
- #[test]
- fn test_bip85_bip39() {
+ #[async_test::test]
+ async fn test_bip85_bip39() {
lock();
- assert!(bip85_bip39(&mut TestingHal::new(), 12, 0).is_err());
+ assert!(bip85_bip39(&mut TestingHal::new(), 12, 0).await.is_err());
// Test fixtures generated using:
// `docker build -t bip85 .`
@@ -1858,38 +1975,55 @@ mod tests {
);
assert_eq!(
- bip85_bip39(&mut TestingHal::new(), 12, 0).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 12, 0)
+ .await
+ .unwrap()
+ .as_ref() as &str,
"slender whip place siren tissue chaos ankle door only assume tent shallow",
);
assert_eq!(
- bip85_bip39(&mut TestingHal::new(), 12, 1).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 12, 1)
+ .await
+ .unwrap()
+ .as_ref() as &str,
"income soft level reunion height pony crane use unfold win keen satisfy",
);
assert_eq!(
bip85_bip39(&mut TestingHal::new(), 12, HARDENED - 1)
+ .await
.unwrap()
.as_ref() as &str,
"carry build nerve market domain energy mistake script puzzle replace mixture idea",
);
assert_eq!(
- bip85_bip39(&mut TestingHal::new(), 18, 0).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 18, 0)
+ .await
+ .unwrap()
+ .as_ref() as &str,
"enact peasant tragic habit expand jar senior melody coin acid logic upper soccer later earn napkin planet stereo",
);
assert_eq!(
- bip85_bip39(&mut TestingHal::new(), 24, 0).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 24, 0)
+ .await
+ .unwrap()
+ .as_ref() as &str,
"cabbage wink october add anchor mean tray surprise gasp tomorrow garbage habit beyond merge where arrive beef gentle animal office drop panel chest size",
);
// Invalid number of words.
- assert!(bip85_bip39(&mut TestingHal::new(), 10, 0).is_err());
+ assert!(bip85_bip39(&mut TestingHal::new(), 10, 0).await.is_err());
// Index too high.
- assert!(bip85_bip39(&mut TestingHal::new(), 12, HARDENED).is_err());
+ assert!(
+ bip85_bip39(&mut TestingHal::new(), 12, HARDENED)
+ .await
+ .is_err()
+ );
}
- #[test]
- fn test_bip85_ln() {
+ #[async_test::test]
+ async fn test_bip85_ln() {
lock();
- assert!(bip85_ln(&mut TestingHal::new(), 0).is_err());
+ assert!(bip85_ln(&mut TestingHal::new(), 0).await.is_err());
mock_unlocked_using_mnemonic(
"virtual weapon code laptop defy cricket vicious target wave leopard garden give",
@@ -1897,22 +2031,29 @@ mod tests {
);
assert_eq!(
- bip85_ln(&mut TestingHal::new(), 0).unwrap().as_slice(),
+ bip85_ln(&mut TestingHal::new(), 0)
+ .await
+ .unwrap()
+ .as_slice(),
hex!("3a5f3b888aab88e2a9ab991b60a03ed8"),
);
assert_eq!(
- bip85_ln(&mut TestingHal::new(), 1).unwrap().as_slice(),
+ bip85_ln(&mut TestingHal::new(), 1)
+ .await
+ .unwrap()
+ .as_slice(),
hex!("e7d9ce75f8cb17570e665417b47fa0be"),
);
assert_eq!(
bip85_ln(&mut TestingHal::new(), HARDENED - 1)
+ .await
.unwrap()
.as_slice(),
hex!("1f3b75ea252749700a1e453469148ca6"),
);
// Index too high.
- assert!(bip85_ln(&mut TestingHal::new(), HARDENED).is_err());
+ assert!(bip85_ln(&mut TestingHal::new(), HARDENED).await.is_err());
}
#[async_test::test]
@@ -1984,7 +2125,11 @@ mod tests {
);
mock_hal.securechip.event_counter_reset();
- assert!(encrypt_and_store_seed(&mut mock_hal, seed, "foo").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, seed, "foo")
+ .await
+ .is_ok()
+ );
assert_eq!(mock_hal.securechip.get_event_counter(), 4);
assert!(is_locked());
@@ -2004,13 +2149,13 @@ mod tests {
assert!(!is_locked());
assert_eq!(
- get_bip39_mnemonic(&mut mock_hal).unwrap().as_str(),
+ get_bip39_mnemonic(&mut mock_hal).await.unwrap().as_str(),
test.expected_mnemonic,
);
let keypath = &[44 + HARDENED, 0 + HARDENED, 0 + HARDENED];
mock_hal.securechip.event_counter_reset();
- let xpub = get_xpub_once(&mut mock_hal, keypath).unwrap();
+ let xpub = get_xpub_once(&mut mock_hal, keypath).await.unwrap();
assert_eq!(mock_hal.securechip.get_event_counter(), 1);
assert_eq!(
@@ -2018,14 +2163,14 @@ mod tests {
test.expected_xpub,
);
assert_eq!(
- get_u2f_seed(&mut mock_hal).unwrap().as_slice(),
+ get_u2f_seed(&mut mock_hal).await.unwrap().as_slice(),
test.expected_u2f_seed
);
}
}
- #[test]
- fn test_secp256k1_antiklepto_protocol() {
+ #[async_test::test]
+ async fn test_secp256k1_antiklepto_protocol() {
mock_unlocked();
let mut keypath = [84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0];
@@ -2046,7 +2191,9 @@ mod tests {
let host_commitment: [u8; 32] = host_commitment_vec.try_into().unwrap();
// Get pubkey at keypath.
- let private_key = secp256k1_get_private_key(&mut TestingHal::new(), &keypath).unwrap();
+ let private_key = secp256k1_get_private_key(&mut TestingHal::new(), &keypath)
+ .await
+ .unwrap();
let private_key_bytes: [u8; 32] = private_key.as_slice().try_into().unwrap();
let secret_key = secp256k1::SecretKey::from_slice(&private_key_bytes).unwrap();
let public_key = secret_key.public_key(SECP256K1);
@@ -2091,8 +2238,8 @@ mod tests {
}
}
- #[test]
- fn test_secp256k1_schnorr_sign() {
+ #[async_test::test]
+ async fn test_secp256k1_schnorr_sign() {
mock_unlocked_using_mnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"",
@@ -2109,7 +2256,9 @@ mod tests {
let mut mock_hal = TestingHal::new();
mock_hal.securechip.event_counter_reset();
- let sig = secp256k1_schnorr_sign(&mut mock_hal, &keypath, &msg, None).unwrap();
+ let sig = secp256k1_schnorr_sign(&mut mock_hal, &keypath, &msg, None)
+ .await
+ .unwrap();
assert_eq!(mock_hal.securechip.get_event_counter(), 1);
assert!(
@@ -2130,6 +2279,7 @@ mod tests {
let (tweaked_pubkey, _) = expected_pubkey.add_tweak(SECP256K1, &tweak).unwrap();
let mut mock_hal = TestingHal::new();
let sig = secp256k1_schnorr_sign(&mut mock_hal, &keypath, &msg, Some(&tweak.to_be_bytes()))
+ .await
.unwrap();
assert!(
SECP256K1
@@ -2155,17 +2305,21 @@ mod tests {
// Can repeat until initialized - initialized means backup has been created.
for _ in 0..2 {
- assert!(encrypt_and_store_seed(&mut mock_hal, &seed[..seed_size], "foo").is_ok());
+ assert!(
+ encrypt_and_store_seed(&mut mock_hal, &seed[..seed_size], "foo")
+ .await
+ .is_ok()
+ );
}
// Also unlocks, so we can get the retained seed.
assert_eq!(
- copy_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_seed(&mut mock_hal).await.unwrap().as_slice(),
&seed[..seed_size]
);
lock();
// Can't get seed before unlock.
- assert!(copy_seed(&mut mock_hal).is_err());
+ assert!(copy_seed(&mut mock_hal).await.is_err());
// Wrong password.
assert!(matches!(
@@ -2182,14 +2336,14 @@ mod tests {
);
}
assert_eq!(
- copy_seed(&mut mock_hal).unwrap().as_slice(),
+ copy_seed(&mut mock_hal).await.unwrap().as_slice(),
&seed[..seed_size]
);
// Can't store new seed once initialized.
mock_hal.memory.set_initialized().unwrap();
assert!(matches!(
- encrypt_and_store_seed(&mut mock_hal, &seed[..seed_size], "foo"),
+ encrypt_and_store_seed(&mut mock_hal, &seed[..seed_size], "foo").await,
Err(Error::Memory)
));
}
diff --git a/src/rust/bitbox02-rust/src/keystore/ed25519.rs b/src/rust/bitbox02-rust/src/keystore/ed25519.rs
index db61d1f..36d49f0 100644
--- a/src/rust/bitbox02-rust/src/keystore/ed25519.rs
+++ b/src/rust/bitbox02-rust/src/keystore/ed25519.rs
@@ -18,8 +18,8 @@ fn hmac_sha512(key: &[u8], msg: &[u8]) -> [u8; 64] {
/// This implements a derivation compatible with Ledger according to
/// https://github.com/LedgerHQ/orakolo/blob/0b2d5e669ec61df9a824df9fa1a363060116b490/src/python/orakolo/HDEd25519.py.
/// Returns 96 bytes. It will contain a 64 byte expanded ed25519 private key followed by a 32 byte chain code.
-fn get_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let bip39_seed = crate::keystore::copy_bip39_seed(hal)?;
+async fn get_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+ let bip39_seed = crate::keystore::copy_bip39_seed(hal).await?;
let mut seed_out = zeroize::Zeroizing::new(vec![0u8; 96]);
let first64: &mut [u8] = &mut seed_out.as_mut_slice()[..64];
first64.copy_from_slice(&bip39_seed);
@@ -46,8 +46,8 @@ fn get_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>
Ok(seed_out)
}
-fn get_xprv(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<Xprv<Sha512>, ()> {
- let root = get_seed(hal)?;
+async fn get_xprv(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<Xprv<Sha512>, ()> {
+ let root = get_seed(hal).await?;
Ok(Xprv::<Sha512>::from_normalize(
&root[..ED25519_EXPANDED_SECRET_KEY_SIZE],
&root[ED25519_EXPANDED_SECRET_KEY_SIZE..],
@@ -55,13 +55,16 @@ fn get_xprv(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<Xprv<Sha5
.derive_path(keypath))
}
-pub fn get_xpub(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<Xpub<Sha512>, ()> {
- Ok(get_xprv(hal, keypath)?.public())
+pub async fn get_xpub(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<Xpub<Sha512>, ()> {
+ Ok(get_xprv(hal, keypath).await?.public())
}
-pub fn get_xpub_twice(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<Xpub<Sha512>, ()> {
- let xpub = get_xpub(hal, keypath)?;
- let xpub2 = get_xpub(hal, keypath)?;
+pub async fn get_xpub_twice(
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+) -> Result<Xpub<Sha512>, ()> {
+ let xpub = get_xpub(hal, keypath).await?;
+ let xpub2 = get_xpub(hal, keypath).await?;
if xpub.pubkey_bytes() == xpub2.pubkey_bytes() && xpub.chain_code() == xpub2.chain_code() {
Ok(xpub)
} else {
@@ -74,12 +77,12 @@ pub struct SignResult {
pub public_key: ed25519_dalek::VerifyingKey,
}
-pub fn sign(
+pub async fn sign(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
msg: &[u8; 32],
) -> Result<SignResult, ()> {
- let xprv = get_xprv(hal, keypath)?;
+ let xprv = get_xprv(hal, keypath).await?;
let secret_key =
ed25519_dalek::hazmat::ExpandedSecretKey::from_bytes(&xprv.expanded_secret_key());
let public_key = ed25519_dalek::VerifyingKey::from(&secret_key);
@@ -116,8 +119,8 @@ mod tests {
assert_eq!(hasher.finalize(), sha2::Sha512::digest(b"baz"));
}
- #[test]
- fn test_get_seed() {
+ #[async_test::test]
+ async fn test_get_seed() {
// Test vectors taken from:
// https://github.com/cardano-foundation/CIPs/blob/6c249ef48f8f5b32efc0ec768fadf4321f3173f2/CIP-0003/Ledger.md#test-vectors
// See also: https://github.com/cardano-foundation/CIPs/pull/132
@@ -129,7 +132,7 @@ mod tests {
"",
);
assert_eq!(
- get_seed(&mut mock_hal).unwrap().as_slice(),
+ get_seed(&mut mock_hal).await.unwrap().as_slice(),
&hex!(
"a08cf85b564ecf3b947d8d4321fb96d70ee7bb760877e371899b14e2ccf88658104b884682b57efd97decbb318a45c05a527b9cc5c2f64f7352935a049ceea60680d52308194ccef2a18e6812b452a5815fbd7f5babc083856919aaf668fe7e4"
),
@@ -141,7 +144,7 @@ mod tests {
"",
);
assert_eq!(
- get_seed(&mut mock_hal).unwrap().as_slice(),
+ get_seed(&mut mock_hal).await.unwrap().as_slice(),
&hex!(
"587c6774357ecbf840d4db6404ff7af016dace0400769751ad2abfc77b9a3844cc71702520ef1a4d1b68b91187787a9b8faab0a9bb6b160de541b6ee62469901fc0beda0975fe4763beabd83b7051a5fd5cbce5b88e82c4bbaca265014e524bd"
),
@@ -152,24 +155,24 @@ mod tests {
"foo",
);
assert_eq!(
- get_seed(&mut mock_hal).unwrap().as_slice(),
+ get_seed(&mut mock_hal).await.unwrap().as_slice(),
&hex!(
"f053a1e752de5c26197b60f032a4809f08bb3e5d90484fe42024be31efcba7578d914d3ff992e21652fee6a4d99f6091006938fac2c0c0f9d2de0ba64b754e92a4f3723f23472077aa4cd4dd8a8a175dba07ea1852dad1cf268c61a2679c3890"
),
);
}
- #[test]
- fn test_get_xpub() {
+ #[async_test::test]
+ async fn test_get_xpub() {
crate::keystore::lock();
let mut mock_hal = crate::hal::testing::TestingHal::new();
- assert!(get_xpub(&mut mock_hal, &[]).is_err());
+ assert!(get_xpub(&mut mock_hal, &[]).await.is_err());
mock_unlocked();
- let xpub = get_xpub(&mut mock_hal, &[]).unwrap();
+ let xpub = get_xpub(&mut mock_hal, &[]).await.unwrap();
assert_eq!(
xpub.pubkey_bytes(),
&hex!("1cc2c80d6fb03ec09e8a268baa45d4ca2afe5c5ac4db3ee29c7ad23755abdc14")
@@ -179,7 +182,9 @@ mod tests {
&hex!("f0a5910642d0779817402e5e7a755495e744f55cf11e49eefd22a460e9b2f753")
);
- let xpub = get_xpub(&mut mock_hal, &[10 + HARDENED_OFFSET, 10]).unwrap();
+ let xpub = get_xpub(&mut mock_hal, &[10 + HARDENED_OFFSET, 10])
+ .await
+ .unwrap();
assert_eq!(
xpub.pubkey_bytes(),
&hex!("ab58bd947e2bf664a7c066de2ef0240efc24f36efd502df88393e196af3c918e")
@@ -190,15 +195,15 @@ mod tests {
);
}
- #[test]
- fn test_get_xpub_twice() {
+ #[async_test::test]
+ async fn test_get_xpub_twice() {
crate::keystore::lock();
let mut mock_hal = crate::hal::testing::TestingHal::new();
- assert!(get_xpub_twice(&mut mock_hal, &[]).is_err());
+ assert!(get_xpub_twice(&mut mock_hal, &[]).await.is_err());
mock_unlocked();
- let xpub = get_xpub_twice(&mut mock_hal, &[]).unwrap();
+ let xpub = get_xpub_twice(&mut mock_hal, &[]).await.unwrap();
assert_eq!(
xpub.pubkey_bytes(),
&hex!("1cc2c80d6fb03ec09e8a268baa45d4ca2afe5c5ac4db3ee29c7ad23755abdc14")
@@ -209,16 +214,16 @@ mod tests {
);
}
- #[test]
- fn test_get_xprv() {
+ #[async_test::test]
+ async fn test_get_xprv() {
crate::keystore::lock();
let mut mock_hal = crate::hal::testing::TestingHal::new();
- assert!(get_xprv(&mut mock_hal, &[]).is_err());
+ assert!(get_xprv(&mut mock_hal, &[]).await.is_err());
mock_unlocked();
- let xprv = get_xprv(&mut mock_hal, &[]).unwrap();
+ let xprv = get_xprv(&mut mock_hal, &[]).await.unwrap();
assert_eq!(
xprv.expanded_secret_key().as_slice(),
&hex!(
@@ -226,7 +231,9 @@ mod tests {
)
);
- let xprv = get_xprv(&mut mock_hal, &[10 + HARDENED_OFFSET, 10]).unwrap();
+ let xprv = get_xprv(&mut mock_hal, &[10 + HARDENED_OFFSET, 10])
+ .await
+ .unwrap();
assert_eq!(
xprv.expanded_secret_key().as_slice(),
&hex!(
@@ -235,8 +242,8 @@ mod tests {
);
}
- #[test]
- fn test_sign() {
+ #[async_test::test]
+ async fn test_sign() {
let msg = &[0u8; 32];
crate::keystore::lock();
assert!(
@@ -245,6 +252,7 @@ mod tests {
&[10 + HARDENED_OFFSET, 10],
msg
)
+ .await
.is_err()
);
@@ -254,6 +262,7 @@ mod tests {
&[10 + HARDENED_OFFSET, 10],
msg,
)
+ .await
.unwrap();
assert_eq!(
sig.public_key.as_ref(),
diff --git a/src/rust/bitbox02-rust/src/workflow/unlock.rs b/src/rust/bitbox02-rust/src/workflow/unlock.rs
index a61f732..498caf5 100644
--- a/src/rust/bitbox02-rust/src/workflow/unlock.rs
+++ b/src/rust/bitbox02-rust/src/workflow/unlock.rs
@@ -238,6 +238,7 @@ mod tests {
&hex!("c7940c13479b8d9a6498f4e50d5a42e0d617bc8e8ac9f2b8cecf97e94c2b035c"),
"password",
)
+ .await
.unwrap();
mock_hal.memory.set_initialized().unwrap();
@@ -258,6 +259,7 @@ mod tests {
assert_eq!(
crate::keystore::copy_bip39_seed(&mut mock_hal)
+ .await
.unwrap()
.as_slice(),
&hex!(
@@ -280,6 +282,7 @@ mod tests {
&hex!("c7940c13479b8d9a6498f4e50d5a42e0d617bc8e8ac9f2b8cecf97e94c2b035c"),
"password",
)
+ .await
.unwrap();
mock_hal.memory.set_initialized().unwrap();
@@ -300,7 +303,7 @@ mod tests {
assert_eq!(mock_hal.securechip.get_event_counter(), 4);
// Checks that the device is locked.
- assert!(crate::keystore::copy_seed(&mut mock_hal).is_err());
+ assert!(crate::keystore::copy_seed(&mut mock_hal).await.is_err());
assert_eq!(
mock_hal.ui.screens,
@@ -325,6 +328,7 @@ mod tests {
&hex!("c7940c13479b8d9a6498f4e50d5a42e0d617bc8e8ac9f2b8cecf97e94c2b035c"),
"password",
)
+ .await
.unwrap();
mock_hal.memory.set_initialized().unwrap();
diff --git a/src/rust/bitbox02-rust/src/xpubcache.rs b/src/rust/bitbox02-rust/src/xpubcache.rs
index c80d2be..d297d84 100644
--- a/src/rust/bitbox02-rust/src/xpubcache.rs
+++ b/src/rust/bitbox02-rust/src/xpubcache.rs
@@ -3,7 +3,7 @@
use super::keystore;
use crate::bip32;
-use alloc::vec::Vec;
+use alloc::{boxed::Box, vec::Vec};
#[derive(Copy, Clone)]
pub enum Compute {
@@ -15,12 +15,13 @@ pub enum Compute {
Twice,
}
+#[allow(async_fn_in_trait)]
pub trait Xpub: Sized {
/// Derives a child xpub using the provided keypath.
fn derive(&self, keypath: &[u32], compute: Compute) -> Result<Self, ()>;
/// Derives an xpub from the root xpub using the provided keypath.
- fn from_keypath(
+ async fn from_keypath(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
compute: Compute,
@@ -58,7 +59,11 @@ impl<X: Xpub + Clone> XpubCache<X> {
}
// Retrieves a cached xpub. If the xpub is not cached, derive and cache it first.
- fn cache_get_set(&mut self, hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<X, ()> {
+ async fn cache_get_set(
+ &mut self,
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+ ) -> Result<X, ()> {
// Return cached xpub if exists.
if let Some((_, xpub)) = self
.xpubs
@@ -75,9 +80,14 @@ impl<X: Xpub + Clone> XpubCache<X> {
// from an xpub (hardened elements require the xprv).
const UNHARDENED_LAST: u32 = util::bip32::HARDENED - 1;
let xpub = if let [prefix @ .., last @ 0..=UNHARDENED_LAST] = keypath {
- self.get_xpub(hal, prefix)?.derive(&[*last], self.compute)?
+ // Boxing is required here because `cache_get_set()` calls `get_xpub()`, which can call
+ // back into `cache_get_set()` again. Recursive async calls need indirection so the
+ // future has a finite size.
+ Box::pin(self.get_xpub(hal, prefix))
+ .await?
+ .derive(&[*last], self.compute)?
} else {
- X::from_keypath(hal, keypath, self.compute)?
+ X::from_keypath(hal, keypath, self.compute).await?
};
self.xpubs.push((keypath.to_vec(), xpub.clone()));
Ok(xpub)
@@ -86,7 +96,11 @@ impl<X: Xpub + Clone> XpubCache<X> {
/// Derive an xpub from the keystore's master key. If a prefix of the keypath is cached, the
/// cached xpub will be used as basis for derivation. The longest cached prefix (shortest
/// suffix) is used to minimize the number child derivations necessary afterwards.
- pub fn get_xpub(&mut self, hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<X, ()> {
+ pub async fn get_xpub(
+ &mut self,
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+ ) -> Result<X, ()> {
// Check if any prefix of keypath is is marked as cached. Get the longest such prefix.
let search_result = self
.keypaths
@@ -98,10 +112,10 @@ impl<X: Xpub + Clone> XpubCache<X> {
})
.max_by_key(|(kp, _)| kp.len());
if let Some((cached_prefix, suffix)) = search_result {
- let xpub = self.cache_get_set(hal, &cached_prefix.clone())?;
+ let xpub = self.cache_get_set(hal, &cached_prefix.clone()).await?;
return xpub.derive(suffix, self.compute);
}
- X::from_keypath(hal, keypath, self.compute)
+ X::from_keypath(hal, keypath, self.compute).await
}
}
@@ -120,14 +134,14 @@ impl Xpub for bip32::Xpub {
}
}
- fn from_keypath(
+ async fn from_keypath(
hal: &mut impl crate::hal::Hal,
keypath: &[u32],
compute: Compute,
) -> Result<Self, ()> {
match compute {
- Compute::Once => keystore::get_xpub_once(hal, keypath),
- Compute::Twice => keystore::get_xpub_twice(hal, keypath),
+ Compute::Once => keystore::get_xpub_once(hal, keypath).await,
+ Compute::Twice => keystore::get_xpub_twice(hal, keypath).await,
}
}
}
@@ -144,8 +158,8 @@ mod tests {
use core::cell::RefCell;
use util::bip32::HARDENED;
- #[test]
- fn test_xpub_cache() {
+ #[async_test::test]
+ async fn test_xpub_cache() {
// Mock xpubs by storing the keypath only, so we can unit test access patterns.
std::thread_local! {
static CHILD_DERIVATIONS: RefCell<u32> = const { RefCell::new(0) };
@@ -165,7 +179,7 @@ mod tests {
Ok(Self { keypath: kp })
}
- fn from_keypath(
+ async fn from_keypath(
_hal: &mut impl crate::hal::Hal,
keypath: &[u32],
_compute: Compute,
@@ -184,6 +198,7 @@ mod tests {
assert_eq!(
cache
.get_xpub(&mut crate::hal::testing::TestingHal::new(), &[])
+ .await
.unwrap()
.keypath
.as_slice(),
@@ -196,6 +211,7 @@ mod tests {
assert_eq!(
cache
.get_xpub(&mut crate::hal::testing::TestingHal::new(), &[1, 2, 3])
+ .await
.unwrap()
.keypath
.as_slice(),
@@ -215,6 +231,7 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2]
)
+ .await
.unwrap()
.keypath
.as_slice(),
@@ -235,6 +252,7 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2]
)
+ .await
.unwrap()
.keypath
.as_slice(),
@@ -252,6 +270,7 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0]
)
+ .await
.unwrap()
.keypath
.as_slice(),
@@ -261,8 +280,8 @@ mod tests {
assert_eq!(ROOT_DERIVATIONS.with(|count| *count.borrow()), 0u32);
}
- #[test]
- fn test_bip32_xpub_cache() {
+ #[async_test::test]
+ async fn test_bip32_xpub_cache() {
let mut cache = Bip32XpubCache::new(crate::xpubcache::Compute::Twice);
cache.add_keypath(&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1]);
cache.add_keypath(&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED]);
@@ -274,12 +293,12 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2]
)
+ .await
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
"xpub6H18r9myxw9MztwzVyBYj26X1gVkz9ZzwJ8UgV9HWYu4ae6NQ6AEs2ibibhbF6oK3bzduzVNv4gwmu78o4Z4tkdzAcDMp6siTFbVegg9DEi",
);
-
// Make sure the following xpubs are derived using the cache only, not touching the seed.
crate::keystore::lock();
@@ -289,6 +308,7 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0]
)
+ .await
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -301,6 +321,7 @@ mod tests {
&mut crate::hal::testing::TestingHal::new(),
&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 3]
)
+ .await
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
diff --git a/src/rust/bitbox02/src/hal/securechip.rs b/src/rust/bitbox02/src/hal/securechip.rs
index ddb4595..b5e7dc6 100644
--- a/src/rust/bitbox02/src/hal/securechip.rs
+++ b/src/rust/bitbox02/src/hal/securechip.rs
@@ -105,8 +105,8 @@ impl SecureChip for BitBox02SecureChip {
.map_err(to_hal_error)
}
- fn kdf(&mut self, msg: &[u8; 32]) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
- crate::securechip::kdf(msg).map_err(to_hal_error)
+ async fn kdf(&mut self, msg: &[u8; 32]) -> Result<Box<zeroize::Zeroizing<[u8; 32]>>, Error> {
+ crate::securechip::kdf(msg).await.map_err(to_hal_error)
}
fn attestation_sign(
@@ -260,7 +260,7 @@ mod tests {
fn test_kdf() {
let mut securechip = BitBox02SecureChip;
let msg = [0u8; 32];
- let result = securechip.kdf(&msg).unwrap();
+ let result = util::bb02_async::block_on(securechip.kdf(&msg)).unwrap();
let expected = hex!("1c723ccd9597e76deb55f9fd6808014007bcb3d67fc060f1149aefb9be88f423");
assert_eq!(result.as_slice(), expected.as_slice());
}
diff --git a/src/rust/bitbox02/src/securechip/imp.rs b/src/rust/bitbox02/src/securechip/imp.rs
index 24fbc39..608d998 100644
--- a/src/rust/bitbox02/src/securechip/imp.rs
+++ b/src/rust/bitbox02/src/securechip/imp.rs
@@ -68,10 +68,10 @@ pub fn stretch_password(
/// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte
/// result.
-pub fn kdf(msg: &[u8; 32]) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
+pub async fn kdf(msg: &[u8; 32]) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
match backend() {
Backend::Atecc => atecc::kdf(msg),
- Backend::Optiga => optiga::kdf(msg),
+ Backend::Optiga => optiga::kdf(msg).await,
}
}
diff --git a/src/rust/bitbox02/src/securechip/imp_fake.rs b/src/rust/bitbox02/src/securechip/imp_fake.rs
index d1a1c45..e072877 100644
--- a/src/rust/bitbox02/src/securechip/imp_fake.rs
+++ b/src/rust/bitbox02/src/securechip/imp_fake.rs
@@ -65,7 +65,7 @@ pub fn stretch_password(
/// Perform the secure chip KDF with the message in `msg` and return the zeroizing 32-byte
/// result.
-pub fn kdf(msg: &[u8; 32]) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
+pub async fn kdf(msg: &[u8; 32]) -> Result<Box<Zeroizing<[u8; 32]>>, Error> {
Ok(Box::new(Zeroizing::new(hmac_sha256(&KDF_KEY, msg))))
}
diff --git a/src/rust/bitbox03/src/securechip.rs b/src/rust/bitbox03/src/securechip.rs
index a96cd50..a432eff 100644
--- a/src/rust/bitbox03/src/securechip.rs
+++ b/src/rust/bitbox03/src/securechip.rs
@@ -26,7 +26,7 @@ impl hal::securechip::SecureChip for BitBox03SecureChip {
todo!()
}
- fn kdf(
+ async fn kdf(
&mut self,
_msg: &[u8; 32],
) -> Result<alloc::boxed::Box<zeroize::Zeroizing<[u8; 32]>>, bitbox_hal::securechip::Error>
diff --git a/src/rust/util/src/bb02_async.rs b/src/rust/util/src/bb02_async.rs
index fd4129f..66ac720 100644
--- a/src/rust/util/src/bb02_async.rs
+++ b/src/rust/util/src/bb02_async.rs
@@ -39,7 +39,6 @@ pub fn option<O>(option: &RefCell<Option<O>>) -> AsyncOption<'_, O> {
}
/// Polls a future until the result is available.
-#[cfg(feature = "testing")]
pub fn block_on<O>(task: impl core::future::Future<Output = O>) -> O {
let mut task: crate::bb02_async::Task<O> = alloc::boxed::Box::pin(task);
loop {
diff --git a/test/simulator-graphical/src/main.rs b/test/simulator-graphical/src/main.rs
index 4286d7a..b84bd61 100644
--- a/test/simulator-graphical/src/main.rs
+++ b/test/simulator-graphical/src/main.rs
@@ -40,6 +40,7 @@ use tracing_subscriber::{EnvFilter, filter::LevelFilter, fmt, prelude::*};
use bitbox02::ui::ugui::UG_COLOR;
use bitbox02_rust::hal::{Eeprom, Hal, Memory, System};
+use util::bb02_async::block_on;
// Explicitly link library for its C exports
extern crate bitbox02_rust_c;
@@ -166,7 +167,10 @@ fn init_hww(
if preseed {
let mnemonic = "boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide";
let seed = bitbox02_rust::bip39::mnemonic_to_seed(&mnemonic).unwrap();
- bitbox02_rust::keystore::encrypt_and_store_seed(&mut hal, &seed, "").unwrap();
+ block_on(bitbox02_rust::keystore::encrypt_and_store_seed(
+ &mut hal, &seed, "",
+ ))
+ .unwrap();
hal.memory().set_initialized().unwrap();
}
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.