diff --git a/src/interface.rs b/src/interface.rs index f5745cb..7eb13e0 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -394,7 +394,7 @@ impl HWIClient { arg, r#" import logging - logging.basicConfig(level=arg) + logging.basicConfig(level=arg) "# ); Ok(()) @@ -526,4 +526,32 @@ impl HWIClient { )) } } + + pub fn prompt_pin(&self) -> Result<(), Error> { + Python::with_gil(|py| { + let func_args = (&self.hw_client,); + let output = self + .hwilib + .commands + .getattr(py, "prompt_pin")? + .call1(py, func_args)?; + let output = self.hwilib.json_dumps.call1(py, (output,))?; + let status: HWIStatus = deserialize_obj!(&output.to_string())?; + status.into() + }) + } + + pub fn send_pin(&self, pin: String) -> Result<(), Error> { + Python::with_gil(|py| { + let func_args = (&self.hw_client, pin); + let output = self + .hwilib + .commands + .getattr(py, "send_pin")? + .call1(py, func_args)?; + let output = self.hwilib.json_dumps.call1(py, (output,))?; + let status: HWIStatus = deserialize_obj!(&output.to_string())?; + status.into() + }) + } } diff --git a/src/lib.rs b/src/lib.rs index d986b1c..3fc7692 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ mod tests { use crate::types::{self, HWIDeviceType, TESTNET}; use crate::HWIClient; use std::collections::BTreeMap; + use std::io::stdin; use std::str::FromStr; use bitcoin::bip32::{DerivationPath, KeySource}; @@ -208,7 +209,10 @@ mod tests { let pk = client.get_xpub(&derivation_path, true).unwrap(); let mut hd_keypaths: BTreeMap = Default::default(); // Here device fingerprint is same as master xpub fingerprint - hd_keypaths.insert(pk.public_key, (device.fingerprint, derivation_path)); + hd_keypaths.insert( + pk.public_key, + (device.fingerprint.unwrap(), derivation_path), + ); let script_pubkey = address.address.assume_checked().script_pubkey(); @@ -440,4 +444,54 @@ mod tests { fn test_install_hwi() { HWIClient::install_hwilib(Some("2.1.1")).unwrap(); } + + #[test] + #[serial] + fn test_prompt_pin() { + let devices = HWIClient::enumerate().unwrap(); + let unsupported = [ + HWIDeviceType::Ledger, + HWIDeviceType::Coldcard, + HWIDeviceType::Jade, + HWIDeviceType::BitBox01, + HWIDeviceType::BitBox02, + ]; + for device in devices { + let device = device.unwrap(); + if unsupported.contains(&device.device_type) { + // These devices don't support prompt_pin + continue; + } + let client = HWIClient::get_client(&device, true, TESTNET).unwrap(); + client.prompt_pin().unwrap(); + } + } + + #[test] + #[serial] + fn test_send_pin() { + let devices = HWIClient::enumerate().unwrap(); + let unsupported = [ + HWIDeviceType::Ledger, + HWIDeviceType::Coldcard, + HWIDeviceType::Jade, + HWIDeviceType::BitBox01, + HWIDeviceType::BitBox02, + ]; + for device in devices { + let device = device.unwrap(); + if unsupported.contains(&device.device_type) { + // These devices don't support send_pin + continue; + } + // Set the device to a state where pin can be sent + let client = HWIClient::get_client(&device, true, TESTNET).unwrap(); + client.prompt_pin().unwrap(); + + let mut s = String::new(); + stdin().read_line(&mut s).expect("Please Enter Pin"); + let device_pin: String = s.split_whitespace().map(str::to_string).collect(); + client.send_pin(String::from(device_pin)).unwrap(); + } + } } diff --git a/src/types.rs b/src/types.rs index 4f368ef..fb3b39d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -181,33 +181,46 @@ pub struct HWIDevice { pub path: String, pub needs_pin_sent: bool, pub needs_passphrase_sent: bool, - pub fingerprint: Fingerprint, + pub fingerprint: Option, } impl TryFrom for HWIDevice { type Error = Error; + fn try_from(h: HWIDeviceInternal) -> Result { - match h.error { + let needs_pin_sent = h.needs_pin_sent.unwrap_or_default(); + let result = match h.error { Some(e) => { let code = h.code.and_then(|c| ErrorCode::try_from(c).ok()); - Err(Error::Hwi(e, code)) + if code == Some(ErrorCode::DeviceNotReady) && needs_pin_sent { + None + } else { + Some(Error::Hwi(e, code)) + } + } + None => None, + }; + match result { + Some(error) => Err(error), + None => { + let fingerprint = if needs_pin_sent { + None + } else { + Some(h.fingerprint.expect("Fingerprint should be here")) + }; + Ok(HWIDevice { + device_type: HWIDeviceType::from( + h.device_type.expect("Device type should be here"), + ), + model: h.model.expect("Model should be here"), + path: h.path.expect("Path should be here"), + needs_pin_sent: h.needs_pin_sent.expect("needs_pin_sent should be here"), + needs_passphrase_sent: h + .needs_passphrase_sent + .expect("needs_passphrase_sent should be here"), + fingerprint, + }) } - // When HWIDeviceInternal contains errors, some fields might be missing - // (depending on the error, hwi might not be able to know all of them). - // When there's no error though, all the fields must be present, and - // for this reason we expect here. - None => Ok(HWIDevice { - device_type: HWIDeviceType::from( - h.device_type.expect("Device type should be here"), - ), - model: h.model.expect("Model should be here"), - path: h.path.expect("Path should be here"), - needs_pin_sent: h.needs_pin_sent.expect("needs_pin_sent should be here"), - needs_passphrase_sent: h - .needs_passphrase_sent - .expect("needs_passphrase_sent should be here"), - fingerprint: h.fingerprint.expect("Fingerprint should be here"), - }), } } }