From e1a5bccd52c042cf0440baf8c6990f4e7f6dedc8 Mon Sep 17 00:00:00 2001 From: benk10 Date: Thu, 29 Aug 2024 09:00:53 +0400 Subject: [PATCH] Add HWI binary implementation --- src/error.rs | 3 + src/implementations.rs | 1 + src/implementations/binary_implementation.rs | 331 +++++++++++++++++++ src/types.rs | 27 ++ 4 files changed, 362 insertions(+) create mode 100644 src/implementations/binary_implementation.rs diff --git a/src/error.rs b/src/error.rs index aa983fb..1619507 100644 --- a/src/error.rs +++ b/src/error.rs @@ -78,6 +78,7 @@ pub enum Error { Io(std::io::Error), Hwi(String, Option), Python(pyo3::PyErr), + NotImplemented, } impl fmt::Display for Error { @@ -90,6 +91,7 @@ impl fmt::Display for Error { Io(_) => f.write_str("I/O error"), Hwi(ref s, ref code) => write!(f, "HWI error: {}, ({:?})", s, code), Python(_) => f.write_str("python error"), + NotImplemented => f.write_str("not implemented"), } } } @@ -104,6 +106,7 @@ impl std::error::Error for Error { Io(ref e) => Some(e), Hwi(_, _) => None, Python(ref e) => Some(e), + NotImplemented => None, } } } diff --git a/src/implementations.rs b/src/implementations.rs index 082f357..4a513c5 100644 --- a/src/implementations.rs +++ b/src/implementations.rs @@ -1 +1,2 @@ +pub mod binary_implementation; pub mod python_implementation; diff --git a/src/implementations/binary_implementation.rs b/src/implementations/binary_implementation.rs new file mode 100644 index 0000000..31ad7ac --- /dev/null +++ b/src/implementations/binary_implementation.rs @@ -0,0 +1,331 @@ +use serde_json::value::Value; +use std::str; + +use crate::error::Error; +use crate::types::{ + HWIAddressType, HWIBinaryExecutor, HWIChain, HWIDevice, HWIDeviceType, HWIImplementation, + LogLevel, +}; +use bitcoin::Psbt; + +macro_rules! deserialize_obj { + ( $e: expr ) => {{ + let value: Value = serde_json::from_str($e)?; + let obj = value.clone(); + serde_json::from_value(value) + .map_err(|e| Error::Hwi(format!("error {} while deserializing {}", e, obj), None)) + }}; +} + +#[derive(Debug)] +pub struct BinaryHWIImplementation { + device: Option, + expert: bool, + chain: HWIChain, + _phantom: std::marker::PhantomData, +} + +impl HWIImplementation for BinaryHWIImplementation { + fn enumerate() -> Result { + let output = + BinaryHWIImplementation::::run_hwi_command(None, false, None, vec!["enumerate"])?; + Ok(output.to_string()) + } + + fn get_client(device: &HWIDevice, expert: bool, chain: HWIChain) -> Result { + Ok(Self { + device: Some(device.clone()), + expert, + chain, + _phantom: std::marker::PhantomData, + }) + } + + fn find_device( + password: Option<&str>, + device_type: Option, + fingerprint: Option<&str>, + expert: bool, + chain: HWIChain, + ) -> Result { + let mut client = BinaryHWIImplementation { + device: None, + expert, + chain, + _phantom: std::marker::PhantomData, + }; + + let mut args = vec!["enumerate"]; + + if let Some(pw) = password { + args.extend_from_slice(&["--password", pw]); + } + + let device_type_str = device_type.map(|dt| dt.to_string()); + + if let Some(dt) = device_type_str.as_deref() { + args.extend_from_slice(&["--device-type", dt]); + } + if let Some(fp) = fingerprint { + args.extend_from_slice(&["--fingerprint", fp]); + } + + let output = + BinaryHWIImplementation::::run_hwi_command(None, expert, Some(&client.chain), args)?; + let devices: Vec = deserialize_obj!(&output)?; + + if devices.is_empty() { + return Err(Error::Hwi("No devices found".to_string(), None)); + } + + client.device = Some(devices[0].clone()); + Ok(client) + } + + fn get_master_xpub(&self, addrtype: HWIAddressType, account: u32) -> Result { + let mut args = vec!["getmasterxpub"]; + let addrtype_str = addrtype.to_string(); + let account_str = account.to_string(); + args.extend_from_slice(&["--addr-type", &addrtype_str]); + args.extend_from_slice(&["--account", &account_str]); + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn sign_tx(&self, psbt: &Psbt) -> Result { + let psbt_str = psbt.to_string(); + let args = vec!["signtx", &psbt_str]; + + let output = BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + )?; + Ok(output) + } + + fn get_xpub(&self, path: &str, expert: bool) -> Result { + let args = vec!["getxpub", &path]; + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + expert, + Some(&self.chain), + args, + ) + } + + fn sign_message(&self, message: &str, path: &str) -> Result { + let args = vec!["signmessage", message, path]; + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn get_keypool( + &self, + keypool: bool, + internal: bool, + addr_type: HWIAddressType, + addr_all: bool, + account: u32, + path: Option, + start: u32, + end: u32, + ) -> Result { + let mut args = vec!["getkeypool"]; + + if keypool { + args.push("--keypool"); + } + if internal { + args.push("--internal"); + } + let addrtype_str = addr_type.to_string(); + args.extend_from_slice(&["--addr-type", &addrtype_str]); + if addr_all { + args.push("--addr-all"); + } + let account_str = account.to_string(); + args.extend_from_slice(&["--account", &account_str]); + if let Some(p) = path.as_deref() { + args.extend_from_slice(&["--path", p]); + } + let start_str = start.to_string(); + args.push(&start_str); + let end_str = end.to_string(); + args.push(&end_str); + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn get_descriptors(&self, account: u32) -> Result { + let mut args = vec!["getdescriptors"]; + let account_str = account.to_string(); + args.extend_from_slice(&["--account", &account_str]); + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn display_address_with_desc(&self, descriptor: &str) -> Result { + let mut args = vec!["displayaddress"]; + args.push("--desc"); + args.push(descriptor); + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn display_address_with_path( + &self, + path: &str, + address_type: HWIAddressType, + ) -> Result { + let mut args = vec!["displayaddress"]; + args.extend_from_slice(&["--path", path]); + let addr_type_str = address_type.to_string(); + args.extend_from_slice(&["--addr-type", &addr_type_str]); + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn install_udev_rules(_: &str, location: &str) -> Result { + let mut args = vec!["installudevrules"]; + args.extend_from_slice(&["--location", location]); + + BinaryHWIImplementation::::run_hwi_command(None, false, None, args) + } + + fn set_log_level(_: LogLevel) -> Result<(), Error> { + Err(Error::NotImplemented) + } + + fn toggle_passphrase(&self) -> Result { + let args = vec!["togglepassphrase"]; + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn setup_device(&self, label: &str, passphrase: &str) -> Result { + let mut args = vec!["setup"]; + args.extend_from_slice(&["--label", label]); + args.extend_from_slice(&["--backup_passphrase", passphrase]); + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn restore_device(&self, label: &str, word_count: u8) -> Result { + let mut args = vec!["restore"]; + let word_count_str = word_count.to_string(); + args.extend_from_slice(&["--word_count", &word_count_str]); + args.extend_from_slice(&["--label", label]); + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + fn backup_device(&self, label: &str, backup_passphrase: &str) -> Result { + let mut args = vec!["backup"]; + args.extend_from_slice(&["--label", label]); + args.extend_from_slice(&["--backup_passphrase", backup_passphrase]); + + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn wipe_device(&self) -> Result { + let args = vec!["wipe"]; + BinaryHWIImplementation::::run_hwi_command( + self.device.as_ref(), + self.expert, + Some(&self.chain), + args, + ) + } + + fn get_version() -> Result { + let args = vec!["--version"]; + BinaryHWIImplementation::::run_hwi_command(None, false, None, args) + } + + fn install_hwilib(_: String) -> Result<(), Error> { + Err(Error::NotImplemented) + } +} + +impl BinaryHWIImplementation { + fn run_hwi_command( + device: Option<&HWIDevice>, + expert: bool, + chain: Option<&HWIChain>, + args: Vec<&str>, + ) -> Result { + let mut command_args = Vec::new(); + + if args[0] != "enumerate" && args[0] != "--version" { + let fingerprint = device + .ok_or(Error::Hwi("Device fingerprint not set".to_string(), None))? + .fingerprint; + command_args.push("--fingerprint".to_string()); + command_args.push(fingerprint.to_string()); + } + + if expert { + command_args.push("--expert".to_string()); + } + + if let Some(c) = chain { + command_args.push("--chain".to_string()); + command_args.push(c.to_string()); + } + + command_args.extend(args.iter().map(|s| s.to_string())); + + T::execute_command(command_args) + } +} diff --git a/src/types.rs b/src/types.rs index 6678541..c1f0fd4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -112,6 +112,17 @@ pub enum HWIAddressType { Tap, } +impl fmt::Display for HWIAddressType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + HWIAddressType::Legacy => "LEGACY", + HWIAddressType::Sh_Wit => "SH_WIT", + HWIAddressType::Wit => "WIT", + HWIAddressType::Tap => "TAP", + }) + } +} + impl IntoPy for HWIAddressType { fn into_py(self, py: pyo3::Python) -> PyObject { let addrtype = PyModule::import_bound(py, "hwilib.common") @@ -130,6 +141,18 @@ impl IntoPy for HWIAddressType { #[derive(Clone, Eq, PartialEq, Debug, Deserialize)] pub struct HWIChain(bitcoin::Network); +impl fmt::Display for HWIChain { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self.0 { + bitcoin::Network::Bitcoin => "MAIN", + bitcoin::Network::Testnet => "TEST", + bitcoin::Network::Regtest => "REGTEST", + bitcoin::Network::Signet => "SIGNET", + _ => "UNKNOWN", + }) + } +} + impl IntoPy for HWIChain { fn into_py(self, py: pyo3::Python) -> PyObject { use bitcoin::Network::*; @@ -343,3 +366,7 @@ pub trait HWIImplementation { fn set_log_level(level: LogLevel) -> Result<(), Error>; fn install_hwilib(version: String) -> Result<(), Error>; } + +pub trait HWIBinaryExecutor { + fn execute_command(args: Vec) -> Result; +}