use bitcoin::bip32::DerivationPath;
use super::error::BitBoxError;
use super::proto as pb;
pub fn make_script_config_simple(
simple_type: pb::btc_script_config::SimpleType,
) -> pb::BtcScriptConfig {
pb::BtcScriptConfig {
config: Some(pb::btc_script_config::Config::SimpleType(
simple_type.into(),
)),
}
}
pub fn coin_from_network(network: bitcoin::Network) -> pb::BtcCoin {
if network == bitcoin::Network::Bitcoin {
pb::BtcCoin::Btc
} else {
pb::BtcCoin::Tbtc
}
}
pub fn xpub_type_from_network(network: bitcoin::Network) -> pb::btc_pub_request::XPubType {
if network == bitcoin::Network::Bitcoin {
pb::btc_pub_request::XPubType::Xpub
} else {
pb::btc_pub_request::XPubType::Tpub
}
}
pub fn xpub_request(
coin: pb::BtcCoin,
keypath: &DerivationPath,
xpub_type: pb::btc_pub_request::XPubType,
display: bool,
) -> pb::request::Request {
pb::request::Request::BtcPub(pb::BtcPubRequest {
coin: coin as _,
keypath: keypath.to_u32_vec(),
display,
output: Some(pb::btc_pub_request::Output::XpubType(xpub_type as _)),
})
}
pub fn address_request(
coin: pb::BtcCoin,
keypath: &DerivationPath,
script_config: pb::BtcScriptConfig,
display: bool,
) -> pb::request::Request {
pb::request::Request::BtcPub(pb::BtcPubRequest {
coin: coin as _,
keypath: keypath.to_u32_vec(),
display,
output: Some(pb::btc_pub_request::Output::ScriptConfig(script_config)),
})
}
pub fn root_fingerprint_request() -> pb::request::Request {
pb::request::Request::Fingerprint(pb::RootFingerprintRequest {})
}
pub fn device_info_request() -> pb::request::Request {
pb::request::Request::DeviceInfo(pb::DeviceInfoRequest {})
}
pub fn show_mnemonic_request() -> pb::request::Request {
pb::request::Request::ShowMnemonic(pb::ShowMnemonicRequest {})
}
pub fn set_device_name_request(name: impl Into<String>) -> pb::request::Request {
pb::request::Request::DeviceName(pb::SetDeviceNameRequest { name: name.into() })
}
pub fn set_password_request(entropy: &[u8; 32]) -> pb::request::Request {
pb::request::Request::SetPassword(pb::SetPasswordRequest {
entropy: entropy.to_vec(),
})
}
pub fn create_backup_request(timestamp: u32, timezone_offset: i32) -> pb::request::Request {
pb::request::Request::CreateBackup(pb::CreateBackupRequest {
timestamp,
timezone_offset,
})
}
pub fn restore_from_mnemonic_request(timestamp: u32, timezone_offset: i32) -> pb::request::Request {
pb::request::Request::RestoreFromMnemonic(pb::RestoreFromMnemonicRequest {
timestamp,
timezone_offset,
})
}
pub fn reset_request() -> pb::request::Request {
pb::request::Request::Reset(pb::ResetRequest {})
}
pub fn set_mnemonic_passphrase_enabled_request(enabled: bool) -> pb::request::Request {
pb::request::Request::SetMnemonicPassphraseEnabled(pb::SetMnemonicPassphraseEnabledRequest {
enabled,
})
}
pub fn is_script_config_registered_request(
coin: pb::BtcCoin,
script_config: pb::BtcScriptConfig,
keypath_account: Option<&DerivationPath>,
) -> pb::request::Request {
pb::request::Request::Btc(pb::BtcRequest {
request: Some(pb::btc_request::Request::IsScriptConfigRegistered(
pb::BtcIsScriptConfigRegisteredRequest {
registration: Some(pb::BtcScriptConfigRegistration {
coin: coin as _,
script_config: Some(script_config),
keypath: keypath_account.map_or(vec![], |kp| kp.to_u32_vec()),
}),
},
)),
})
}
pub fn register_script_config_request(
coin: pb::BtcCoin,
script_config: pb::BtcScriptConfig,
keypath_account: Option<&DerivationPath>,
xpub_type: pb::btc_register_script_config_request::XPubType,
name: Option<&str>,
) -> pb::request::Request {
pb::request::Request::Btc(pb::BtcRequest {
request: Some(pb::btc_request::Request::RegisterScriptConfig(
pb::BtcRegisterScriptConfigRequest {
registration: Some(pb::BtcScriptConfigRegistration {
coin: coin as _,
script_config: Some(script_config),
keypath: keypath_account.map_or(vec![], |kp| kp.to_u32_vec()),
}),
name: name.unwrap_or("").into(),
xpub_type: xpub_type as _,
},
)),
})
}
pub fn decode_response(bytes: &[u8]) -> Result<pb::response::Response, BitBoxError> {
use prost::Message;
let response = pb::Response::decode(bytes)?;
match response.response {
Some(pb::response::Response::Error(pb::Error { code, message })) => {
Err(BitBoxError::from_reply(code, message))
}
Some(r) => Ok(r),
None => Err(BitBoxError::UnexpectedResponse),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn show_mnemonic_request_uses_backup_flow() {
assert!(matches!(
show_mnemonic_request(),
pb::request::Request::ShowMnemonic(pb::ShowMnemonicRequest {})
));
}
#[test]
fn setup_requests_preserve_external_inputs() {
assert!(matches!(
set_device_name_request("HWI Test"),
pb::request::Request::DeviceName(pb::SetDeviceNameRequest { name })
if name == "HWI Test"
));
let entropy = [42; 32];
assert!(matches!(
set_password_request(&entropy),
pb::request::Request::SetPassword(pb::SetPasswordRequest { entropy: encoded })
if encoded == entropy
));
assert!(matches!(
create_backup_request(1_601_450_521, 3_600),
pb::request::Request::CreateBackup(pb::CreateBackupRequest {
timestamp: 1_601_450_521,
timezone_offset: 3_600,
})
));
assert!(matches!(
restore_from_mnemonic_request(1_601_450_521, -3_600),
pb::request::Request::RestoreFromMnemonic(pb::RestoreFromMnemonicRequest {
timestamp: 1_601_450_521,
timezone_offset: -3_600,
})
));
}
#[test]
fn wipe_request_uses_reset() {
assert!(matches!(
reset_request(),
pb::request::Request::Reset(pb::ResetRequest {})
));
}
#[test]
fn toggle_passphrase_request_preserves_enabled_state() {
assert!(matches!(
set_mnemonic_passphrase_enabled_request(true),
pb::request::Request::SetMnemonicPassphraseEnabled(
pb::SetMnemonicPassphraseEnabledRequest { enabled: true }
)
));
}
}