What changed, and why it matters
This commit is a large internal refactoring of the BitBox02 firmware. It adds a new secure-chip key-derivation function (KDF) to the hardware abstraction layer (HAL) and threads that HAL through many existing functions so they can use it. The change touches 31 files and many tests, but it does not appear to introduce a new security vulnerability on its own. It is a structural change that prepares the code for using the secure chip's KDF more consistently.
Treat this as a routine refactor. Reviewers should verify that every call site previously using the global `securechip::kdf()` now correctly passes the HAL, that the test KDF is only used in tests, and that no production code path can accidentally instantiate the testing HAL. A follow-up review of the underlying C `securechip::kdf()` implementation remains worthwhile because the Rust layer now funnels more operations through it.
Security signals we found
Large refactor (+737/-410) across 31 files
New KDF abstraction added to SecureChip HAL
Global securechip::kdf() replaced by HAL-mediated kdf() in seed-retention key stretching
Many functions now require an explicit HAL parameter, which can improve testability and auditability
No new input validation, no new memory-safety primitives, and no change to access-control logic visible in the diff
Evidence from the diff
The commit adds kdf() to the SecureChip trait and its real (BitBox02SecureChip) and test (TestingSecureChip) implementations. It then propagates a &mut impl Hal parameter through keystore, xpub derivation, signing, backup, restore, and various Bitcoin/Ethereum/Cardano API handlers so that functions such as copy_seed, copy_bip39_seed, stretch_retained_seed_encryption_key, get_xpub_once/twice, secp256k1_get_private_key, and bip85_* can call hal.securechip().kdf() instead of the global bitbox02::securechip::kdf(). The real implementation delegates to bitbox02::securechip::kdf(msg). The test implementation uses a fixed HMAC-SHA256 key. The change is mostly plumbing and test updates; no new cryptographic algorithm or access-control bypass is introduced in the diff.
Changed components
src/rust/bitbox02-rust/src/hal.rssrc/rust/bitbox02-rust/src/keystore.rssrc/rust/bitbox02-rust/src/hww/api/backup.rssrc/rust/bitbox02-rust/src/hww/api/bip85.rssrc/rust/bitbox02-rust/src/hww/api/bitcoin/*.rssrc/rust/bitbox02-rust/src/hww/api/cardano/*.rssrc/rust/bitbox02-rust/src/hww/api/electrum.rssrc/rust/bitbox02-rust/src/hww/api/ethereum/*.rssrc/rust/bitbox02-rust/src/hww/api/restore.rssrc/rust/bitbox02-rust/src/hww/api/set_password.rssrc/rust/bitbox02-rust/src/hww/api/show_mnemonic.rssrc/rust/bitbox02-rust/src/workflow/unlock.rssrc/rust/bitbox02-rust/src/xpubcache.rsInspect captured patch +737 / −410
diff --git a/src/rust/bitbox02-rust/src/hal.rs b/src/rust/bitbox02-rust/src/hal.rs
index 9224528..52361d5 100644
--- a/src/rust/bitbox02-rust/src/hal.rs
+++ b/src/rust/bitbox02-rust/src/hal.rs
@@ -44,6 +44,10 @@ pub trait SecureChip {
&mut self,
password: &str,
) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error>;
+ fn kdf(
+ &mut self,
+ msg: &[u8],
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error>;
}
/// Hardware abstraction layer for BitBox devices.
@@ -119,6 +123,13 @@ impl SecureChip for BitBox02SecureChip {
) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
bitbox02::securechip::stretch_password(password)
}
+
+ fn kdf(
+ &mut self,
+ msg: &[u8],
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
+ bitbox02::securechip::kdf(msg)
+ }
}
pub struct BitBox02Hal {
@@ -163,6 +174,8 @@ pub mod testing {
use bitcoin::hashes::{Hash, sha256};
+ use hex_lit::hex;
+
pub struct TestingRandom {
mock_next_values: VecDeque<[u8; 32]>,
counter: u32,
@@ -301,6 +314,23 @@ pub mod testing {
hmac_result.to_byte_array().to_vec(),
))
}
+
+ fn kdf(
+ &mut self,
+ msg: &[u8],
+ ) -> Result<zeroize::Zeroizing<Vec<u8>>, bitbox02::securechip::Error> {
+ self.event_counter += 1;
+
+ use bitcoin::hashes::{HashEngine, Hmac, HmacEngine, sha256};
+ let mut engine = HmacEngine::<sha256::Hash>::new(&hex!(
+ "d2e1e6b18b6c6b08433edbc1d168c1a0043774a4221877e79ed56684be5ac01b"
+ ));
+ engine.input(msg);
+ let hmac_result: Hmac<sha256::Hash> = Hmac::from_engine(engine);
+ Ok(zeroize::Zeroizing::new(
+ hmac_result.to_byte_array().to_vec(),
+ ))
+ }
}
pub struct TestingHal<'a> {
diff --git a/src/rust/bitbox02-rust/src/hww.rs b/src/rust/bitbox02-rust/src/hww.rs
index 18766ce..ea1497e 100644
--- a/src/rust/bitbox02-rust/src/hww.rs
+++ b/src/rust/bitbox02-rust/src/hww.rs
@@ -552,7 +552,7 @@ mod tests {
]
);
- let seed = crate::keystore::copy_seed().unwrap();
+ let seed = crate::keystore::copy_seed(&mut mock_hal).unwrap();
assert_eq!(seed.len(), host_entropy.len());
mock_hal.ui = crate::workflow::testing::TestingWorkflows::new();
assert!(matches!(
@@ -718,7 +718,7 @@ mod tests {
);
// Restored seed is the same as the seed that was backed up.
- assert_eq!(seed, crate::keystore::copy_seed().unwrap());
+ assert_eq!(seed, crate::keystore::copy_seed(&mut mock_hal).unwrap());
}
}
}
diff --git a/src/rust/bitbox02-rust/src/hww/api.rs b/src/rust/bitbox02-rust/src/hww/api.rs
index d6a5810..3ade481 100644
--- a/src/rust/bitbox02-rust/src/hww/api.rs
+++ b/src/rust/bitbox02-rust/src/hww/api.rs
@@ -184,7 +184,7 @@ async fn process_api(hal: &mut impl crate::hal::Hal, request: &Request) -> Resul
Request::RestoreBackup(request) => restore::from_file(hal, request).await,
Request::ShowMnemonic(_) => show_mnemonic::process(hal).await,
Request::RestoreFromMnemonic(request) => restore::from_mnemonic(hal, request).await,
- Request::ElectrumEncryptionKey(request) => electrum::process(request).await,
+ Request::ElectrumEncryptionKey(request) => electrum::process(hal, request).await,
#[cfg(feature = "app-ethereum")]
Request::Eth(pb::EthRequest {
diff --git a/src/rust/bitbox02-rust/src/hww/api/backup.rs b/src/rust/bitbox02-rust/src/hww/api/backup.rs
index a115154..1b1dda8 100644
--- a/src/rust/bitbox02-rust/src/hww/api/backup.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/backup.rs
@@ -30,7 +30,7 @@ pub async fn check(
return Err(Error::InvalidInput);
}
- let seed = crate::keystore::copy_seed()?;
+ let seed = crate::keystore::copy_seed(hal)?;
let id = backup::id(&seed);
let (backup_data, metadata) = backup::load(hal, &id).await?;
if seed.as_slice() != backup_data.get_seed() {
@@ -102,7 +102,7 @@ pub async fn create(
let seed = if is_initialized {
unlock::unlock_keystore(hal, "Unlock device", unlock::CanCancel::Yes).await?
} else {
- let seed = crate::keystore::copy_seed()?;
+ let seed = crate::keystore::copy_seed(hal)?;
// Yield now to give executor a chance to process USB/BLE communication, as copy_seed() causes
// some delay.
futures_lite::future::yield_now().await;
diff --git a/src/rust/bitbox02-rust/src/hww/api/bip85.rs b/src/rust/bitbox02-rust/src/hww/api/bip85.rs
index f019043..fea9dcf 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bip85.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bip85.rs
@@ -121,7 +121,7 @@ async fn process_bip39(hal: &mut impl crate::hal::Hal) -> Result<(), Error> {
})
.await?;
- let mnemonic = keystore::bip85_bip39(num_words, index)?;
+ let mnemonic = keystore::bip85_bip39(hal, num_words, index)?;
let words: Vec<&str> = mnemonic.split(' ').collect();
hal.ui().show_and_confirm_mnemonic(&words).await?;
@@ -151,7 +151,7 @@ async fn process_ln(
})
.await?;
- Ok(keystore::bip85_ln(account_number)
+ Ok(keystore::bip85_ln(hal, account_number)
.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 71bc7fa..b447cf9 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
@@ -126,7 +126,7 @@ async fn xpub(
})
.await?
}
- let xpub = keystore::get_xpub_twice(keypath)
+ let xpub = keystore::get_xpub_twice(hal, keypath)
.or(Err(Error::InvalidInput))?
.serialize_str(xpub_type)?;
if display {
@@ -150,6 +150,7 @@ async fn xpub(
}
pub fn derive_address_simple(
+ hal: &mut impl crate::hal::Hal,
coin: BtcCoin,
simple_type: SimpleType,
keypath: &[u32],
@@ -164,6 +165,7 @@ pub fn derive_address_simple(
)
.or(Err(Error::InvalidInput))?;
Ok(common::Payload::from_simple(
+ hal,
&mut crate::xpubcache::XpubCache::new(crate::xpubcache::Compute::Twice),
coin_params,
simple_type,
@@ -180,7 +182,7 @@ async fn address_simple(
keypath: &[u32],
display: bool,
) -> Result<Response, Error> {
- let address = derive_address_simple(coin, simple_type, keypath)?;
+ let address = derive_address_simple(hal, coin, simple_type, keypath)?;
if display {
let confirm_params = confirm::Params {
title: params::get(coin).name,
@@ -205,7 +207,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(multisig, account_keypath)?;
+ multisig::validate(hal, multisig, account_keypath)?;
let name = match multisig::get_name(coin, multisig, account_keypath)? {
Some(name) => name,
None => return Err(Error::InvalidInput),
@@ -247,7 +249,7 @@ async fn address_policy(
keypath::validate_address_policy(keypath, keypath::ReceiveSpend::Receive)
.or(Err(Error::InvalidInput))?;
- let parsed = policies::parse(policy, coin)?;
+ let parsed = policies::parse(hal, policy, coin)?;
let name = parsed.name(coin_params)?.ok_or(Error::InvalidInput)?;
@@ -316,7 +318,7 @@ pub async fn process_api(
registration::process_register_script_config(hal, request).await
}
Request::SignMessage(request) => signmsg::process(hal, request).await,
- Request::Xpubs(request) => xpubs::process_xpubs(request).await,
+ Request::Xpubs(request) => xpubs::process_xpubs(hal, request).await,
// These are streamed asynchronously using the `next_request()` primitive in
// bitcoin/signtx.rs and are not handled directly.
Request::PrevtxInit(_)
@@ -1074,7 +1076,7 @@ mod tests {
root_fingerprint: keystore::root_fingerprint().unwrap(),
keypath: KEYPATH_ACCOUNT_TESTNET.to_vec(),
xpub: Some(
- crate::keystore::get_xpub_once(KEYPATH_ACCOUNT_TESTNET)
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), KEYPATH_ACCOUNT_TESTNET)
.unwrap()
.into(),
),
@@ -1083,7 +1085,7 @@ mod tests {
root_fingerprint: keystore::root_fingerprint().unwrap(),
keypath: KEYPATH_ACCOUNT_MAINNET.to_vec(),
xpub: Some(
- crate::keystore::get_xpub_once(KEYPATH_ACCOUNT_MAINNET)
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), KEYPATH_ACCOUNT_MAINNET)
.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 a8827f1..46d35a5 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
@@ -81,6 +81,7 @@ pub struct Payload {
impl Payload {
pub fn from_simple(
+ hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
params: &Params,
simple_type: SimpleType,
@@ -88,12 +89,12 @@ impl Payload {
) -> Result<Self, Error> {
match simple_type {
SimpleType::P2wpkh => Ok(Payload {
- data: xpub_cache.get_xpub(keypath)?.pubkey_hash160(),
+ data: xpub_cache.get_xpub(hal, keypath)?.pubkey_hash160(),
output_type: BtcOutputType::P2wpkh,
}),
SimpleType::P2wpkhP2sh => {
let payload_p2wpkh =
- Payload::from_simple(xpub_cache, params, SimpleType::P2wpkh, keypath)?;
+ Payload::from_simple(hal, xpub_cache, params, SimpleType::P2wpkh, keypath)?;
let pkscript_p2wpkh = payload_p2wpkh.pk_script(params)?;
Ok(Payload {
data: bitcoin::hashes::hash160::Hash::hash(&pkscript_p2wpkh)
@@ -106,7 +107,7 @@ impl Payload {
if params.taproot_support {
Ok(Payload {
data: xpub_cache
- .get_xpub(keypath)?
+ .get_xpub(hal, keypath)?
.schnorr_bip86_pubkey()?
.to_vec(),
output_type: BtcOutputType::P2tr,
@@ -190,6 +191,7 @@ 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(
+ hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
params: &Params,
keypath: &[u32],
@@ -197,7 +199,7 @@ impl Payload {
) -> Result<Self, Error> {
match &script_config_account.config {
ValidatedScriptConfig::SimpleType(simple_type) => {
- Self::from_simple(xpub_cache, params, *simple_type, keypath)
+ Self::from_simple(hal, xpub_cache, params, *simple_type, keypath)
}
ValidatedScriptConfig::Multisig { multisig, .. } => Self::from_multisig(
params,
@@ -582,6 +584,7 @@ mod tests {
// p2wpkh
assert_eq!(
Payload::from_simple(
+ &mut crate::hal::testing::TestingHal::new(),
&mut xpub_cache,
coin_params,
SimpleType::P2wpkh,
@@ -596,6 +599,7 @@ mod tests {
// p2wpkh-p2sh
assert_eq!(
Payload::from_simple(
+ &mut crate::hal::testing::TestingHal::new(),
&mut xpub_cache,
coin_params,
SimpleType::P2wpkhP2sh,
@@ -610,6 +614,7 @@ mod tests {
// p2tr
assert_eq!(
Payload::from_simple(
+ &mut crate::hal::testing::TestingHal::new(),
&mut xpub_cache,
coin_params,
SimpleType::P2tr,
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 2448f72..4c8a51c 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/multisig.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/multisig.rs
@@ -259,7 +259,11 @@ 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(multisig: &Multisig, keypath: &[u32]) -> Result<(), Error> {
+pub fn validate(
+ hal: &mut impl crate::hal::Hal,
+ multisig: &Multisig,
+ keypath: &[u32],
+) -> Result<(), Error> {
if multisig.xpubs.len() < 2 || multisig.xpubs.len() > MAX_SIGNERS {
return Err(Error::InvalidInput);
}
@@ -270,7 +274,7 @@ pub fn validate(multisig: &Multisig, keypath: &[u32]) -> Result<(), Error> {
return Err(Error::InvalidInput);
}
- let our_xpub = crate::keystore::get_xpub_once(keypath)?.serialize(None)?;
+ let our_xpub = crate::keystore::get_xpub_once(hal, keypath)?.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 {
@@ -589,18 +593,20 @@ mod tests {
script_type: ScriptType::P2wsh as _,
};
+ let mut mock_hal = crate::hal::testing::TestingHal::new();
+
// Keystore locked.
crate::keystore::lock();
- assert!(validate(&multisig, keypath).is_err());
+ assert!(validate(&mut mock_hal, &multisig, keypath).is_err());
// Ok.
mock_unlocked_using_mnemonic(
"sudden tenant fault inject concert weather maid people chunk youth stumble grit",
"",
);
- assert!(validate(&multisig, keypath).is_ok());
+ assert!(validate(&mut mock_hal, &multisig, keypath).is_ok());
// Ok at arbitrary keypath.
- assert!(validate(&Multisig {
+ assert!(validate(&mut mock_hal,&Multisig {
threshold: 1,
xpubs: vec![
parse_xpub("xpub6FMWuwbCA9KhoRzAMm63ZhLspk5S2DM5sePo8J8mQhcS1xyMbAqnc7Q7UescVEVFCS6qBMQLkEJWQ9Z3aDPgBov5nFUYxsJhwumsxM4npSo").unwrap(),
@@ -633,7 +639,7 @@ mod tests {
"xpub6ECHc4kmTC2tQg2ZoAoazwyag9C4V6yFsZEhjwMJixdVNsUibot6uEvsZY38ZLVqWCtyc9gbzFEwHQLHCT8EiDDKSNNsFAB8NQYRgkiAQwu",
"xpub6F7CaxXzBCtvXwpRi61KYyhBRkgT1856ujHV5AbJK6ySCUYoDruBH6Pnsi6eHkDiuKuAJ2tSc9x3emP7aax9Dc3u7nP7RCQXEjLKihQu6w1",
].iter().map(|s| parse_xpub(s).unwrap()).collect();
- assert!(validate(&invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
}
{
@@ -641,21 +647,21 @@ mod tests {
let mut invalid = multisig.clone();
invalid.xpubs = vec![];
- assert!(validate(&invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
invalid.our_xpub_index = 0;
invalid.xpubs = vec![parse_xpub(our_xpub_str).unwrap()];
- assert!(validate(&invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
}
{
// threshold larger than number of cosigners
let mut invalid = multisig.clone();
invalid.threshold = 3;
- assert!(validate(&invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
// threshold zero
invalid.threshold = 0;
- assert!(validate(&invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
}
{
@@ -663,7 +669,7 @@ mod tests {
// bounds).
let mut invalid = multisig.clone();
invalid.our_xpub_index = 2;
- assert!(validate(&invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
}
{
@@ -671,7 +677,7 @@ mod tests {
let mut invalid = multisig.clone();
invalid.xpubs[1] = parse_xpub("xpub6FNT7x2ZEBMhs4jvZJSEBV2qBCBnRidNsyqe7inT9V2wmEn4sqidTEudB4dVSvEjXz2NytcymwWJb8PPYExRycNf9SH8fAHzPWUsQJAmbR3").unwrap();
- assert!(validate(&invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).is_err());
}
{
@@ -679,7 +685,7 @@ mod tests {
let mut invalid = multisig.clone();
invalid.xpubs[0] = invalid.xpubs[1].clone();
- assert!(validate(&invalid, keypath).is_err());
+ assert!(validate(&mut mock_hal, &invalid, keypath).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 8c446a4..4bd045f 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs
@@ -50,7 +50,11 @@ 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(key: &pb::KeyOriginInfo, our_root_fingerprint: &[u8]) -> Result<bool, ()> {
+fn is_our_key(
+ hal: &mut impl crate::hal::Hal,
+ key: &pb::KeyOriginInfo,
+ our_root_fingerprint: &[u8],
+) -> Result<bool, ()> {
match key {
pb::KeyOriginInfo {
root_fingerprint,
@@ -58,7 +62,7 @@ fn is_our_key(key: &pb::KeyOriginInfo, our_root_fingerprint: &[u8]) -> Result<bo
xpub: Some(xpub),
..
} if root_fingerprint.as_slice() == our_root_fingerprint => {
- let our_xpub = crate::keystore::get_xpub_once(keypath)?.serialize(None)?;
+ let our_xpub = crate::keystore::get_xpub_once(hal, keypath)?.serialize(None)?;
let maybe_our_xpub = bip32::Xpub::from(xpub).serialize(None)?;
Ok(our_xpub == maybe_our_xpub)
}
@@ -555,12 +559,13 @@ impl ParsedPolicy<'_> {
/// path, and if the latter, which leaf exactly.
pub fn taproot_spend_info(
&self,
+ hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
keypath: &[u32],
) -> Result<TaprootSpendInfo, Error> {
match self.derive_at_keypath(keypath)? {
Descriptor::Tr(tr) => {
- let xpub = xpub_cache.get_xpub(keypath)?;
+ let xpub = xpub_cache.get_xpub(hal, keypath)?;
let is_keypath_spend =
xpub.public_key() == tr.inner.internal_key().inner.serialize();
@@ -657,7 +662,11 @@ 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(policy: &Policy, coin: BtcCoin) -> Result<ParsedPolicy<'_>, Error> {
+pub fn parse<'a>(
+ hal: &mut impl crate::hal::Hal,
+ policy: &'a Policy,
+ coin: BtcCoin,
+) -> Result<ParsedPolicy<'a>, Error> {
check_enabled(coin)?;
if policy.keys.len() > MAX_KEYS {
return Err(Error::InvalidInput);
@@ -669,7 +678,7 @@ pub fn parse(policy: &Policy, coin: BtcCoin) -> Result<ParsedPolicy<'_>, Error>
let is_our_key: Vec<bool> = policy
.keys
.iter()
- .map(|key| is_our_key(key, &our_root_fingerprint))
+ .map(|key| is_our_key(hal, key, &our_root_fingerprint))
.collect::<Result<Vec<bool>, ()>>()?;
let parsed = match desc.as_bytes() {
@@ -787,7 +796,9 @@ mod tests {
// Creates a policy for one of our own keys at keypath.
fn make_our_key(keypath: &[u32]) -> pb::KeyOriginInfo {
- let our_xpub = crate::keystore::get_xpub_once(keypath).unwrap();
+ let our_xpub =
+ crate::keystore::get_xpub_once(&mut crate::hal::testing::TestingHal::new(), keypath)
+ .unwrap();
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath.to_vec(),
@@ -866,7 +877,14 @@ mod tests {
make_our_key(KEYPATH_ACCOUNT),
],
);
- let pks: Vec<String> = parse(&policy, BtcCoin::Tbtc).unwrap().iter_pk().collect();
+ let pks: Vec<String> = parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &policy,
+ BtcCoin::Tbtc,
+ )
+ .unwrap()
+ .iter_pk()
+ .collect();
assert_eq!(pks.as_slice(), test.expected_pks);
}
}
@@ -874,10 +892,11 @@ mod tests {
#[test]
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);
// 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(&policy, coin).unwrap().descriptor {
+ match &parse(&mut mock_hal, &policy, coin).unwrap().descriptor {
Descriptor::Wsh(Wsh {
miniscript_expr, ..
}) => {
@@ -894,7 +913,7 @@ mod tests {
"wsh(or_b(pk(@0/**),s:pk(@1/**)))",
&[our_key.clone(), make_key(SOME_XPUB_1)],
);
- match &parse(&policy, coin).unwrap().descriptor {
+ match &parse(&mut mock_hal, &policy, coin).unwrap().descriptor {
Descriptor::Wsh(Wsh {
miniscript_expr, ..
}) => {
@@ -909,6 +928,7 @@ mod tests {
// Unknown top-level fragment.
assert_eq!(
parse(
+ &mut mock_hal,
&make_policy("unknown(pk(@0/**))", core::slice::from_ref(&our_key)),
coin
)
@@ -919,6 +939,7 @@ mod tests {
// Unknown script fragment.
assert_eq!(
parse(
+ &mut mock_hal,
&make_policy("wsh(unknown(@0/**))", core::slice::from_ref(&our_key)),
coin
)
@@ -929,6 +950,7 @@ mod tests {
// Miniscript type-check fails (should be `or_b(pk(@0/**),s:pk(@1/**))`).
assert_eq!(
parse(
+ &mut mock_hal,
&make_policy(
"wsh(or_b(pk(@0/**),pk(@1/**)))",
&[our_key.clone(), make_key(SOME_XPUB_1)]
@@ -950,6 +972,7 @@ mod tests {
// All good.
assert!(
parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", core::slice::from_ref(&our_key)),
coin
)
@@ -959,6 +982,7 @@ mod tests {
// All good, all keys are used across internal key & leaf scripts.
assert!(
parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy(
"tr(@0/**,{pk(@1/**),pk(@2/**)})",
&[
@@ -976,6 +1000,7 @@ mod tests {
for coin in [BtcCoin::Ltc, BtcCoin::Tltc] {
assert!(matches!(
parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", core::slice::from_ref(&our_key)),
coin
),
@@ -988,13 +1013,18 @@ mod tests {
.map(|i| make_our_key(&[48 + HARDENED, 1 + HARDENED, i + HARDENED, 3 + HARDENED]))
.collect();
assert!(matches!(
- parse(&make_policy("wsh(pk(@0/**))", &many_keys), coin),
+ parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &make_policy("wsh(pk(@0/**))", &many_keys),
+ coin
+ ),
Err(Error::InvalidInput)
));
// Our key is not present - fingerprint missing.
assert!(matches!(
parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", &[make_key(SOME_XPUB_1)]),
coin
),
@@ -1005,13 +1035,18 @@ mod tests {
let mut wrong_key = our_key.clone();
wrong_key.xpub = Some(parse_xpub(SOME_XPUB_1).unwrap());
assert!(matches!(
- parse(&make_policy("wsh(pk(@0/**))", &[wrong_key]), coin),
+ parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &make_policy("wsh(pk(@0/**))", &[wrong_key]),
+ coin
+ ),
Err(Error::InvalidInput)
));
// Contains duplicate keys.
assert!(matches!(
parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy(
"wsh(multi(2,@0/**,@1/**,@2/**))",
&[
@@ -1028,6 +1063,7 @@ mod tests {
// Contains a key with missing xpub.
assert!(matches!(
parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy(
"wsh(multi(2,@0/**,@1/**))",
&[
@@ -1047,6 +1083,7 @@ mod tests {
// Not all keys are used.
assert!(matches!(
parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@0/**))", &[our_key.clone(), make_key(SOME_XPUB_1)]),
coin
),
@@ -1056,6 +1093,7 @@ mod tests {
// Referenced key does not exist
assert!(matches!(
parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy("wsh(pk(@1/**))", core::slice::from_ref(&our_key)),
coin
),
@@ -1072,21 +1110,21 @@ mod tests {
// Ok, one key.
let pol = make_policy("wsh(pk(@0/**))", core::slice::from_ref(&our_key));
- assert!(parse(&pol, coin).is_ok());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_ok());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_ok());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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,
@@ -1096,39 +1134,39 @@ mod tests {
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<1;2>/*)))",
core::slice::from_ref(&our_key),
);
- assert!(parse(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
}
#[test]
@@ -1140,14 +1178,14 @@ mod tests {
// Ok, only internal key.
let pol = make_policy("tr(@0/**)", core::slice::from_ref(&our_key));
- assert!(parse(&pol, coin).is_ok());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_ok());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_ok());
// Ok, one leaf with two keys.
let pol = make_policy(
@@ -1158,26 +1196,26 @@ mod tests {
make_key(SOME_XPUB_2),
],
);
- assert!(parse(&pol, coin).is_ok());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).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(&pol, coin).is_err());
+ assert!(parse(&mut crate::hal::testing::TestingHal::new(), &pol, coin).is_err());
}
#[test]
@@ -1338,20 +1376,28 @@ mod tests {
let coin = BtcCoin::Tbtc;
let witness_script = |pol: &str, keys: &[pb::KeyOriginInfo], is_change: bool| {
- let derived = parse(&make_policy(pol, keys), coin)
- .unwrap()
- .derive(is_change, address_index)
- .unwrap();
+ let derived = parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &make_policy(pol, keys),
+ coin,
+ )
+ .unwrap()
+ .derive(is_change, address_index)
+ .unwrap();
match derived {
Descriptor::Wsh(wsh) => hex::encode(wsh.witness_script()),
_ => panic!("expected wsh"),
}
};
let witness_script_at_keypath = |pol: &str, keys: &[pb::KeyOriginInfo], keypath: &[u32]| {
- let derived = parse(&make_policy(pol, keys), coin)
- .unwrap()
- .derive_at_keypath(keypath)
- .unwrap();
+ let derived = parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &make_policy(pol, keys),
+ coin,
+ )
+ .unwrap()
+ .derive_at_keypath(keypath)
+ .unwrap();
match derived {
Descriptor::Wsh(wsh) => hex::encode(wsh.witness_script()),
_ => panic!("expected wsh"),
@@ -1470,6 +1516,7 @@ mod tests {
let (is_change, address_index) = (false, 0);
let derived = parse(
+ &mut crate::hal::testing::TestingHal::new(),
&make_policy("tr(@0/**)", core::slice::from_ref(&our_key)),
coin,
)
@@ -1499,20 +1546,28 @@ mod tests {
let output_key =
|pol: &str, keys: &[pb::KeyOriginInfo], is_change: bool, address_index: u32| {
- let derived = parse(&make_policy(pol, keys), coin)
- .unwrap()
- .derive(is_change, address_index)
- .unwrap();
+ 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 derived = parse(&make_policy(pol, keys), coin)
- .unwrap()
- .derive_at_keypath(keypath)
- .unwrap();
+ let derived = parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &make_policy(pol, keys),
+ coin,
+ )
+ .unwrap()
+ .derive_at_keypath(keypath)
+ .unwrap();
match derived {
Descriptor::Tr(tr) => hex::encode(tr.output_key()),
_ => panic!("expected tr"),
@@ -1646,7 +1701,12 @@ mod tests {
{
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>/*)})";
let policy = make_policy(policy_str, &[k0.clone(), k1.clone(), k2.clone()]);
- let parsed_policy = parse(&policy, BtcCoin::Tbtc).unwrap();
+ let parsed_policy = parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &policy,
+ BtcCoin::Tbtc,
+ )
+ .unwrap();
assert_eq!(
parsed_policy.taproot_is_unspendable_internal_key(),
Ok(Some(0))
@@ -1660,7 +1720,12 @@ mod tests {
let policy_str = "tr(@1/<0;1>/*,{and_v(v:multi_a(1,@0/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@0/<0;1>/*,@2/<0;1>/*)})";
let policy = make_policy(policy_str, &[k1.clone(), k0.clone(), k2.clone()]);
- let parsed_policy = parse(&policy, BtcCoin::Tbtc).unwrap();
+ let parsed_policy = parse(
+ &mut crate::hal::testing::TestingHal::new(),
+ &policy,
+ BtcCoin::Tbtc,
+ )
+ .unwrap();
assert_eq!(
parsed_policy.taproot_is_unspendable_internal_key(),
Ok(Some(1))
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 5370e7e..9e39449 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/registration.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/registration.rs
@@ -123,7 +123,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(multisig, keypath)?;
+ super::multisig::validate(hal, multisig, keypath)?;
let xpub_type = XPubType::try_from(request.xpub_type)?;
super::multisig::confirm_extended(
hal,
@@ -158,7 +158,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(policy, coin)?;
+ let parsed = super::policies::parse(hal, policy, coin)?;
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 9955b9a..f38e468 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
@@ -120,6 +120,7 @@ mod tests {
#[test]
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 {
policy: "wsh(multi(2,@0/**,@1/**))".into(),
@@ -127,7 +128,7 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(keypath).unwrap().into()),
+ xpub: Some(crate::keystore::get_xpub_once(&mut mock_hal,keypath).unwrap().into()),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -137,7 +138,8 @@ mod tests {
],
};
- let parsed_policy = super::super::policies::parse(&policy, pb::BtcCoin::Btc).unwrap();
+ let parsed_policy =
+ super::super::policies::parse(&mut mock_hal, &policy, pb::BtcCoin::Btc).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 72c3a91..370b593 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
@@ -61,7 +61,7 @@ pub async fn process(
}
// Keypath and script_config are validated in address_simple().
- let address = super::derive_address_simple(coin, simple_type, keypath)?;
+ let address = super::derive_address_simple(hal, coin, simple_type, keypath)?;
let basic_info = format!("Coin: {}", super::params::get(coin).name);
let confirm_params = confirm::Params {
@@ -98,7 +98,7 @@ 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 = keystore::secp256k1_nonce_commit(
- keystore::secp256k1_get_private_key(keypath)?
+ keystore::secp256k1_get_private_key(hal, keypath)?
.as_slice()
.try_into()
.unwrap(),
@@ -118,7 +118,7 @@ pub async fn process(
};
let sign_result = keystore::secp256k1_sign(
- keystore::secp256k1_get_private_key(keypath)?
+ keystore::secp256k1_get_private_key(hal, keypath)?
.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 d25ac88..78adb11 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -268,6 +268,7 @@ fn is_taproot(script_config_account: &ValidatedScriptConfigWithKeypath) -> bool
///
/// See https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification, item 5:
fn sighash_script(
+ hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
script_config_account: &ValidatedScriptConfigWithKeypath,
keypath: &[u32],
@@ -281,7 +282,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(keypath)?.pubkey_hash160();
+ let pubkey_hash160 = xpub_cache.get_xpub(hal, keypath)?.pubkey_hash160();
let mut result = Vec::<u8>::new();
result.extend_from_slice(b"\x76\xa9\x14");
result.extend_from_slice(&pubkey_hash160);
@@ -381,6 +382,7 @@ async fn handle_prevtx(
}
fn validate_script_config<'a>(
+ hal: &mut impl crate::hal::Hal,
script_config: &'a pb::BtcScriptConfigWithKeypath,
coin_params: &super::params::Params,
) -> Result<ValidatedScriptConfigWithKeypath<'a>, Error> {
@@ -392,7 +394,7 @@ fn validate_script_config<'a>(
}),
keypath,
} => {
- super::multisig::validate(multisig, keypath)?;
+ super::multisig::validate(hal, multisig, keypath)?;
let name = super::multisig::get_name(coin_params.coin, multisig, keypath)?
.ok_or(Error::InvalidInput)?;
Ok(ValidatedScriptConfigWithKeypath {
@@ -407,7 +409,7 @@ fn validate_script_config<'a>(
}),
keypath,
} => {
- let parsed_policy = super::policies::parse(policy, coin_params.coin)?;
+ let parsed_policy = super::policies::parse(hal, policy, coin_params.coin)?;
let name = parsed_policy
.name(coin_params)?
.ok_or(Error::InvalidInput)?;
@@ -444,12 +446,13 @@ fn validate_script_config<'a>(
}
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(config, coin_params))
+ .map(|config| validate_script_config(hal, config, coin_params))
.collect::<Result<Vec<ValidatedScriptConfigWithKeypath>, Error>>()?;
Ok(validated)
}
@@ -463,7 +466,7 @@ async fn validate_input_script_configs<'a>(
return Err(Error::InvalidInput);
}
- let script_configs = validate_script_configs(coin_params, script_configs)?;
+ let script_configs = validate_script_configs(hal, coin_params, script_configs)?;
// 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
@@ -684,7 +687,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(coin_params, &request.output_script_configs)?;
+ validate_script_configs(hal, coin_params, &request.output_script_configs)?;
let mut xpub_cache = Bip32XpubCache::new(Compute::Once);
setup_xpub_cache(&mut xpub_cache, &request.script_configs);
@@ -766,6 +769,7 @@ async fn _process(
// https://github.com/bitcoin/bips/blob/bb8dc57da9b3c6539b88378348728a2ff43f7e9c/bip-0341.mediawiki#common-signature-message
// accumulate `sha_scriptpubkeys`
let pk_script = common::Payload::from(
+ hal,
&mut xpub_cache,
coin_params,
&tx_input.keypath,
@@ -789,7 +793,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(&tx_input.keypath)?,
+ &crate::keystore::secp256k1_get_private_key(hal, &tx_input.keypath)?,
)
.unwrap();
// For Taproot, only key path spends are allowed in silent payments, and we need to
@@ -888,6 +892,7 @@ async fn _process(
)?;
common::Payload::from(
+ hal,
&mut xpub_cache,
coin_params,
&tx_output.keypath,
@@ -1159,7 +1164,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(&tx_input.keypath)?;
+ let xpub = xpub_cache.get_xpub(hal, &tx_input.keypath)?;
let pubkey = bitcoin::PublicKey::from_slice(xpub.public_key())
.map_err(|_| Error::Generic)?;
TaprootSpendInfo::KeySpend(bitcoin::TapTweakHash::from_key_and_tweak(
@@ -1173,7 +1178,7 @@ 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(&mut xpub_cache, &tx_input.keypath)?
+ parsed_policy.taproot_spend_info(hal, &mut xpub_cache, &tx_input.keypath)?
}
_ => return Err(Error::Generic),
};
@@ -1195,7 +1200,7 @@ async fn _process(
next_response.next.has_signature = true;
next_response.next.signature = crate::keystore::secp256k1_schnorr_sign(
- hal.random(),
+ hal,
&tx_input.keypath,
&sighash,
if let TaprootSpendInfo::KeySpend(tweak_hash) = &spend_info {
@@ -1216,6 +1221,7 @@ async fn _process(
outpoint_hash: tx_input.prev_out_hash.as_slice().try_into().unwrap(),
outpoint_index: tx_input.prev_out_index,
sighash_script: &sighash_script(
+ hal,
&mut xpub_cache,
script_config_account,
&tx_input.keypath,
@@ -1227,7 +1233,7 @@ async fn _process(
sighash_flags: SIGHASH_ALL,
});
- let private_key = crate::keystore::secp256k1_get_private_key(&tx_input.keypath)?;
+ let private_key = crate::keystore::secp256k1_get_private_key(hal, &tx_input.keypath)?;
// 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 }) => {
@@ -1830,7 +1836,7 @@ mod tests {
let multisig = pb::btc_script_config::Multisig {
threshold: 1,
xpubs: vec![
- crate::keystore::get_xpub_once(keypath).unwrap().into(),
+ crate::keystore::get_xpub_once(&mut TestingHal::new(), keypath).unwrap().into(),
parse_xpub("xpub6ERxBysTYfQyY4USv6c6J1HNVv9hpZFN9LHVPu47Ac4rK8fLy6NnAeeAHyEsMvG4G66ay5aFZii2VM7wT3KxLKX8Q8keZPd67kRGmrD1WJj").unwrap(),
],
our_xpub_index: 0,
@@ -1847,7 +1853,10 @@ mod tests {
.unwrap();
bitbox02::memory::multisig_set_by_hash(&hash, "test name").unwrap();
- assert!(super::super::multisig::validate(&multisig, keypath).is_ok());
+ assert!(
+ super::super::multisig::validate(&mut TestingHal::new(), &multisig, keypath)
+ .is_ok()
+ );
let mut init_req_invalid = init_req_valid.clone();
init_req_invalid.script_configs = vec![
@@ -3209,6 +3218,8 @@ mod tests {
#[test]
fn test_policy() {
+ let mut mock_hal = TestingHal::new();
+
let transaction = alloc::rc::Rc::new(core::cell::RefCell::new(Transaction::new_policy()));
// Check that previous transactions are streamed, as not all inputs are taproot.
static mut PREVTX_REQUESTED: bool = false;
@@ -3237,7 +3248,7 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(keypath_account).unwrap().into()),
+ xpub: Some(crate::keystore::get_xpub_once(&mut mock_hal, keypath_account).unwrap().into()),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -3251,8 +3262,6 @@ mod tests {
let policy_hash = super::super::policies::get_hash(pb::BtcCoin::Tbtc, &policy).unwrap();
bitbox02::memory::multisig_set_by_hash(&policy_hash, "test policy account name").unwrap();
- let mut mock_hal = TestingHal::new();
-
let result = block_on(process(
&mut mock_hal,
&transaction
@@ -3361,7 +3370,7 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(keypath_account).unwrap().into()),
+ xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -3430,7 +3439,7 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(keypath_account).unwrap().into()),
+ xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
},
],
};
@@ -3539,7 +3548,7 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(keypath_account).unwrap().into()),
+ xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -3596,7 +3605,7 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(keypath_account).unwrap().into()),
+ xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).unwrap().into()),
},
pb::KeyOriginInfo {
root_fingerprint: vec![],
@@ -3645,7 +3654,7 @@ mod tests {
pb::KeyOriginInfo {
root_fingerprint: crate::keystore::root_fingerprint().unwrap(),
keypath: keypath_account.to_vec(),
- xpub: Some(crate::keystore::get_xpub_once(keypath_account).unwrap().into()),
+ xpub: Some(crate::keystore::get_xpub_once(&mut TestingHal::new(),keypath_account).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 d5d7139..1983260 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/xpubs.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/xpubs.rs
@@ -29,7 +29,10 @@ const MAX_XPUBS: usize = 20;
/// Retrieves up to 20 xpubs at once.
///
/// Only standard keypaths are allowed for now.
-pub async fn process_xpubs(request: &pb::BtcXpubsRequest) -> Result<Response, Error> {
+pub async fn process_xpubs(
+ hal: &mut impl crate::hal::Hal,
+ request: &pb::BtcXpubsRequest,
+) -> Result<Response, Error> {
let coin = BtcCoin::try_from(request.coin)?;
super::coin_enabled(coin)?;
@@ -56,7 +59,7 @@ pub async fn process_xpubs(request: &pb::BtcXpubsRequest) -> Result<Response, Er
.map_err(|_| Error::InvalidInput)?;
}
- let xpubs = crate::keystore::get_xpubs_twice(&keypaths)?;
+ let xpubs = crate::keystore::get_xpubs_twice(hal, &keypaths)?;
let xpub_strings: Vec<String> = xpubs
.iter()
.map(|xpub| xpub.serialize_str(xpub_type))
@@ -80,9 +83,10 @@ mod tests {
"",
);
- bitbox02::securechip::fake_event_counter_reset();
+ let mut mock_hal = crate::hal::testing::TestingHal::new();
+ mock_hal.securechip.event_counter_reset();
assert_eq!(
- block_on(process_xpubs(&pb::BtcXpubsRequest {
+ block_on(process_xpubs(&mut mock_hal, &pb::BtcXpubsRequest {
coin: BtcCoin::Btc as _,
xpub_type: XPubType::Xpub as _,
keypaths: vec![
@@ -105,11 +109,11 @@ mod tests {
]
})),
);
- assert_eq!(bitbox02::securechip::fake_event_counter(), 2);
+ assert_eq!(mock_hal.securechip.get_event_counter(), 2);
// Different output type
assert_eq!(
- block_on(process_xpubs(&pb::BtcXpubsRequest {
+ block_on(process_xpubs(&mut crate::hal::testing::TestingHal::new(),&pb::BtcXpubsRequest {
coin: BtcCoin::Btc as _,
xpub_type: XPubType::Tpub as _,
keypaths: vec![
@@ -127,7 +131,7 @@ mod tests {
// Different coin
assert_eq!(
- block_on(process_xpubs(&pb::BtcXpubsRequest {
+ block_on(process_xpubs(&mut crate::hal::testing::TestingHal::new(),&pb::BtcXpubsRequest {
coin: BtcCoin::Ltc as _,
xpub_type: XPubType::Xpub as _,
keypaths: vec![
@@ -150,15 +154,18 @@ mod tests {
mock_unlocked();
// At limit
- let result = block_on(process_xpubs(&pb::BtcXpubsRequest {
- coin: BtcCoin::Btc as _,
- xpub_type: XPubType::Xpub as _,
- keypaths: (0..20)
- .map(|i| pb::Keypath {
- keypath: vec![86 + HARDENED, HARDENED, HARDENED + i],
- })
- .collect(),
- }))
+ let result = block_on(process_xpubs(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::BtcXpubsRequest {
+ coin: BtcCoin::Btc as _,
+ xpub_type: XPubType::Xpub as _,
+ keypaths: (0..20)
+ .map(|i| pb::Keypath {
+ keypath: vec![86 + HARDENED, HARDENED, HARDENED + i],
+ })
+ .collect(),
+ },
+ ))
.unwrap();
match result {
Response::Pubs(pubs) => assert_eq!(pubs.pubs.len(), 20),
@@ -167,15 +174,18 @@ mod tests {
// Over limit
assert_eq!(
- block_on(process_xpubs(&pb::BtcXpubsRequest {
- coin: BtcCoin::Btc as _,
- xpub_type: XPubType::Xpub as _,
- keypaths: (0..21)
- .map(|i| pb::Keypath {
- keypath: vec![86 + HARDENED, HARDENED, HARDENED + i],
- })
- .collect(),
- })),
+ block_on(process_xpubs(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::BtcXpubsRequest {
+ coin: BtcCoin::Btc as _,
+ xpub_type: XPubType::Xpub as _,
+ keypaths: (0..21)
+ .map(|i| pb::Keypath {
+ keypath: vec![86 + HARDENED, HARDENED, HARDENED + i],
+ })
+ .collect(),
+ }
+ )),
Err(Error::InvalidInput)
);
}
@@ -184,13 +194,16 @@ mod tests {
pub fn test_process_invalid_keypath() {
mock_unlocked();
assert_eq!(
- block_on(process_xpubs(&pb::BtcXpubsRequest {
- coin: BtcCoin::Ltc as _,
- xpub_type: XPubType::Xpub as _,
- keypaths: vec![pb::Keypath {
- keypath: vec![84 + HARDENED, 0 + HARDENED, HARDENED],
- },],
- })),
+ block_on(process_xpubs(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::BtcXpubsRequest {
+ coin: BtcCoin::Ltc as _,
+ xpub_type: XPubType::Xpub as _,
+ keypaths: vec![pb::Keypath {
+ keypath: vec![84 + HARDENED, 0 + HARDENED, HARDENED],
+ },],
+ }
+ )),
Err(Error::InvalidInput),
);
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano.rs b/src/rust/bitbox02-rust/src/hww/api/cardano.rs
index afacf84..7643f94 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano.rs
@@ -33,7 +33,7 @@ pub async fn process_api(
request: &Request,
) -> Result<Response, Error> {
match request {
- Request::Xpubs(request) => xpubs::process(request),
+ Request::Xpubs(request) => xpubs::process(hal, request),
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 e0ae4ac..a244de1 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs
@@ -313,8 +313,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(keypath: &[u32]) -> Result<[u8; ADDRESS_HASH_SIZE], ()> {
- let xpub = crate::keystore::ed25519::get_xpub(keypath)?;
+pub 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(hal, keypath)?;
let pubkey_bytes = xpub.pubkey_bytes();
let mut hasher = Blake2bVar::new(ADDRESS_HASH_SIZE).unwrap();
hasher.update(pubkey_bytes);
@@ -336,6 +339,7 @@ fn address_header(params: ¶ms::Params, script_config: &Config) -> u8 {
/// `keypath_prefix` is provided, it is also validated that the address keypaths start with this
/// prefix.
pub fn validate_and_encode_payment_address(
+ hal: &mut impl crate::hal::Hal,
params: ¶ms::Params,
script_config: &Config,
bip44_account: Option<u32>,
@@ -350,8 +354,8 @@ pub fn validate_and_encode_payment_address(
bip44_account,
)?;
- let payment_key_hash = pubkey_hash_at_keypath(&config.keypath_payment)?;
- let stake_key_hash = pubkey_hash_at_keypath(&config.keypath_stake)?;
+ 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 mut bytes: Vec<u8> = Vec::with_capacity(1 + 2 * ADDRESS_HASH_SIZE);
bytes.push(header);
@@ -381,7 +385,7 @@ pub async fn process(
.as_ref()
.ok_or(Error::InvalidInput)?;
- let encoded_address = validate_and_encode_payment_address(params, script_config, None)?;
+ let encoded_address = validate_and_encode_payment_address(hal, params, script_config, None)?;
if request.display {
hal.ui()
@@ -490,12 +494,18 @@ mod tests {
fn test_pubkey_hash_at_keypath() {
crate::keystore::lock();
assert!(
- pubkey_hash_at_keypath(&[1852 + HARDENED, 1815 + HARDENED, HARDENED, 0, 0]).is_err()
+ pubkey_hash_at_keypath(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[1852 + HARDENED, 1815 + HARDENED, HARDENED, 0, 0]
+ )
+ .is_err()
);
mock_unlocked();
assert_eq!(
- pubkey_hash_at_keypath(&[1852 + HARDENED, 1815 + HARDENED, HARDENED, 0, 0]),
+ pubkey_hash_at_keypath(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[1852 + HARDENED, 1815 + HARDENED, HARDENED, 0, 0]),
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 401780f..f05e97e 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
@@ -58,8 +58,12 @@ fn format_value(params: ¶ms::Params, value: u64) -> String {
)
}
-fn make_shelley_witness(keypath: &[u32], tx_body_hash: &[u8; 32]) -> Result<ShelleyWitness, ()> {
- let result = ed25519::sign(keypath, tx_body_hash)?;
+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)?;
Ok(ShelleyWitness {
public_key: result.public_key.as_ref().to_vec(),
signature: result.signature.to_vec(),
@@ -233,6 +237,7 @@ async fn _process(
config: Some(config),
} => {
let encoded_address = super::address::validate_and_encode_payment_address(
+ hal,
params,
config,
Some(bip44_account),
@@ -295,7 +300,7 @@ async fn _process(
let tx_body_hash: [u8; 32] = {
let mut hasher = Blake2bVar::new(32).unwrap();
- cbor::encode_transaction_body(request, cbor::HashedWriter::new(&mut hasher))?;
+ cbor::encode_transaction_body(hal, request, cbor::HashedWriter::new(&mut hasher))?;
let mut out = [0u8; 32];
hasher.finalize_variable(&mut out).or(Err(Error::Generic))?;
@@ -307,7 +312,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(keypath, &tx_body_hash)?);
+ shelley_witnesses.push(make_shelley_witness(hal, keypath, &tx_body_hash)?);
}
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 639e05d..301fa27 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
@@ -45,10 +45,11 @@ 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>(
+ hal: &mut impl crate::hal::Hal,
encoder: &mut Encoder<W>,
keypath: &[u32],
) -> Result<(), Error> {
- let pubkey_hash = pubkey_hash_at_keypath(keypath)?;
+ let pubkey_hash = pubkey_hash_at_keypath(hal, keypath)?;
encoder.array(2)?.u8(0)?.bytes(&pubkey_hash)?;
Ok(())
}
@@ -57,10 +58,11 @@ fn encode_stake_credential<W: Write>(
///
/// 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(
+ hal: &mut impl crate::hal::Hal,
params: ¶ms::Params,
keypath: &[u32],
) -> Result<Vec<u8>, Error> {
- let pubkey_hash = pubkey_hash_at_keypath(keypath)?;
+ let pubkey_hash = pubkey_hash_at_keypath(hal, keypath)?;
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;
@@ -92,6 +94,7 @@ fn encode_set_header<W: Write>(
/// - 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>(
+ hal: &mut impl crate::hal::Hal,
tx: &pb::CardanoSignTransactionRequest,
writer: W,
) -> Result<(), Error> {
@@ -165,11 +168,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(&mut encoder, keypath)?;
+ encode_stake_credential(hal, &mut encoder, keypath)?;
}
certificate::Cert::StakeDeregistration(pb::Keypath { keypath }) => {
encoder.array(2)?.u8(1)?;
- encode_stake_credential(&mut encoder, keypath)?;
+ encode_stake_credential(hal, &mut encoder, keypath)?;
}
certificate::Cert::StakeDelegation(certificate::StakeDelegation {
keypath,
@@ -179,7 +182,7 @@ pub fn encode_transaction_body<W: Write>(
return Err(Error::InvalidInput);
}
encoder.array(3)?.u8(2)?;
- encode_stake_credential(&mut encoder, keypath)?;
+ encode_stake_credential(hal, &mut encoder, keypath)?;
encoder.bytes(pool_keyhash)?;
}
certificate::Cert::VoteDelegation(certificate::VoteDelegation {
@@ -188,7 +191,7 @@ pub fn encode_transaction_body<W: Write>(
drep_credhash,
}) => {
encoder.array(3)?.u8(9)?;
- encode_stake_credential(&mut encoder, keypath)?;
+ encode_stake_credential(hal, &mut encoder, keypath)?;
let drep_type =
certificate::vote_delegation::CardanoDRepType::try_from(*r#type)?;
match drep_type {
@@ -234,7 +237,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(params, keypath)?;
+ let withdrawal_address = encode_withdrawal_address(hal, params, keypath)?;
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 644434c..2c9aaaf 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano/xpubs.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano/xpubs.rs
@@ -24,12 +24,15 @@ 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(request: &pb::CardanoXpubsRequest) -> Result<Response, Error> {
+pub fn process(
+ hal: &mut impl crate::hal::Hal,
+ request: &pb::CardanoXpubsRequest,
+) -> Result<Response, Error> {
let mut xpubs: Vec<Vec<u8>> = Vec::with_capacity(request.keypaths.len());
for pb::Keypath { keypath } in &request.keypaths {
validate_account_shelley(keypath)?;
- let xpub = crate::keystore::ed25519::get_xpub(keypath)?;
+ let xpub = crate::keystore::ed25519::get_xpub(hal, keypath)?;
let mut xpub_bytes = Vec::with_capacity(64);
xpub_bytes.extend_from_slice(xpub.pubkey_bytes());
xpub_bytes.extend_from_slice(xpub.chain_code());
@@ -49,32 +52,41 @@ mod tests {
fn test_process() {
crate::keystore::lock();
assert_eq!(
- process(&pb::CardanoXpubsRequest { keypaths: vec![] }),
+ process(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::CardanoXpubsRequest { keypaths: vec![] }
+ ),
Ok(Response::Xpubs(pb::CardanoXpubsResponse { xpubs: vec![] })),
);
// Locked.
assert_eq!(
- process(&pb::CardanoXpubsRequest {
- keypaths: vec![pb::Keypath {
- keypath: vec![1852 + HARDENED, 1815 + HARDENED, HARDENED]
- }],
- }),
+ process(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::CardanoXpubsRequest {
+ keypaths: vec![pb::Keypath {
+ keypath: vec![1852 + HARDENED, 1815 + HARDENED, HARDENED]
+ }],
+ }
+ ),
Err(Error::Generic),
);
mock_unlocked();
assert_eq!(
- process(&pb::CardanoXpubsRequest {
- keypaths: vec![
- pb::Keypath {
- keypath: vec![1852 + HARDENED, 1815 + HARDENED, HARDENED]
- },
- pb::Keypath {
- keypath: vec![1852 + HARDENED, 1815 + HARDENED, 1 + HARDENED]
- }
- ],
- }),
+ process(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::CardanoXpubsRequest {
+ keypaths: vec![
+ pb::Keypath {
+ keypath: vec![1852 + HARDENED, 1815 + HARDENED, HARDENED]
+ },
+ pb::Keypath {
+ keypath: vec![1852 + HARDENED, 1815 + HARDENED, 1 + HARDENED]
+ }
+ ],
+ }
+ ),
Ok(Response::Xpubs(pb::CardanoXpubsResponse {
xpubs: vec![
vec![
@@ -111,11 +123,14 @@ mod tests {
];
for invalid_keypath in invalid_keypaths {
assert_eq!(
- process(&pb::CardanoXpubsRequest {
- keypaths: vec![pb::Keypath {
- keypath: invalid_keypath.to_vec(),
- },],
- }),
+ process(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::CardanoXpubsRequest {
+ keypaths: vec![pb::Keypath {
+ keypath: invalid_keypath.to_vec(),
+ },],
+ }
+ ),
Err(Error::InvalidInput),
);
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/electrum.rs b/src/rust/bitbox02-rust/src/hww/api/electrum.rs
index a1e583e..3613bf7 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 @@ const ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_TWO: u32 = 1112098098 + HARDENED;
/// Note: the result of this is only meant to be used for encryption by Electrum.
/// The resulting xpub must not be used to derive addresses or to receive coins.
pub async fn process(
+ hal: &mut impl crate::hal::Hal,
pb::ElectrumEncryptionKeyRequest { keypath }: &pb::ElectrumEncryptionKeyRequest,
) -> Result<Response, Error> {
if *keypath
@@ -39,7 +40,7 @@ pub async fn process(
{
return Err(Error::InvalidInput);
}
- let xpub = keystore::get_xpub_twice(keypath)
+ let xpub = keystore::get_xpub_twice(hal, keypath)
.or(Err(Error::InvalidInput))?
.serialize_str(bip32::XPubType::Xpub)?;
@@ -62,7 +63,7 @@ mod tests {
// All good.
assert_eq!(
- block_on(process(&pb::ElectrumEncryptionKeyRequest {
+ block_on(process(&mut crate::hal::testing::TestingHal::new(),&pb::ElectrumEncryptionKeyRequest {
keypath: vec![
ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_ONE,
ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_TWO
@@ -77,21 +78,27 @@ mod tests {
// Invalid keypath.
assert_eq!(
- block_on(process(&pb::ElectrumEncryptionKeyRequest {
- keypath: vec![ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_ONE, 0]
- })),
+ block_on(process(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::ElectrumEncryptionKeyRequest {
+ keypath: vec![ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_ONE, 0]
+ }
+ )),
Err(Error::InvalidInput),
);
// Invalid keypath (wrong length).
assert_eq!(
- block_on(process(&pb::ElectrumEncryptionKeyRequest {
- keypath: vec![
- ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_ONE,
- ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_TWO,
- 0
- ]
- })),
+ block_on(process(
+ &mut crate::hal::testing::TestingHal::new(),
+ &pb::ElectrumEncryptionKeyRequest {
+ keypath: vec![
+ ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_ONE,
+ ELECTRUM_WALLET_ENCRYPTION_KEYPATH_LEVEL_TWO,
+ 0
+ ]
+ }
+ )),
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 8b09707..8182ddd 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs
@@ -45,7 +45,7 @@ async fn process_address(
if !super::keypath::is_valid_keypath_address(&request.keypath) {
return Err(Error::InvalidInput);
}
- let pubkey = crate::keystore::get_xpub_twice(&request.keypath)
+ let pubkey = crate::keystore::get_xpub_twice(hal, &request.keypath)
.or(Err(Error::InvalidInput))?
.pubkey_uncompressed()?;
let address = super::address::from_pubkey(&pubkey);
@@ -70,7 +70,10 @@ async fn process_address(
Ok(Response::Pub(pb::PubResponse { r#pub: address }))
}
-fn process_xpub(request: &pb::EthPubRequest) -> Result<Response, Error> {
+fn process_xpub(
+ hal: &mut impl crate::hal::Hal,
+ request: &pb::EthPubRequest,
+) -> Result<Response, Error> {
if request.display {
// No xpub user verification for now.
return Err(Error::InvalidInput);
@@ -79,7 +82,7 @@ fn process_xpub(request: &pb::EthPubRequest) -> Result<Response, Error> {
if !super::keypath::is_valid_keypath_xpub(&request.keypath) {
return Err(Error::InvalidInput);
}
- let xpub = keystore::get_xpub_twice(&request.keypath)
+ let xpub = keystore::get_xpub_twice(hal, &request.keypath)
.or(Err(Error::InvalidInput))?
.serialize_str(bip32::XPubType::Xpub)?;
@@ -93,7 +96,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(request),
+ OutputType::Xpub => process_xpub(hal, request),
}
}
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 c8aa2f4..38bf3aa 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
@@ -389,7 +389,7 @@ 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 = keystore::secp256k1_nonce_commit(
- &keystore::secp256k1_get_private_key(request.keypath())?
+ &keystore::secp256k1_get_private_key(hal, request.keypath())?
.as_slice()
.try_into()
.unwrap(),
@@ -408,7 +408,7 @@ pub async fn _process(
None => [0; 32],
};
let sign_result = keystore::secp256k1_sign(
- &keystore::secp256k1_get_private_key(request.keypath())?
+ &keystore::secp256k1_get_private_key(hal, request.keypath())?
.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 a2430e5..54318a9 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
@@ -563,7 +563,7 @@ pub async fn process(
let host_nonce = match request.host_nonce_commitment {
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
let signer_commitment = keystore::secp256k1_nonce_commit(
- keystore::secp256k1_get_private_key(&request.keypath)?
+ keystore::secp256k1_get_private_key(hal, &request.keypath)?
.as_slice()
.try_into()
.unwrap(),
@@ -582,7 +582,7 @@ pub async fn process(
};
let sign_result = keystore::secp256k1_sign(
- keystore::secp256k1_get_private_key(&request.keypath)?
+ keystore::secp256k1_get_private_key(hal, &request.keypath)?
.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 2a15ddf..79b6b07 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
@@ -67,7 +67,7 @@ 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 = keystore::secp256k1_nonce_commit(
- keystore::secp256k1_get_private_key(&request.keypath)?
+ keystore::secp256k1_get_private_key(hal, &request.keypath)?
.as_slice()
.try_into()
.unwrap(),
@@ -87,7 +87,7 @@ pub async fn process(
};
let sign_result = keystore::secp256k1_sign(
- keystore::secp256k1_get_private_key(&request.keypath)?
+ keystore::secp256k1_get_private_key(hal, &request.keypath)?
.as_slice()
.try_into()
.unwrap(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/restore.rs b/src/rust/bitbox02-rust/src/hww/api/restore.rs
index 68945fd..fa78f4d 100644
--- a/src/rust/bitbox02-rust/src/hww/api/restore.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/restore.rs
@@ -201,19 +201,21 @@ mod tests {
Ok(Response::Success(pb::Success {}))
);
assert_eq!(mock_hal.securechip.get_event_counter(), 8);
- drop(mock_hal); // to remove mutable borrow of counter
- assert_eq!(counter, 2);
+
assert!(!crate::keystore::is_locked());
assert!(memory::is_initialized());
// 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().unwrap()),
+ hex::encode(crate::keystore::copy_seed(&mut mock_hal).unwrap()),
"19f1bcfccf3e9d497cd245cf864ff0d42216625258d4f68d56b571aceb329257"
);
assert_eq!(
- hex::encode(crate::keystore::copy_bip39_seed().unwrap()),
+ hex::encode(crate::keystore::copy_bip39_seed(&mut mock_hal).unwrap()),
"257724bccc8858cfe565b456b01263a4a6a45184fab4531f5c199649207a74e74c399a01d4f957258c05cee818369b31404c884a4b7a29ff6886bae6700fb56a"
);
+
+ drop(mock_hal); // to remove mutable borrow of counter
+ assert_eq!(counter, 2);
}
}
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 332c084..0d51e82 100644
--- a/src/rust/bitbox02-rust/src/hww/api/set_password.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/set_password.rs
@@ -40,7 +40,8 @@ pub async fn process(
hal.ui().status(&format!("Error\n{:?}", err), false).await;
return Err(Error::Generic);
}
- unlock::unlock_bip39(hal, &keystore::copy_seed()?).await;
+ let seed = keystore::copy_seed(hal)?;
+ unlock::unlock_bip39(hal, &seed).await;
Ok(Response::Success(pb::Success {}))
}
@@ -81,10 +82,11 @@ mod tests {
Ok(Response::Success(pb::Success {}))
);
assert_eq!(mock_hal.securechip.get_event_counter(), 9);
+ assert!(!keystore::is_locked());
+ assert!(keystore::copy_seed(&mut mock_hal).unwrap().len() == 32);
+
drop(mock_hal); // to remove mutable borrow of counter
assert_eq!(counter, 2);
- assert!(!keystore::is_locked());
- assert!(keystore::copy_seed().unwrap().len() == 32);
}
/// Shorter host entropy results in shorter seed.
@@ -106,7 +108,7 @@ mod tests {
Ok(Response::Success(pb::Success {}))
);
assert!(!keystore::is_locked());
- assert!(keystore::copy_seed().unwrap().len() == 16);
+ assert!(keystore::copy_seed(&mut mock_hal).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 2fe7b34..58f1ac5 100644
--- a/src/rust/bitbox02-rust/src/hww/api/show_mnemonic.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/show_mnemonic.rs
@@ -31,7 +31,7 @@ pub async fn process(hal: &mut impl crate::hal::Hal) -> Result<Response, Error>
let seed = if bitbox02::memory::is_initialized() {
unlock::unlock_keystore(hal, "Unlock device", unlock::CanCancel::Yes).await?
} else {
- crate::keystore::copy_seed()?
+ crate::keystore::copy_seed(hal)?
};
crate::bip39::mnemonic_from_seed(&seed)?
diff --git a/src/rust/bitbox02-rust/src/keystore.rs b/src/rust/bitbox02-rust/src/keystore.rs
index 854cfed..2592e1d 100644
--- a/src/rust/bitbox02-rust/src/keystore.rs
+++ b/src/rust/bitbox02-rust/src/keystore.rs
@@ -20,8 +20,8 @@ use alloc::vec::Vec;
use crate::bip32;
use crate::hal::{Random, SecureChip};
+use bitbox02::keystore;
pub use bitbox02::keystore::SignResult;
-use bitbox02::{keystore, securechip};
use util::bip32::HARDENED;
use util::cell::SyncCell;
@@ -102,17 +102,23 @@ struct RetainedEncryptedBuffer {
impl RetainedEncryptedBuffer {
fn from_buffer(
- random: &mut impl crate::hal::Random,
+ hal: &mut impl crate::hal::Hal,
data: &[u8],
purpose: &'static str,
) -> Result<Self, Error> {
- let rand: [u8; 32] = random.random_32_bytes().as_slice().try_into().unwrap();
+ let rand: [u8; 32] = hal
+ .random()
+ .random_32_bytes()
+ .as_slice()
+ .try_into()
+ .unwrap();
let encryption_key = stretch_retained_seed_encryption_key(
+ hal,
&rand,
&format!("{}_in", purpose),
&format!("{}_out", purpose),
)?;
- let iv_rand = random.random_32_bytes();
+ let iv_rand = hal.random().random_32_bytes();
let iv: &[u8; 16] = iv_rand.first_chunk::<16>().unwrap();
let encrypted = bitbox_aes::encrypt_with_hmac(iv, &encryption_key, data);
Ok(RetainedEncryptedBuffer {
@@ -122,8 +128,12 @@ impl RetainedEncryptedBuffer {
})
}
- fn decrypt(&self) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
+ fn decrypt(
+ &self,
+ hal: &mut impl crate::hal::Hal,
+ ) -> 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),
@@ -183,9 +193,9 @@ fn hash_seed(seed: &[u8]) -> Result<[u8; 32], Error> {
Ok(Hmac::<sha256::Hash>::from_engine(engine).to_byte_array())
}
-fn retain_seed(random: &mut impl crate::hal::Random, seed: &[u8]) -> Result<(), Error> {
+fn retain_seed(hal: &mut impl crate::hal::Hal, seed: &[u8]) -> Result<(), Error> {
RETAINED_SEED.write(Some(RetainedEncryptedBuffer::from_buffer(
- random,
+ hal,
seed,
"keystore_retained_seed_access",
)?));
@@ -193,9 +203,9 @@ fn retain_seed(random: &mut impl crate::hal::Random, seed: &[u8]) -> Result<(),
Ok(())
}
-fn retain_bip39_seed(random: &mut impl crate::hal::Random, bip39_seed: &[u8]) -> Result<(), Error> {
+fn retain_bip39_seed(hal: &mut impl crate::hal::Hal, bip39_seed: &[u8]) -> Result<(), Error> {
RETAINED_BIP39_SEED.write(Some(RetainedEncryptedBuffer::from_buffer(
- random,
+ hal,
bip39_seed,
"keystore_retained_bip39_seed_access",
)?));
@@ -237,7 +247,7 @@ fn encrypt_and_store_seed_internal(
return Err(Error::Memory);
}
- retain_seed(hal.random(), seed)
+ retain_seed(hal, seed)
}
/// Restores a seed. This also unlocks the keystore with this seed.
@@ -267,13 +277,13 @@ 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().map_err(|_| Error::InvalidState)?;
+ let bip39_seed = copy_bip39_seed(hal).map_err(|_| Error::InvalidState)?;
let root_fingerprint = ROOT_FINGERPRINT.read().ok_or(Error::InvalidState)?;
encrypt_and_store_seed_internal(hal, seed, new_password)?;
// Re-retain the bip39 seed and root fingerprint
- retain_bip39_seed(hal.random(), bip39_seed.as_slice())?;
+ retain_bip39_seed(hal, bip39_seed.as_slice())?;
ROOT_FINGERPRINT.write(Some(root_fingerprint));
Ok(())
@@ -346,7 +356,7 @@ pub fn unlock(
panic!("Seed has suddenly changed. This should never happen.");
}
} else {
- retain_seed(hal.random(), &seed)?;
+ retain_seed(hal, &seed)?;
}
bitbox02::memory::smarteeprom_reset_unlock_attempts();
Ok(seed)
@@ -364,7 +374,7 @@ pub fn get_remaining_unlock_attempts() -> u8 {
/// `mnemonic_passphrase` is the bip39 passphrase used in the derivation. Use the empty string if no
/// passphrase is needed or provided.
pub async fn unlock_bip39(
- random: &mut impl crate::hal::Random,
+ hal: &mut impl crate::hal::Hal,
seed: &[u8],
mnemonic_passphrase: &str,
yield_now: impl AsyncFn(),
@@ -381,7 +391,7 @@ pub async fn unlock_bip39(
return Err(Error::Memory);
}
- retain_bip39_seed(random, bip39_seed.as_slice())?;
+ retain_bip39_seed(hal, bip39_seed.as_slice())?;
// Store root fingerprint.
ROOT_FINGERPRINT.write(Some(root_fingerprint));
@@ -389,16 +399,16 @@ pub async fn unlock_bip39(
}
/// Returns a copy of the retained seed. Errors if the keystore is locked.
-pub fn copy_seed() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- RETAINED_SEED.read().ok_or(())?.decrypt().map_err(|_| ())
+pub fn copy_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+ RETAINED_SEED.read().ok_or(())?.decrypt(hal).map_err(|_| ())
}
/// Returns a copy of the retained bip39 seed. Errors if the keystore is locked.
-pub fn copy_bip39_seed() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+pub fn copy_bip39_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
RETAINED_BIP39_SEED
.read()
.ok_or(())?
- .decrypt()
+ .decrypt(hal)
.map_err(|_| ())
}
@@ -437,12 +447,14 @@ pub fn create_and_store_seed(
}
/// Returns the keystore's seed encoded as a BIP-39 mnemonic.
-pub fn get_bip39_mnemonic() -> Result<zeroize::Zeroizing<String>, ()> {
- crate::bip39::mnemonic_from_seed(©_seed()?)
+pub fn get_bip39_mnemonic(
+ hal: &mut impl crate::hal::Hal,
+) -> Result<zeroize::Zeroizing<String>, ()> {
+ crate::bip39::mnemonic_from_seed(©_seed(hal)?)
}
-fn get_xprv(keypath: &[u32]) -> Result<bip32::Xprv, ()> {
- let bip39_seed = copy_bip39_seed()?;
+fn get_xprv(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<bip32::Xprv, ()> {
+ let bip39_seed = copy_bip39_seed(hal)?;
let xprv: bip32::Xprv =
bitcoin::bip32::Xpriv::new_master(bitcoin::NetworkKind::Main, &bip39_seed)
.map_err(|_| ())?
@@ -455,17 +467,23 @@ fn get_xprv(keypath: &[u32]) -> Result<bip32::Xprv, ()> {
}
/// Get the private key at the keypath.
-pub fn secp256k1_get_private_key(keypath: &[u32]) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let xprv = get_xprv(keypath)?;
+pub fn secp256k1_get_private_key(
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+ let xprv = get_xprv(hal, keypath)?;
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(keypath: &[u32]) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let privkey = secp256k1_get_private_key(keypath)?;
- if privkey == secp256k1_get_private_key(keypath)? {
+pub 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)? {
Ok(privkey)
} else {
Err(())
@@ -475,8 +493,8 @@ pub fn secp256k1_get_private_key_twice(keypath: &[u32]) -> Result<zeroize::Zeroi
/// 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(keypath: &[u32]) -> Result<bip32::Xpub, ()> {
- let xpriv = get_xprv(keypath)?;
+pub fn get_xpub_once(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<bip32::Xpub, ()> {
+ let xpriv = get_xprv(hal, keypath)?;
let xpub = bitcoin::bip32::Xpub::from_priv(SECP256K1, &xpriv.xprv);
Ok(bip32::Xpub::from(xpub))
}
@@ -484,9 +502,9 @@ pub fn get_xpub_once(keypath: &[u32]) -> Result<bip32::Xpub, ()> {
/// 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(keypath: &[u32]) -> Result<bip32::Xpub, ()> {
- let res1 = get_xpub_once(keypath)?;
- let res2 = get_xpub_once(keypath)?;
+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)?;
if res1 != res2 {
return Err(());
}
@@ -495,7 +513,10 @@ pub fn get_xpub_twice(keypath: &[u32]) -> Result<bip32::Xpub, ()> {
/// 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(keypaths: &[&[u32]]) -> Result<Vec<bip32::Xpub>, ()> {
+pub fn get_xpubs_twice(
+ hal: &mut impl crate::hal::Hal,
+ keypaths: &[&[u32]],
+) -> Result<Vec<bip32::Xpub>, ()> {
if is_locked() {
return Err(());
}
@@ -504,8 +525,8 @@ pub fn get_xpubs_twice(keypaths: &[&[u32]]) -> Result<Vec<bip32::Xpub>, ()> {
}
// 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(&[])?;
- let xprv2 = get_xprv(&[])?;
+ let xprv = get_xprv(hal, &[])?;
+ let xprv2 = get_xprv(hal, &[])?;
let mut out = Vec::with_capacity(keypaths.len());
for keypath in keypaths {
@@ -546,13 +567,14 @@ 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(
+ hal: &mut impl crate::hal::Hal,
encryption_key: &[u8; 32],
purpose_in: &str,
purpose_out: &str,
) -> Result<zeroize::Zeroizing<Vec<u8>>, Error> {
let salted_in = crate::salt::hash_data(encryption_key, purpose_in).map_err(|_| Error::Salt)?;
- let kdf = securechip::kdf(salted_in.as_slice())?;
+ let kdf = hal.securechip().kdf(salted_in.as_slice())?;
let salted_out =
crate::salt::hash_data(encryption_key, purpose_out).map_err(|_| Error::Salt)?;
@@ -574,8 +596,11 @@ pub extern "C" fn rust_keystore_is_locked() -> bool {
is_locked()
}
-fn bip85_entropy(keypath: &[u32]) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let priv_key = secp256k1_get_private_key_twice(keypath)?;
+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 mut engine = HmacEngine::<sha512::Hash>::new(b"bip-entropy-from-k");
engine.input(&priv_key);
@@ -588,7 +613,11 @@ fn bip85_entropy(keypath: &[u32]) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
/// 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(words: u32, index: u32) -> Result<zeroize::Zeroizing<String>, ()> {
+pub fn bip85_bip39(
+ hal: &mut impl crate::hal::Hal,
+ words: u32,
+ index: u32,
+) -> Result<zeroize::Zeroizing<String>, ()> {
if index >= HARDENED {
return Err(());
}
@@ -608,7 +637,7 @@ pub fn bip85_bip39(words: u32, index: u32) -> Result<zeroize::Zeroizing<String>,
index + HARDENED,
];
- let entropy = bip85_entropy(&keypath)?;
+ let entropy = bip85_entropy(hal, &keypath)?;
crate::bip39::mnemonic_from_seed(&entropy[..seed_size])
}
@@ -617,7 +646,10 @@ pub fn bip85_bip39(words: u32, index: u32) -> Result<zeroize::Zeroizing<String>,
/// '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(index: u32) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+pub fn bip85_ln(
+ hal: &mut impl crate::hal::Hal,
+ index: u32,
+) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
if index >= HARDENED {
return Err(());
}
@@ -629,7 +661,7 @@ pub fn bip85_ln(index: u32) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
index + HARDENED,
];
- let mut entropy = bip85_entropy(&keypath)?;
+ let mut entropy = bip85_entropy(hal, &keypath)?;
entropy.truncate(16);
Ok(entropy)
}
@@ -689,12 +721,12 @@ pub fn secp256k1_nonce_commit(
/// Sign a message using the private key at the keypath, which is optionally tweaked with the given
/// tweak.
pub fn secp256k1_schnorr_sign(
- random: &mut impl crate::hal::Random,
+ 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(keypath)?;
+ let private_key = secp256k1_get_private_key(hal, keypath)?;
let mut keypair =
bitcoin::secp256k1::Keypair::from_seckey_slice(SECP256K1, &private_key).map_err(|_| ())?;
@@ -707,7 +739,7 @@ pub fn secp256k1_schnorr_sign(
.map_err(|_| ())?;
}
- let aux_rand = random.random_32_bytes();
+ let aux_rand = hal.random().random_32_bytes();
let sig = SECP256K1.sign_schnorr_with_aux_rand(
&bitcoin::secp256k1::Message::from_digest(*msg),
&keypair,
@@ -718,8 +750,8 @@ pub fn secp256k1_schnorr_sign(
/// Get the seed to be used for u2f
#[cfg(feature = "app-u2f")]
-pub fn get_u2f_seed() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let bip39_seed = copy_bip39_seed()?;
+pub fn get_u2f_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+ let bip39_seed = copy_bip39_seed(hal)?;
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.
@@ -732,7 +764,7 @@ pub fn get_u2f_seed() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
#[cfg(feature = "app-u2f")]
#[unsafe(no_mangle)]
pub extern "C" fn rust_keystore_get_u2f_seed(mut seed_out: util::bytes::BytesMut) -> bool {
- match get_u2f_seed() {
+ match get_u2f_seed(&mut crate::hal::BitBox02Hal::new()) {
Ok(seed) => {
seed_out.as_mut().copy_from_slice(&seed);
true
@@ -745,7 +777,7 @@ pub extern "C" fn rust_keystore_get_u2f_seed(mut seed_out: util::bytes::BytesMut
pub mod testing {
/// This mocks an unlocked keystore with the given bip39 recovery words and bip39 passphrase.
pub fn mock_unlocked_using_mnemonic(mnemonic: &str, passphrase: &str) {
- let mut mock_hal = crate::hal::testing::TestingRandom::new();
+ let mut mock_hal = crate::hal::testing::TestingHal::new();
let seed = crate::bip39::mnemonic_to_seed(mnemonic).unwrap();
super::retain_seed(&mut mock_hal, &seed).unwrap();
util::bb02_async::block_on(super::unlock_bip39(
@@ -770,7 +802,7 @@ pub mod testing {
mod tests {
use super::*;
- use crate::hal::testing::{TestingHal, TestingRandom};
+ use crate::hal::testing::TestingHal;
use hex_lit::hex;
use bitbox02::testing::mock_memory;
@@ -781,13 +813,14 @@ mod tests {
#[test]
fn test_copy_seed() {
+ let mut mock_hal = TestingHal::new();
// 12 words
mock_unlocked_using_mnemonic(
"trust cradle viable innocent stand equal little small junior frost laundry room",
"",
);
assert_eq!(
- copy_seed().unwrap().as_slice(),
+ copy_seed(&mut mock_hal).unwrap().as_slice(),
b"\xe9\xa6\x3f\xcd\x3a\x4d\x48\x98\x20\xa6\x63\x79\x2b\xad\xf6\xdd",
);
@@ -797,7 +830,7 @@ mod tests {
"",
);
assert_eq!(
- copy_seed().unwrap().as_slice(),
+ copy_seed(&mut mock_hal).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",
);
@@ -806,7 +839,7 @@ mod tests {
"",
);
assert_eq!(
- copy_seed().unwrap().as_slice(),
+ copy_seed(&mut mock_hal).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",
);
}
@@ -864,7 +897,10 @@ mod tests {
let mut hal = TestingHal::new();
hal.random.mock_next(seed_random);
assert!(create_and_store_seed(&mut hal, "password", &host_entropy[..size]).is_ok());
- assert_eq!(copy_seed().unwrap().as_slice(), &expected_seed[..size]);
+ assert_eq!(
+ copy_seed(&mut hal).unwrap().as_slice(),
+ &expected_seed[..size]
+ );
// Check the seed has been stored encrypted with the expected encryption key.
// Decrypt and check seed.
let cipher = bitbox02::memory::get_encrypted_seed_and_hmac().unwrap();
@@ -910,8 +946,7 @@ mod tests {
let unlocked_seed = unlock(&mut mock_hal, "old_password").unwrap();
assert_eq!(unlocked_seed.as_slice(), seed.as_slice());
- let mut random = crate::hal::testing::TestingRandom::new();
- assert!(block_on(unlock_bip39(&mut random, &seed, "", async || {})).is_ok());
+ assert!(block_on(unlock_bip39(&mut mock_hal, &seed, "", async || {})).is_ok());
// Step 3: Re-encrypt with new password
assert!(re_encrypt_seed(&mut mock_hal, &seed, "new_password").is_ok());
@@ -939,11 +974,10 @@ mod tests {
// Initial setup
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password1").is_ok());
- let mut random = crate::hal::testing::TestingRandom::new();
- assert!(block_on(unlock_bip39(&mut random, &seed, "", async || {})).is_ok());
+ assert!(block_on(unlock_bip39(&mut mock_hal, &seed, "", async || {})).is_ok());
- let seed_reference = copy_seed().unwrap();
- let bip39_seed_reference = copy_bip39_seed().unwrap();
+ let seed_reference = copy_seed(&mut mock_hal).unwrap();
+ let bip39_seed_reference = copy_bip39_seed(&mut mock_hal).unwrap();
let root_fingerprint_reference = root_fingerprint().unwrap();
// Re-encrypt multiple times
@@ -952,9 +986,12 @@ mod tests {
assert!(re_encrypt_seed(&mut mock_hal, &seed_reference, new_password).is_ok());
// Verify everything is still there and correct
- assert_eq!(copy_seed().unwrap().as_slice(), seed_reference.as_slice());
assert_eq!(
- copy_bip39_seed().unwrap().as_slice(),
+ copy_seed(&mut mock_hal).unwrap().as_slice(),
+ seed_reference.as_slice()
+ );
+ assert_eq!(
+ copy_bip39_seed(&mut mock_hal).unwrap().as_slice(),
bip39_seed_reference.as_slice()
);
assert_eq!(root_fingerprint().unwrap(), root_fingerprint_reference);
@@ -973,8 +1010,7 @@ mod tests {
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
unlock(&mut mock_hal, "password").unwrap();
- let mut random = crate::hal::testing::TestingRandom::new();
- assert!(block_on(unlock_bip39(&mut random, &seed, "", async || {})).is_ok());
+ assert!(block_on(unlock_bip39(&mut mock_hal, &seed, "", async || {})).is_ok());
// Try to re-encrypt with invalid seed size
assert!(matches!(
@@ -988,19 +1024,19 @@ mod tests {
mock_memory();
lock();
- let mut random = crate::hal::testing::TestingRandom::new();
+ let mut mock_hal = TestingHal::new();
let bip39_seed = hex!(
"2b3c63de86f0f2b13cc6a36c1ba2314fbc1b40c77ab9cb64e96ba4d5c62fc204748ca6626a9f035e7d431bce8c9210ec0bdffc2e7db873dee56c8ac2153eee9a"
);
// Before retention, should not be available
- assert!(copy_bip39_seed().is_err());
+ assert!(copy_bip39_seed(&mut mock_hal).is_err());
// Retain the BIP39 seed
- assert!(retain_bip39_seed(&mut random, &bip39_seed).is_ok());
+ assert!(retain_bip39_seed(&mut mock_hal, &bip39_seed).is_ok());
// Should now be available
- let retrieved = copy_bip39_seed().unwrap();
+ let retrieved = copy_bip39_seed(&mut mock_hal).unwrap();
assert_eq!(retrieved.as_slice(), bip39_seed.as_slice());
}
@@ -1008,7 +1044,7 @@ mod tests {
fn test_retain_bip39_seed_overwrites_previous() {
mock_memory();
lock();
- let mut random = crate::hal::testing::TestingRandom::new();
+ let mut mock_hal = TestingHal::new();
let bip39_seed1 = hex!(
"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
);
@@ -1017,16 +1053,16 @@ mod tests {
);
// Retain first seed
- assert!(retain_bip39_seed(&mut random, &bip39_seed1).is_ok());
+ assert!(retain_bip39_seed(&mut mock_hal, &bip39_seed1).is_ok());
assert_eq!(
- copy_bip39_seed().unwrap().as_slice(),
+ copy_bip39_seed(&mut mock_hal).unwrap().as_slice(),
bip39_seed1.as_slice()
);
// Retain second seed (should overwrite)
- assert!(retain_bip39_seed(&mut random, &bip39_seed2).is_ok());
+ assert!(retain_bip39_seed(&mut mock_hal, &bip39_seed2).is_ok());
assert_eq!(
- copy_bip39_seed().unwrap().as_slice(),
+ copy_bip39_seed(&mut mock_hal).unwrap().as_slice(),
bip39_seed2.as_slice()
);
}
@@ -1044,19 +1080,22 @@ mod tests {
assert!(encrypt_and_store_seed(&mut TestingHal::new(), &seed, "password").is_ok());
// Create new (different) seed.
assert!(encrypt_and_store_seed(&mut TestingHal::new(), &seed2, "password").is_ok());
- assert_eq!(copy_seed().unwrap().as_slice(), &seed2);
+ assert_eq!(
+ copy_seed(&mut TestingHal::new()).unwrap().as_slice(),
+ &seed2
+ );
}
#[test]
fn test_lock() {
- let mut random = crate::hal::testing::TestingRandom::new();
+ let mut mock_hal = TestingHal::new();
lock();
assert!(is_locked());
let seed = hex!("cb33c20cea62a5c277527e2002da82e6e2b37450a755143a540a54cea8da9044");
- assert!(encrypt_and_store_seed(&mut TestingHal::new(), &seed, "password").is_ok());
+ assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
assert!(is_locked()); // still locked, it is only unlocked after unlock_bip39.
- assert!(block_on(unlock_bip39(&mut random, &seed, "foo", async || {})).is_ok());
+ assert!(block_on(unlock_bip39(&mut mock_hal, &seed, "foo", async || {})).is_ok());
assert!(!is_locked());
lock();
assert!(is_locked());
@@ -1127,7 +1166,7 @@ mod tests {
// Still seeded.
assert!(bitbox02::memory::is_seeded());
// Wrong password does not lock the keystore again if already unlocked.
- assert!(copy_seed().is_ok());
+ assert!(copy_seed(&mut mock_hal).is_ok());
}
// Last attempt, triggers reset.
assert!(matches!(
@@ -1136,7 +1175,7 @@ mod tests {
));
// Last wrong attempt locks & resets. There is no more seed.
assert!(!bitbox02::memory::is_seeded());
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
assert!(matches!(
unlock(&mut mock_hal, "password"),
Err(Error::Unseeded)
@@ -1158,7 +1197,7 @@ mod tests {
assert!(encrypt_and_store_seed(&mut mock_hal, &seed, "password").is_ok());
lock();
assert!(is_locked());
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
for attempt in 1..bitbox02::memory::MAX_UNLOCK_ATTEMPTS {
assert!(matches!(
@@ -1171,7 +1210,7 @@ mod tests {
bitbox02::memory::MAX_UNLOCK_ATTEMPTS - attempt
);
assert!(is_locked());
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
assert!(bitbox02::memory::is_seeded());
}
@@ -1180,7 +1219,7 @@ mod tests {
Err(Error::MaxAttemptsExceeded)
));
assert!(is_locked());
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
assert!(!bitbox02::memory::is_seeded());
assert!(matches!(
unlock(&mut mock_hal, "password"),
@@ -1219,7 +1258,7 @@ mod tests {
Err(Error::MaxAttemptsExceeded)
));
assert!(is_locked());
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
assert!(!bitbox02::memory::is_seeded());
}
@@ -1252,16 +1291,16 @@ mod tests {
}
wrong_attempt(&mut mock_hal);
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
assert_eq!(unlock(&mut mock_hal, "password").unwrap().as_slice(), seed);
- assert!(copy_seed().is_ok());
+ assert!(copy_seed(&mut mock_hal).is_ok());
lock();
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
wrong_attempt(&mut mock_hal);
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
assert!(bitbox02::memory::is_seeded());
}
@@ -1283,7 +1322,7 @@ mod tests {
lock();
assert_eq!(unlock(&mut mock_hal, "password").unwrap().as_slice(), seed);
- assert!(copy_seed().is_ok());
+ assert!(copy_seed(&mut mock_hal).is_ok());
fn wrong_attempt(hal: &mut impl crate::hal::Hal) {
assert!(matches!(
@@ -1297,13 +1336,13 @@ mod tests {
}
wrong_attempt(&mut mock_hal);
- assert!(copy_seed().is_ok());
+ assert!(copy_seed(&mut mock_hal).is_ok());
assert_eq!(unlock(&mut mock_hal, "password").unwrap().as_slice(), seed);
- assert!(copy_seed().is_ok());
+ assert!(copy_seed(&mut mock_hal).is_ok());
wrong_attempt(&mut mock_hal);
- assert!(copy_seed().is_ok());
+ assert!(copy_seed(&mut mock_hal).is_ok());
assert!(bitbox02::memory::is_seeded());
}
@@ -1324,7 +1363,7 @@ mod tests {
// Incorrect seed passed
assert!(
block_on(unlock_bip39(
- &mut crate::hal::testing::TestingRandom::new(),
+ &mut TestingHal::new(),
b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"foo",
async || {}
@@ -1332,15 +1371,15 @@ mod tests {
.is_err()
);
// Correct seed passed.
- let mut random = crate::hal::testing::TestingRandom::new();
+ let mut mock_hal = TestingHal::new();
// Mock random value used for creating the unstretched bip39 seed encryption key.
- random.mock_next(hex!(
+ mock_hal.random.mock_next(hex!(
"9b44c7048893faaf6e2d7625d13d8f1cab0765fd61f159d9713e08155d06717c"
));
- bitbox02::securechip::fake_event_counter_reset();
- assert!(block_on(unlock_bip39(&mut random, &seed, "foo", async || {})).is_ok());
- assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
+ mock_hal.securechip.event_counter_reset();
+ assert!(block_on(unlock_bip39(&mut mock_hal, &seed, "foo", async || {})).is_ok());
+ assert_eq!(mock_hal.securechip.get_event_counter(), 1);
assert_eq!(root_fingerprint(), Ok(vec![0xf1, 0xbc, 0x3c, 0x46]),);
let expected_bip39_seed = hex!(
@@ -1348,7 +1387,7 @@ mod tests {
);
assert_eq!(
- copy_bip39_seed().unwrap().as_slice(),
+ copy_bip39_seed(&mut mock_hal).unwrap().as_slice(),
expected_bip39_seed.as_slice()
);
@@ -1372,49 +1411,62 @@ mod tests {
#[test]
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(keypath).is_err());
+ assert!(secp256k1_get_private_key(&mut mock_hal, keypath).is_err());
mock_unlocked_using_mnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"",
);
- bitbox02::securechip::fake_event_counter_reset();
+ mock_hal.securechip.event_counter_reset();
assert_eq!(
- secp256k1_get_private_key(keypath).unwrap().as_slice(),
+ secp256k1_get_private_key(&mut mock_hal, keypath)
+ .unwrap()
+ .as_slice(),
hex!("4604b4b710fe91f584fff084e1a9159fe4f8408fff380596a604948474ce4fa3"),
);
- assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
+ assert_eq!(mock_hal.securechip.get_event_counter(), 1);
}
#[test]
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(keypath).is_err());
+ assert!(secp256k1_get_private_key_twice(&mut mock_hal, keypath).is_err());
mock_unlocked_using_mnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"",
);
- bitbox02::securechip::fake_event_counter_reset();
+ mock_hal.securechip.event_counter_reset();
assert_eq!(
- secp256k1_get_private_key_twice(keypath).unwrap().as_slice(),
+ secp256k1_get_private_key_twice(&mut mock_hal, keypath)
+ .unwrap()
+ .as_slice(),
hex!("4604b4b710fe91f584fff084e1a9159fe4f8408fff380596a604948474ce4fa3"),
);
- assert_eq!(bitbox02::securechip::fake_event_counter(), 2);
+ assert_eq!(mock_hal.securechip.get_event_counter(), 2);
}
#[test]
fn test_get_bip39_mnemonic() {
lock();
- assert!(get_bip39_mnemonic().is_err());
+ assert!(get_bip39_mnemonic(&mut TestingHal::new()).is_err());
mock_unlocked();
- assert_eq!(get_bip39_mnemonic().unwrap().as_str(), TEST_MNEMONIC);
+ assert_eq!(
+ get_bip39_mnemonic(&mut TestingHal::new()).unwrap().as_str(),
+ TEST_MNEMONIC
+ );
}
#[test]
@@ -1423,8 +1475,10 @@ mod tests {
// Also test with unhardened and non-zero elements.
let keypath_5 = &[44 + HARDENED, 1 + HARDENED, 10 + HARDENED, 1, 100];
+ let mut mock_hal = TestingHal::new();
+
lock();
- assert!(get_xpub_twice(keypath).is_err());
+ assert!(get_xpub_twice(&mut mock_hal, keypath).is_err());
// 24 words
mock_unlocked_using_mnemonic(
@@ -1432,27 +1486,27 @@ mod tests {
"",
);
- bitbox02::securechip::fake_event_counter_reset();
+ mock_hal.securechip.event_counter_reset();
assert_eq!(
- get_xpub_twice(&[])
+ get_xpub_twice(&mut mock_hal, &[])
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
"xpub661MyMwAqRbcEhX8d9WJh78SZrxusAzWFoykz4n5CF75uYRzixw5FZPUSoWyhaaJ1bpiPFdzdHSQqJN38PcTkyrLmxT4J2JDYfoGJQ4ioE2",
);
- assert_eq!(bitbox02::securechip::fake_event_counter(), 2);
+ assert_eq!(mock_hal.securechip.get_event_counter(), 2);
assert_eq!(
- get_xpub_twice(keypath)
+ get_xpub_twice(&mut mock_hal, keypath)
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
"xpub6Cj6NNCGj2CRPHvkuEG1rbW3nrNCAnLjaoTg1P67FCGoahSsbg9WQ7YaMEEP83QDxt2kZ3hTPAPpGdyEZcfAC1C75HfR66UbjpAb39f4PnG",
);
assert_eq!(
- get_xpub_twice(keypath_5)
+ get_xpub_twice(&mut mock_hal, keypath_5)
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -1465,7 +1519,7 @@ mod tests {
"",
);
assert_eq!(
- get_xpub_twice(keypath)
+ get_xpub_twice(&mut mock_hal, keypath)
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -1478,7 +1532,7 @@ mod tests {
"",
);
assert_eq!(
- get_xpub_twice(keypath)
+ get_xpub_twice(&mut mock_hal, keypath)
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -1489,7 +1543,8 @@ mod tests {
#[test]
fn test_get_xpubs_twice() {
lock();
- assert!(get_xpubs_twice(&[]).is_err());
+
+ assert!(get_xpubs_twice(&mut TestingHal::new(), &[]).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",
@@ -1497,30 +1552,35 @@ mod tests {
);
// Helper to convert to strings.
- let get = |keypaths| -> Vec<String> {
- get_xpubs_twice(keypaths)
+ fn get(hal: &mut impl crate::hal::Hal, keypaths: &[&[u32]]) -> Vec<String> {
+ get_xpubs_twice(hal, keypaths)
.unwrap()
.iter()
.map(|xpub| xpub.serialize_str(bip32::XPubType::Xpub).unwrap())
.collect()
- };
+ }
- bitbox02::securechip::fake_event_counter_reset();
- assert!(get_xpubs_twice(&[]).unwrap().is_empty());
- assert_eq!(bitbox02::securechip::fake_event_counter(), 0);
+ let mut mock_hal = TestingHal::new();
- bitbox02::securechip::fake_event_counter_reset();
+ mock_hal.securechip.event_counter_reset();
+ assert!(get_xpubs_twice(&mut mock_hal, &[]).unwrap().is_empty());
+ assert_eq!(mock_hal.securechip.get_event_counter(), 0);
+
+ mock_hal.securechip.event_counter_reset();
assert_eq!(
- get(&[
- &[84 + HARDENED, HARDENED, HARDENED],
- &[86 + HARDENED, HARDENED, HARDENED],
- ]),
+ get(
+ &mut mock_hal,
+ &[
+ &[84 + HARDENED, HARDENED, HARDENED],
+ &[86 + HARDENED, HARDENED, HARDENED],
+ ]
+ ),
vec![
"xpub6CNbmcHwZDudAvCAZVE5kejUoFD63mbkRbRMA2HoF9oNWsCofni87gJKp31qZJ9FsCMQR2vK9AS51mT8dgUMGsHW6SfaAKb4eSzpqJn7zwK",
"xpub6CGwpj8iQNuzSeeEKF4yuQt32fpLqfHj7sUfFH4uW34DoctWPksxAdjNYC9KwYgwA149B7SDdcLH1aFmucRcjBL4U6piN7HgaiFCBsToamH",
],
);
- assert_eq!(bitbox02::securechip::fake_event_counter(), 2);
+ assert_eq!(mock_hal.securechip.get_event_counter(), 2);
}
#[test]
@@ -1558,6 +1618,7 @@ mod tests {
hex!("00112233445566778899aabbccddeeff112233445566778899aabbccddeeff00");
let stretched = stretch_retained_seed_encryption_key(
+ &mut TestingHal::new(),
&encryption_key,
"keystore_retained_seed_access_in",
"keystore_retained_seed_access_out",
@@ -1574,15 +1635,19 @@ mod tests {
bitbox02::memory::set_salt_root(&[0xffu8; 32]).unwrap();
let encryption_key = [0u8; 32];
- let result =
- stretch_retained_seed_encryption_key(&encryption_key, "purpose_in", "purpose_out");
+ let result = stretch_retained_seed_encryption_key(
+ &mut TestingHal::new(),
+ &encryption_key,
+ "purpose_in",
+ "purpose_out",
+ );
assert!(matches!(result, Err(Error::Salt)));
}
#[test]
fn test_bip85_bip39() {
lock();
- assert!(bip85_bip39(12, 0).is_err());
+ assert!(bip85_bip39(&mut TestingHal::new(), 12, 0).is_err());
// Test fixtures generated using:
// `docker build -t bip85 .`
@@ -1599,36 +1664,38 @@ mod tests {
);
assert_eq!(
- bip85_bip39(12, 0).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 12, 0).unwrap().as_ref() as &str,
"slender whip place siren tissue chaos ankle door only assume tent shallow",
);
assert_eq!(
- bip85_bip39(12, 1).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 12, 1).unwrap().as_ref() as &str,
"income soft level reunion height pony crane use unfold win keen satisfy",
);
assert_eq!(
- bip85_bip39(12, HARDENED - 1).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 12, HARDENED - 1)
+ .unwrap()
+ .as_ref() as &str,
"carry build nerve market domain energy mistake script puzzle replace mixture idea",
);
assert_eq!(
- bip85_bip39(18, 0).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 18, 0).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(24, 0).unwrap().as_ref() as &str,
+ bip85_bip39(&mut TestingHal::new(), 24, 0).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(10, 0).is_err());
+ assert!(bip85_bip39(&mut TestingHal::new(), 10, 0).is_err());
// Index too high.
- assert!(bip85_bip39(12, HARDENED).is_err());
+ assert!(bip85_bip39(&mut TestingHal::new(), 12, HARDENED).is_err());
}
#[test]
fn test_bip85_ln() {
lock();
- assert!(bip85_ln(0).is_err());
+ assert!(bip85_ln(&mut TestingHal::new(), 0).is_err());
mock_unlocked_using_mnemonic(
"virtual weapon code laptop defy cricket vicious target wave leopard garden give",
@@ -1636,20 +1703,22 @@ mod tests {
);
assert_eq!(
- bip85_ln(0).unwrap().as_slice(),
+ bip85_ln(&mut TestingHal::new(), 0).unwrap().as_slice(),
hex!("3a5f3b888aab88e2a9ab991b60a03ed8"),
);
assert_eq!(
- bip85_ln(1).unwrap().as_slice(),
+ bip85_ln(&mut TestingHal::new(), 1).unwrap().as_slice(),
hex!("e7d9ce75f8cb17570e665417b47fa0be"),
);
assert_eq!(
- bip85_ln(HARDENED - 1).unwrap().as_slice(),
+ bip85_ln(&mut TestingHal::new(), HARDENED - 1)
+ .unwrap()
+ .as_slice(),
hex!("1f3b75ea252749700a1e453469148ca6"),
);
// Index too high.
- assert!(bip85_ln(HARDENED).is_err());
+ assert!(bip85_ln(&mut TestingHal::new(), HARDENED).is_err());
}
#[test]
@@ -1707,11 +1776,11 @@ mod tests {
lock();
let seed = &seed[..test.seed_len];
- let mut mock_hal = crate::hal::testing::TestingHal::new();
+ let mut mock_hal = TestingHal::new();
assert!(
block_on(unlock_bip39(
- &mut mock_hal.random,
+ &mut mock_hal,
seed,
test.mnemonic_passphrase,
async || {}
@@ -1728,7 +1797,7 @@ mod tests {
mock_hal.securechip.event_counter_reset();
assert!(
block_on(unlock_bip39(
- &mut mock_hal.random,
+ &mut mock_hal,
seed,
test.mnemonic_passphrase,
async || {}
@@ -1739,20 +1808,23 @@ mod tests {
assert!(!is_locked());
assert_eq!(
- get_bip39_mnemonic().unwrap().as_str(),
+ get_bip39_mnemonic(&mut mock_hal).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(keypath).unwrap();
+ let xpub = get_xpub_once(&mut mock_hal, keypath).unwrap();
assert_eq!(mock_hal.securechip.get_event_counter(), 1);
assert_eq!(
xpub.serialize_str(crate::bip32::XPubType::Xpub).unwrap(),
test.expected_xpub,
);
- assert_eq!(get_u2f_seed().unwrap().as_slice(), test.expected_u2f_seed);
+ assert_eq!(
+ get_u2f_seed(&mut mock_hal).unwrap().as_slice(),
+ test.expected_u2f_seed
+ );
}
}
@@ -1826,7 +1898,7 @@ mod tests {
let host_commitment: [u8; 32] = host_commitment_vec.try_into().unwrap();
// Get pubkey at keypath.
- let private_key = secp256k1_get_private_key(&keypath).unwrap();
+ let private_key = secp256k1_get_private_key(&mut TestingHal::new(), &keypath).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);
@@ -1881,10 +1953,10 @@ mod tests {
// Test without tweak
- bitbox02::securechip::fake_event_counter_reset();
- let mut random = crate::hal::testing::TestingRandom::new();
- let sig = secp256k1_schnorr_sign(&mut random, &keypath, &msg, None).unwrap();
- assert_eq!(bitbox02::securechip::fake_event_counter(), 1);
+ let mut mock_hal = TestingHal::new();
+ mock_hal.securechip.event_counter_reset();
+ let sig = secp256k1_schnorr_sign(&mut mock_hal, &keypath, &msg, None).unwrap();
+ assert_eq!(mock_hal.securechip.get_event_counter(), 1);
assert!(
SECP256K1
@@ -1902,8 +1974,8 @@ mod tests {
))
.unwrap();
let (tweaked_pubkey, _) = expected_pubkey.add_tweak(SECP256K1, &tweak).unwrap();
- let mut random = crate::hal::testing::TestingRandom::new();
- let sig = secp256k1_schnorr_sign(&mut random, &keypath, &msg, Some(&tweak.to_be_bytes()))
+ let mut mock_hal = TestingHal::new();
+ let sig = secp256k1_schnorr_sign(&mut mock_hal, &keypath, &msg, Some(&tweak.to_be_bytes()))
.unwrap();
assert!(
SECP256K1
@@ -1932,11 +2004,14 @@ mod tests {
assert!(encrypt_and_store_seed(&mut mock_hal, &seed[..seed_size], "foo").is_ok());
}
// Also unlocks, so we can get the retained seed.
- assert_eq!(copy_seed().unwrap().as_slice(), &seed[..seed_size]);
+ assert_eq!(
+ copy_seed(&mut mock_hal).unwrap().as_slice(),
+ &seed[..seed_size]
+ );
lock();
// Can't get seed before unlock.
- assert!(copy_seed().is_err());
+ assert!(copy_seed(&mut mock_hal).is_err());
// Wrong password.
assert!(matches!(
@@ -1952,7 +2027,10 @@ mod tests {
&seed[..seed_size]
);
}
- assert_eq!(copy_seed().unwrap().as_slice(), &seed[..seed_size]);
+ assert_eq!(
+ copy_seed(&mut mock_hal).unwrap().as_slice(),
+ &seed[..seed_size]
+ );
// Can't store new seed once initialized.
bitbox02::memory::set_initialized().unwrap();
diff --git a/src/rust/bitbox02-rust/src/keystore/ed25519.rs b/src/rust/bitbox02-rust/src/keystore/ed25519.rs
index 26eb86f..255dc7b 100644
--- a/src/rust/bitbox02-rust/src/keystore/ed25519.rs
+++ b/src/rust/bitbox02-rust/src/keystore/ed25519.rs
@@ -30,8 +30,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() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
- let bip39_seed = crate::keystore::copy_bip39_seed()?;
+fn get_seed(hal: &mut impl crate::hal::Hal) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
+ let bip39_seed = crate::keystore::copy_bip39_seed(hal)?;
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);
@@ -58,8 +58,8 @@ fn get_seed() -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
Ok(seed_out)
}
-fn get_xprv(keypath: &[u32]) -> Result<Xprv<Sha512>, ()> {
- let root = get_seed()?;
+fn get_xprv(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<Xprv<Sha512>, ()> {
+ let root = get_seed(hal)?;
Ok(Xprv::<Sha512>::from_normalize(
&root[..ED25519_EXPANDED_SECRET_KEY_SIZE],
&root[ED25519_EXPANDED_SECRET_KEY_SIZE..],
@@ -67,8 +67,8 @@ fn get_xprv(keypath: &[u32]) -> Result<Xprv<Sha512>, ()> {
.derive_path(keypath))
}
-pub fn get_xpub(keypath: &[u32]) -> Result<Xpub<Sha512>, ()> {
- Ok(get_xprv(keypath)?.public())
+pub fn get_xpub(hal: &mut impl crate::hal::Hal, keypath: &[u32]) -> Result<Xpub<Sha512>, ()> {
+ Ok(get_xprv(hal, keypath)?.public())
}
pub struct SignResult {
@@ -76,8 +76,12 @@ pub struct SignResult {
pub public_key: ed25519_dalek::VerifyingKey,
}
-pub fn sign(keypath: &[u32], msg: &[u8; 32]) -> Result<SignResult, ()> {
- let xprv = get_xprv(keypath)?;
+pub fn sign(
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+ msg: &[u8; 32],
+) -> Result<SignResult, ()> {
+ let xprv = get_xprv(hal, keypath)?;
let secret_key =
ed25519_dalek::hazmat::ExpandedSecretKey::from_bytes(&xprv.expanded_secret_key());
let public_key = ed25519_dalek::VerifyingKey::from(&secret_key);
@@ -119,12 +123,14 @@ mod tests {
// https://github.com/cardano-foundation/CIPs/blob/6c249ef48f8f5b32efc0ec768fadf4321f3173f2/CIP-0003/Ledger.md#test-vectors
// See also: https://github.com/cardano-foundation/CIPs/pull/132
+ let mut mock_hal = crate::hal::testing::TestingHal::new();
+
mock_unlocked_using_mnemonic(
"recall grace sport punch exhibit mad harbor stand obey short width stem awkward used stairs wool ugly trap season stove worth toward congress jaguar",
"",
);
assert_eq!(
- get_seed().unwrap().as_slice(),
+ get_seed(&mut mock_hal).unwrap().as_slice(),
b"\xa0\x8c\xf8\x5b\x56\x4e\xcf\x3b\x94\x7d\x8d\x43\x21\xfb\x96\xd7\x0e\xe7\xbb\x76\x08\x77\xe3\x71\x89\x9b\x14\xe2\xcc\xf8\x86\x58\x10\x4b\x88\x46\x82\xb5\x7e\xfd\x97\xde\xcb\xb3\x18\xa4\x5c\x05\xa5\x27\xb9\xcc\x5c\x2f\x64\xf7\x35\x29\x35\xa0\x49\xce\xea\x60\x68\x0d\x52\x30\x81\x94\xcc\xef\x2a\x18\xe6\x81\x2b\x45\x2a\x58\x15\xfb\xd7\xf5\xba\xbc\x08\x38\x56\x91\x9a\xaf\x66\x8f\xe7\xe4",
);
@@ -134,7 +140,7 @@ mod tests {
"",
);
assert_eq!(
- get_seed().unwrap().as_slice(),
+ get_seed(&mut mock_hal).unwrap().as_slice(),
b"\x58\x7c\x67\x74\x35\x7e\xcb\xf8\x40\xd4\xdb\x64\x04\xff\x7a\xf0\x16\xda\xce\x04\x00\x76\x97\x51\xad\x2a\xbf\xc7\x7b\x9a\x38\x44\xcc\x71\x70\x25\x20\xef\x1a\x4d\x1b\x68\xb9\x11\x87\x78\x7a\x9b\x8f\xaa\xb0\xa9\xbb\x6b\x16\x0d\xe5\x41\xb6\xee\x62\x46\x99\x01\xfc\x0b\xed\xa0\x97\x5f\xe4\x76\x3b\xea\xbd\x83\xb7\x05\x1a\x5f\xd5\xcb\xce\x5b\x88\xe8\x2c\x4b\xba\xca\x26\x50\x14\xe5\x24\xbd",
);
@@ -143,7 +149,7 @@ mod tests {
"foo",
);
assert_eq!(
- get_seed().unwrap().as_slice(),
+ get_seed(&mut mock_hal).unwrap().as_slice(),
b"\xf0\x53\xa1\xe7\x52\xde\x5c\x26\x19\x7b\x60\xf0\x32\xa4\x80\x9f\x08\xbb\x3e\x5d\x90\x48\x4f\xe4\x20\x24\xbe\x31\xef\xcb\xa7\x57\x8d\x91\x4d\x3f\xf9\x92\xe2\x16\x52\xfe\xe6\xa4\xd9\x9f\x60\x91\x00\x69\x38\xfa\xc2\xc0\xc0\xf9\xd2\xde\x0b\xa6\x4b\x75\x4e\x92\xa4\xf3\x72\x3f\x23\x47\x20\x77\xaa\x4c\xd4\xdd\x8a\x8a\x17\x5d\xba\x07\xea\x18\x52\xda\xd1\xcf\x26\x8c\x61\xa2\x67\x9c\x38\x90",
);
}
@@ -151,15 +157,18 @@ mod tests {
#[test]
fn test_get_xpub() {
crate::keystore::lock();
- assert!(get_xpub(&[]).is_err());
+
+ let mut mock_hal = crate::hal::testing::TestingHal::new();
+
+ assert!(get_xpub(&mut mock_hal, &[]).is_err());
mock_unlocked();
- let xpub = get_xpub(&[]).unwrap();
+ let xpub = get_xpub(&mut mock_hal, &[]).unwrap();
assert_eq!(xpub.pubkey_bytes(), b"\x1c\xc2\xc8\x0d\x6f\xb0\x3e\xc0\x9e\x8a\x26\x8b\xaa\x45\xd4\xca\x2a\xfe\x5c\x5a\xc4\xdb\x3e\xe2\x9c\x7a\xd2\x37\x55\xab\xdc\x14");
assert_eq!(xpub.chain_code(), b"\xf0\xa5\x91\x06\x42\xd0\x77\x98\x17\x40\x2e\x5e\x7a\x75\x54\x95\xe7\x44\xf5\x5c\xf1\x1e\x49\xee\xfd\x22\xa4\x60\xe9\xb2\xf7\x53");
- let xpub = get_xpub(&[10 + HARDENED_OFFSET, 10]).unwrap();
+ let xpub = get_xpub(&mut mock_hal, &[10 + HARDENED_OFFSET, 10]).unwrap();
assert_eq!(xpub.pubkey_bytes(), b"\xab\x58\xbd\x94\x7e\x2b\xf6\x64\xa7\xc0\x66\xde\x2e\xf0\x24\x0e\xfc\x24\xf3\x6e\xfd\x50\x2d\xf8\x83\x93\xe1\x96\xaf\x3c\x91\x8e");
assert_eq!(xpub.chain_code(), b"\xf2\x00\x13\x38\x58\x02\xa6\xf9\xc0\x5e\xe7\xb0\x36\x16\xad\xf6\x9f\x5f\x9e\xc4\x32\x53\xa5\xd0\x8b\xe9\x65\x79\x81\x90\x83\xbb");
}
@@ -167,13 +176,16 @@ mod tests {
#[test]
fn test_get_xprv() {
crate::keystore::lock();
- assert!(get_xprv(&[]).is_err());
+
+ let mut mock_hal = crate::hal::testing::TestingHal::new();
+
+ assert!(get_xprv(&mut mock_hal, &[]).is_err());
mock_unlocked();
- let xprv = get_xprv(&[]).unwrap();
+ let xprv = get_xprv(&mut mock_hal, &[]).unwrap();
assert_eq!(xprv.expanded_secret_key().as_slice(), b"\xf8\xcb\x28\x85\x37\x60\x2b\x90\xd1\x29\x75\x4b\xdd\x0e\x4b\xed\xf9\xe2\x92\x3a\x04\xb6\x86\x7e\xdb\xeb\xc7\x93\xa7\x17\x6f\x5d\xca\xc5\xc9\x5d\x5f\xd2\x3a\x8e\x01\x6c\x95\x57\x69\x0e\xad\x1f\x00\x2b\x0f\x35\xd7\x06\xff\x8e\x59\x84\x1c\x09\xe0\xb6\xbb\x23");
- let xprv = get_xprv(&[10 + HARDENED_OFFSET, 10]).unwrap();
+ let xprv = get_xprv(&mut mock_hal, &[10 + HARDENED_OFFSET, 10]).unwrap();
assert_eq!(xprv.expanded_secret_key().as_slice(), b"\x00\x28\x46\xb1\xeb\x06\x66\xff\x4e\xf1\x66\xde\x37\x80\xdf\xe1\x95\xed\x6f\xfd\xce\x41\x18\x09\x9d\x9d\x80\x85\xaa\x17\x6f\x5d\x1f\xcf\xf9\x55\x2e\xe4\xc0\xcb\x03\xaa\x42\x1a\xe8\x2f\x98\xa0\x0a\xfc\x65\xb6\x84\x66\x31\xaa\x41\x8e\x6d\x5a\x62\x6e\x75\xf4");
}
@@ -181,10 +193,22 @@ mod tests {
fn test_sign() {
let msg = &[0u8; 32];
crate::keystore::lock();
- assert!(sign(&[10 + HARDENED_OFFSET, 10], msg).is_err());
+ assert!(
+ sign(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[10 + HARDENED_OFFSET, 10],
+ msg
+ )
+ .is_err()
+ );
mock_unlocked();
- let sig = sign(&[10 + HARDENED_OFFSET, 10], msg).unwrap();
+ let sig = sign(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[10 + HARDENED_OFFSET, 10],
+ msg,
+ )
+ .unwrap();
assert_eq!(sig.public_key.as_ref(), b"\xab\x58\xbd\x94\x7e\x2b\xf6\x64\xa7\xc0\x66\xde\x2e\xf0\x24\x0e\xfc\x24\xf3\x6e\xfd\x50\x2d\xf8\x83\x93\xe1\x96\xaf\x3c\x91\x8e");
assert_eq!(
sig.signature,
diff --git a/src/rust/bitbox02-rust/src/workflow/unlock.rs b/src/rust/bitbox02-rust/src/workflow/unlock.rs
index 934799a..c654793 100644
--- a/src/rust/bitbox02-rust/src/workflow/unlock.rs
+++ b/src/rust/bitbox02-rust/src/workflow/unlock.rs
@@ -137,7 +137,7 @@ pub async fn unlock_bip39(hal: &mut impl crate::hal::Hal, seed: &[u8]) {
let ((), result) = futures_lite::future::zip(
super::unlock_animation::animate(),
crate::keystore::unlock_bip39(
- hal.random(),
+ hal,
seed,
&mnemonic_passphrase,
// for the simulator, we don't yield at all, otherwise unlock becomes very slow in the
@@ -226,7 +226,9 @@ mod tests {
assert!(!crate::keystore::is_locked());
assert_eq!(
- crate::keystore::copy_bip39_seed().unwrap().as_slice(),
+ crate::keystore::copy_bip39_seed(&mut mock_hal)
+ .unwrap()
+ .as_slice(),
&hex!(
"cff4b263e5b0eb299e5fd35fcd09988f6b14e5b464f8d18fb84b152f889dd2a30550f4c2b346cae825ffedd4a87fc63fc12a9433de5125b6c7fdbc5eab0c590b"
),
@@ -273,7 +275,7 @@ mod tests {
assert_eq!(mock_hal.securechip.get_event_counter(), 5);
// Checks that the device is locked.
- assert!(crate::keystore::copy_seed().is_err());
+ assert!(crate::keystore::copy_seed(&mut mock_hal).is_err());
assert_eq!(
mock_hal.ui.screens,
diff --git a/src/rust/bitbox02-rust/src/xpubcache.rs b/src/rust/bitbox02-rust/src/xpubcache.rs
index 03e3018..9659364 100644
--- a/src/rust/bitbox02-rust/src/xpubcache.rs
+++ b/src/rust/bitbox02-rust/src/xpubcache.rs
@@ -32,7 +32,11 @@ pub trait Xpub: Sized {
fn derive(&self, keypath: &[u32], compute: Compute) -> Result<Self, ()>;
/// Derives an xpub from the root xpub using the provided keypath.
- fn from_keypath(keypath: &[u32], compute: Compute) -> Result<Self, ()>;
+ fn from_keypath(
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+ compute: Compute,
+ ) -> Result<Self, ()>;
}
/// Implements a cache for xpubs. Cached intermediate xpubs are used to derive child xpubs.
@@ -66,7 +70,7 @@ 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, keypath: &[u32]) -> Result<X, ()> {
+ 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
@@ -83,9 +87,9 @@ 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(prefix)?.derive(&[*last], self.compute)?
+ self.get_xpub(hal, prefix)?.derive(&[*last], self.compute)?
} else {
- X::from_keypath(keypath, self.compute)?
+ X::from_keypath(hal, keypath, self.compute)?
};
self.xpubs.push((keypath.to_vec(), xpub.clone()));
Ok(xpub)
@@ -94,7 +98,7 @@ 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, keypath: &[u32]) -> Result<X, ()> {
+ pub 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
@@ -106,10 +110,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(&cached_prefix.clone())?;
+ let xpub = self.cache_get_set(hal, &cached_prefix.clone())?;
return xpub.derive(suffix, self.compute);
}
- X::from_keypath(keypath, self.compute)
+ X::from_keypath(hal, keypath, self.compute)
}
}
@@ -128,10 +132,14 @@ impl Xpub for bip32::Xpub {
}
}
- fn from_keypath(keypath: &[u32], compute: Compute) -> Result<Self, ()> {
+ fn from_keypath(
+ hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+ compute: Compute,
+ ) -> Result<Self, ()> {
match compute {
- Compute::Once => keystore::get_xpub_once(keypath),
- Compute::Twice => keystore::get_xpub_twice(keypath),
+ Compute::Once => keystore::get_xpub_once(hal, keypath),
+ Compute::Twice => keystore::get_xpub_twice(hal, keypath),
}
}
}
@@ -166,7 +174,11 @@ mod tests {
Ok(MockXpub(kp))
}
- fn from_keypath(keypath: &[u32], _compute: Compute) -> Result<Self, ()> {
+ fn from_keypath(
+ _hal: &mut impl crate::hal::Hal,
+ keypath: &[u32],
+ _compute: Compute,
+ ) -> Result<Self, ()> {
*ROOT_DERIVATIONS.borrow_mut() += 1;
Ok(MockXpub(keypath.to_vec()))
}
@@ -176,12 +188,26 @@ mod tests {
let mut cache = MockCache::new(crate::xpubcache::Compute::Once);
- assert_eq!(cache.get_xpub(&[]).unwrap().0.as_slice(), &[]);
+ assert_eq!(
+ cache
+ .get_xpub(&mut crate::hal::testing::TestingHal::new(), &[])
+ .unwrap()
+ .0
+ .as_slice(),
+ &[]
+ );
assert_eq!(*CHILD_DERIVATIONS.borrow(), 0u32);
assert_eq!(*ROOT_DERIVATIONS.borrow(), 1u32);
*ROOT_DERIVATIONS.borrow_mut() = 0;
- assert_eq!(cache.get_xpub(&[1, 2, 3]).unwrap().0.as_slice(), &[1, 2, 3]);
+ assert_eq!(
+ cache
+ .get_xpub(&mut crate::hal::testing::TestingHal::new(), &[1, 2, 3])
+ .unwrap()
+ .0
+ .as_slice(),
+ &[1, 2, 3]
+ );
assert_eq!(*CHILD_DERIVATIONS.borrow(), 0u32);
assert_eq!(*ROOT_DERIVATIONS.borrow(), 1u32);
*ROOT_DERIVATIONS.borrow_mut() = 0;
@@ -192,7 +218,10 @@ mod tests {
assert_eq!(
cache
- .get_xpub(&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2])
+ .get_xpub(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2]
+ )
.unwrap()
.0
.as_slice(),
@@ -209,7 +238,10 @@ mod tests {
// Same keypath again is a cache hit at m/84'/0'/0'/1 with one child derivation.
assert_eq!(
cache
- .get_xpub(&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2])
+ .get_xpub(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2]
+ )
.unwrap()
.0
.as_slice(),
@@ -223,7 +255,10 @@ mod tests {
// call using m/84'/0'/0'/1/2.
assert_eq!(
cache
- .get_xpub(&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0])
+ .get_xpub(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0]
+ )
.unwrap()
.0
.as_slice(),
@@ -242,7 +277,10 @@ mod tests {
mock_unlocked();
assert_eq!(
&cache
- .get_xpub(&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2])
+ .get_xpub(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 2]
+ )
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -254,7 +292,10 @@ mod tests {
assert_eq!(
&cache
- .get_xpub(&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0])
+ .get_xpub(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0]
+ )
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
@@ -263,7 +304,10 @@ mod tests {
assert_eq!(
&cache
- .get_xpub(&[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 3])
+ .get_xpub(
+ &mut crate::hal::testing::TestingHal::new(),
+ &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 3]
+ )
.unwrap()
.serialize_str(bip32::XPubType::Xpub)
.unwrap(),
Why this scored 32/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.