diff --git a/bhwi-async/src/lib.rs b/bhwi-async/src/lib.rs index 646983e..f7224e1 100644 --- a/bhwi-async/src/lib.rs +++ b/bhwi-async/src/lib.rs @@ -6,7 +6,7 @@ pub mod transport; use std::{error::Error as StdError, fmt::Debug}; use async_trait::async_trait; -pub use bhwi::common::Version; +pub use bhwi::common::Info; use bhwi::{ Interpreter, bitcoin::{ @@ -35,7 +35,7 @@ pub trait HttpClient { pub trait HWI { type Error: Debug; async fn unlock(&mut self, network: Network) -> Result<(), Self::Error>; - async fn get_version(&mut self) -> Result; + async fn get_info(&mut self) -> Result; async fn get_master_fingerprint(&mut self) -> Result; async fn get_extended_pubkey( &mut self, @@ -55,7 +55,7 @@ pub trait HWI { #[async_trait(?Send)] pub trait HWIDevice { async fn unlock(&mut self, network: Network) -> Result<(), HWIDeviceError>; - async fn get_version(&mut self) -> Result; + async fn get_info(&mut self) -> Result; async fn get_master_fingerprint(&mut self) -> Result; async fn get_extended_pubkey( &mut self, @@ -112,8 +112,8 @@ where Ok(()) } - async fn get_version(&mut self) -> Result { - if let common::Response::Version(version) = + async fn get_info(&mut self) -> Result { + if let common::Response::Info(version) = run_command(self, common::Command::GetVersion).await? { Ok(version) @@ -179,8 +179,8 @@ where .map_err(HWIDeviceError::new) } - async fn get_version(&mut self) -> Result { - HWI::get_version(self).await.map_err(HWIDeviceError::new) + async fn get_info(&mut self) -> Result { + HWI::get_info(self).await.map_err(HWIDeviceError::new) } async fn get_master_fingerprint(&mut self) -> Result { diff --git a/bhwi-cli/Cargo.toml b/bhwi-cli/Cargo.toml index 7f61542..6d20fc9 100644 --- a/bhwi-cli/Cargo.toml +++ b/bhwi-cli/Cargo.toml @@ -15,7 +15,7 @@ path = "src/bin/bhwi.rs" [dependencies] anyhow.workspace = true async-trait.workspace = true -bitcoin.workspace = true +bitcoin = { workspace = true, features = ["serde"] } bhwi-async = { workspace = true, features = ["emulators"] } futures.workspace = true hex = { workspace = true, features = ["serde"] } diff --git a/bhwi-cli/src/bin/bhwi.rs b/bhwi-cli/src/bin/bhwi.rs index 71fba84..f0fbb09 100644 --- a/bhwi-cli/src/bin/bhwi.rs +++ b/bhwi-cli/src/bin/bhwi.rs @@ -94,7 +94,7 @@ async fn main() -> Result<()> { let fingerprint = device.fingerprint().await?; let name = device.name().to_string(); let is_emulated = device.is_emulated(); - let version = device.version().await?; + let info = device.info().await?; match format { Some(OutputFormat::Pretty) => { if i == 0 { @@ -104,10 +104,10 @@ async fn main() -> Result<()> { ); } println!("{}", "-".repeat(80)); - let network = version.network.unwrap_or_default(); + let network = info.networks_string(); println!( "{name:<18} | {is_emulated:<8} | {fingerprint:<15} | {network:<12} | {:<8}", - version.version + info.version ); println!("{}", "-".repeat(80)); } diff --git a/bhwi-cli/src/lib.rs b/bhwi-cli/src/lib.rs index c2d62a3..761bee0 100644 --- a/bhwi-cli/src/lib.rs +++ b/bhwi-cli/src/lib.rs @@ -1,8 +1,7 @@ use anyhow::Result; use async_trait::async_trait; use bhwi_async::HWIDevice; -use bhwi_async::Version; -use bitcoin::bip32::Fingerprint; +use bitcoin::{Network, bip32::Fingerprint}; use clap::ValueEnum; use futures::future::join_all; use serde::{Serialize, Serializer}; @@ -26,7 +25,35 @@ pub struct Device { #[serde(default, serialize_with = "option_fingerprint")] fingerprint: Option, #[serde(flatten, skip_serializing_if = "Option::is_none")] - version: Option, + info: Option, +} + +/// Serializable Device Information +#[derive(Debug, Clone, Default, Serialize)] +pub struct Info { + pub version: String, + pub networks: Vec, + pub firmware: Option, +} + +impl Info { + pub fn networks_string(&self) -> String { + self.networks + .iter() + .map(|n| n.to_string()) + .collect::>() + .join(", ") + } +} + +impl From for Info { + fn from(info: bhwi_async::Info) -> Self { + Self { + version: info.version, + networks: info.networks, + firmware: info.firmware, + } + } } impl Device { @@ -36,7 +63,7 @@ impl Device { device, is_emulated, fingerprint: None, - version: None, + info: None, }) } @@ -62,13 +89,13 @@ impl Device { } } - pub async fn version(&mut self) -> Result { - if let Some(ref version) = self.version { - Ok(version.clone()) + pub async fn info(&mut self) -> Result { + if let Some(ref info) = self.info { + Ok(info.clone()) } else { - let version = self.device.get_version().await?; - self.version = Some(version.clone()); - Ok(version) + let info: Info = self.device.get_info().await?.into(); + self.info = Some(info.clone()); + Ok(info) } } } @@ -100,17 +127,31 @@ impl DeviceManager { } pub async fn get_device_with_fingerprint(&self) -> Result> { + let mut target_dev = None; for mut d in self.enumerate().await? { d.device.unlock(self.config.network).await?; if let Some(fingerprint) = self.config.fingerprint { if fingerprint == d.fingerprint().await? { - return Ok(Some(d)); + target_dev = Some(d); } } else { - return Ok(Some(d)); + target_dev = Some(d); } } - Ok(None) + let Some(mut dev) = target_dev else { + return Ok(None); + }; + let info = dev.info().await?; + let networks = &info.networks; + let net = self.config.network; + if !networks.is_empty() && !networks.contains(&net) { + eprintln!( + "Warning: device {} is on {}, expected {net}", + dev.name, + info.networks_string() + ); + } + Ok(Some(dev)) } pub async fn enumerate(&self) -> Result> { diff --git a/bhwi/src/coldcard/mod.rs b/bhwi/src/coldcard/mod.rs index 5a24391..aef756b 100644 --- a/bhwi/src/coldcard/mod.rs +++ b/bhwi/src/coldcard/mod.rs @@ -8,7 +8,7 @@ use bitcoin::secp256k1::ecdsa::Signature; use crate::Interpreter; use crate::coldcard::api::response::ResponseMessage; -use crate::common::{Command, Error, Recipient, Response, Transmit, Version}; +use crate::common::{Command, Error, Info, Recipient, Response, Transmit}; use crate::device::DeviceId; pub const DEFAULT_CKCC_SOCKET: &str = "/tmp/ckcc-simulator.sock"; @@ -219,9 +219,9 @@ impl From for Response { ColdcardResponse::Version { version, device_model, - } => Response::Version(Version { + } => Response::Info(Info { version: version.as_str().into(), - network: None, + networks: vec![], firmware: Some(device_model), }), ColdcardResponse::MyPub { encryption_key, .. } => { diff --git a/bhwi/src/common.rs b/bhwi/src/common.rs index e3e0270..21ae46b 100644 --- a/bhwi/src/common.rs +++ b/bhwi/src/common.rs @@ -28,20 +28,18 @@ pub enum Command { pub enum Response { TaskDone, TaskBusy, - Version(Version), + Info(Info), MasterFingerprint(Fingerprint), Xpub(Xpub), EncryptionKey([u8; 64]), Signature(u8, Signature), } -/// Version information returned from a device. -#[derive(Debug, Clone, Default, serde::Serialize)] -pub struct Version { +/// Device Information +#[derive(Debug, Clone, Default)] +pub struct Info { pub version: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub network: Option, - #[serde(skip_serializing_if = "Option::is_none")] + pub networks: Vec, pub firmware: Option, } diff --git a/bhwi/src/jade/api.rs b/bhwi/src/jade/api.rs index 379a425..faf23a3 100644 --- a/bhwi/src/jade/api.rs +++ b/bhwi/src/jade/api.rs @@ -1,4 +1,5 @@ // See https://github.com/Blockstream/Jade/blob/master/docs/index.rst +use bitcoin::Network; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt::Display}; @@ -133,6 +134,23 @@ pub enum JadeNetworks { All, } +impl From for Vec { + fn from(networks: JadeNetworks) -> Self { + let main = [Network::Bitcoin]; + let testnets = [Network::Testnet, Network::Testnet4]; + match networks { + JadeNetworks::Main => main.to_vec(), + JadeNetworks::Test => testnets.to_vec(), + JadeNetworks::All => main + .iter() + .chain(testnets.iter()) + .chain([Network::Signet, Network::Regtest].iter()) + .copied() + .collect(), + } + } +} + impl Display for JadeNetworks { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( diff --git a/bhwi/src/jade/mod.rs b/bhwi/src/jade/mod.rs index 6a8c869..7c26cf9 100644 --- a/bhwi/src/jade/mod.rs +++ b/bhwi/src/jade/mod.rs @@ -11,7 +11,7 @@ use serde::Serialize; use serde::de::DeserializeOwned; use crate::Interpreter; -use crate::common::{Command, Error, Recipient, Response, Transmit, Version}; +use crate::common::{Command, Error, Info, Recipient, Response, Transmit}; use crate::device::DeviceId; use crate::jade::api::GetInfoResponse; @@ -287,9 +287,9 @@ impl From for Response { JadeResponse::MasterFingerprint(fg) => Response::MasterFingerprint(fg), JadeResponse::Xpub(xpub) => Response::Xpub(xpub), JadeResponse::Signature(header, signature) => Response::Signature(header, signature), - JadeResponse::GetInfo(info) => Response::Version(Version { + JadeResponse::GetInfo(info) => Response::Info(Info { version: info.jade_version.as_str().into(), - network: Some(info.jade_networks.to_string()), + networks: info.jade_networks.into(), firmware: None, }), } diff --git a/bhwi/src/ledger/mod.rs b/bhwi/src/ledger/mod.rs index e7548d7..4ce1714 100644 --- a/bhwi/src/ledger/mod.rs +++ b/bhwi/src/ledger/mod.rs @@ -18,7 +18,7 @@ use store::{DelegatedStore, StoreError}; pub use wallet::{WalletPolicy, WalletPubKey}; use crate::Interpreter; -use crate::common::{Command, Error, Response, Version}; +use crate::common::{Command, Error, Info, Response}; use crate::device::DeviceId; pub const LEDGER_DEVICE_ID: DeviceId = DeviceId::new(0x2c97) @@ -310,9 +310,9 @@ impl TryFrom for LedgerCommand { impl From for Response { fn from(res: LedgerResponse) -> Response { match res { - LedgerResponse::AppInfo(res) => Response::Version(Version { + LedgerResponse::AppInfo(res) => Response::Info(Info { version: res.version.to_string(), - network: Some(res.network().to_string()), + networks: vec![res.network()], firmware: None, }), LedgerResponse::Signature(header, signature) => Response::Signature(header, signature), diff --git a/e2e/coldcard/src/lib.rs b/e2e/coldcard/src/lib.rs index 2dd0a5a..f908508 100644 --- a/e2e/coldcard/src/lib.rs +++ b/e2e/coldcard/src/lib.rs @@ -87,10 +87,10 @@ mod tests { } #[tokio::test] - async fn can_get_version() { + async fn can_get_info() { let (mut dev, _) = device().await; - let version = dev.get_version().await.unwrap(); - assert_eq!(version.firmware, Some("mk4".to_string())); - assert_eq!(version.version.to_string(), "5.x.x"); + let info = dev.get_info().await.unwrap(); + assert_eq!(info.firmware, Some("mk4".to_string())); + assert_eq!(info.version.to_string(), "5.x.x"); } } diff --git a/e2e/jade/src/lib.rs b/e2e/jade/src/lib.rs index eccd6bf..e946e78 100644 --- a/e2e/jade/src/lib.rs +++ b/e2e/jade/src/lib.rs @@ -55,11 +55,21 @@ mod tests { } #[tokio::test] - async fn can_get_version() { + async fn can_get_info() { let mut dev = device().await; - let version = dev.get_version().await.unwrap(); + let info = dev.get_info().await.unwrap(); // 1.0.39-beta2-11-g1ca0a0a4-dirty - assert!(version.version.to_string().contains("1.0.39")); - assert_eq!(version.network.unwrap(), "all"); + assert!(info.version.to_string().contains("1.0.39")); + // jade has "all" networks + assert_eq!( + info.networks, + vec![ + Network::Bitcoin, + Network::Testnet, + Network::Testnet4, + Network::Regtest, + Network::Signet + ] + ); } } diff --git a/e2e/ledger/src/lib.rs b/e2e/ledger/src/lib.rs index 0505688..6c48721 100644 --- a/e2e/ledger/src/lib.rs +++ b/e2e/ledger/src/lib.rs @@ -160,9 +160,9 @@ mod tests { #[tokio::test] async fn can_get_version() { let (mut dev, _) = init().await; - let version = dev.get_version().await.unwrap(); - assert_eq!(version.version.to_string(), "2.4.5"); - assert_eq!(version.firmware, Some("Bitcoin Test".to_string())); - assert_eq!(version.network.unwrap().to_string(), "testnet"); + let info = dev.get_info().await.unwrap(); + assert_eq!(info.version.to_string(), "2.4.5"); + assert_eq!(info.firmware, Some("Bitcoin Test".to_string())); + assert_eq!(info.networks.first().unwrap().to_string(), "testnet"); } }