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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/rust/bitbox02-rust/src/hal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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> {
Expand Down
4 changes: 2 additions & 2 deletions src/rust/bitbox02-rust/src/hww.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -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());
}
}
}
2 changes: 1 addition & 1 deletion src/rust/bitbox02-rust/src/hww/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/rust/bitbox02-rust/src/hww/api/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/rust/bitbox02-rust/src/hww/api/bip85.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;

Expand Down Expand Up @@ -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())
}
16 changes: 9 additions & 7 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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],
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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)?;

Expand Down Expand Up @@ -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(_)
Expand Down Expand Up @@ -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(),
),
Expand All @@ -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(),
),
Expand Down
13 changes: 9 additions & 4 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,20 @@ pub struct Payload {

impl Payload {
pub fn from_simple(
hal: &mut impl crate::hal::Hal,
xpub_cache: &mut Bip32XpubCache,
params: &Params,
simple_type: SimpleType,
keypath: &[u32],
) -> 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)
Expand All @@ -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,
Expand Down Expand Up @@ -190,14 +191,15 @@ 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],
script_config_account: &ValidatedScriptConfigWithKeypath,
) -> 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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
32 changes: 19 additions & 13 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -633,53 +639,53 @@ 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());
}

{
// number of cosigners too small

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());
}

{
// our xpub index larger than number of cosigners (xpubs[our_xpb_index] would be out of
// 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());
}

{
// our xpub is not part of the multisig (overwrite our xpub with an arbitrary other one).

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());
}

{
// duplicate

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());
}
}

Expand Down
Loading