Skip to content

Commit c1f20b2

Browse files
committed
Separate Python calls from the interface
1 parent 5e88904 commit c1f20b2

File tree

6 files changed

+552
-341
lines changed

6 files changed

+552
-341
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,16 @@ use bitcoin::Network;
5858
use bitcoin::bip32::DerivationPath;
5959
use hwi::error::Error;
6060
use hwi::HWIClient;
61+
use hwi::implementations::python_implementation::PythonHWIImplementation;
6162
use std::str::FromStr;
6263

6364
fn main() -> Result<(), Error> {
64-
let mut devices = HWIClient::enumerate()?;
65+
let mut devices = HWIClient::<PythonHWIImplementation>::enumerate()?;
6566
if devices.is_empty() {
6667
panic!("No devices found!");
6768
}
6869
let first_device = devices.remove(0)?;
69-
let client = HWIClient::get_client(&first_device, true, Network::Bitcoin.into())?;
70+
let client = HWIClient::<PythonHWIImplementation>::get_client(&first_device, true, Network::Bitcoin.into())?;
7071
let derivation_path = DerivationPath::from_str("m/44'/1'/0'/0/0").unwrap();
7172
let s = client.sign_message("I love BDK wallet", &derivation_path)?;
7273
println!("{:?}", s.signature);

src/implementations.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod python_implementation;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
use crate::error::Error;
2+
use crate::types::{
3+
HWIAddressType, HWIChain, HWIDevice, HWIDeviceType, HWIImplementation, LogLevel,
4+
};
5+
use bitcoin::Psbt;
6+
use pyo3::{prelude::*, py_run};
7+
use std::ops::Deref;
8+
use std::process::Command;
9+
10+
/// Convenience class containing required Python objects
11+
#[derive(Debug)]
12+
struct HWILib {
13+
commands: Py<PyModule>,
14+
json_dumps: Py<PyAny>,
15+
}
16+
17+
impl HWILib {
18+
pub fn initialize() -> Result<Self, Error> {
19+
Python::with_gil(|py| {
20+
let commands: Py<PyModule> = PyModule::import_bound(py, "hwilib.commands")?.into();
21+
let json_dumps: Py<PyAny> =
22+
PyModule::import_bound(py, "json")?.getattr("dumps")?.into();
23+
Ok(HWILib {
24+
commands,
25+
json_dumps,
26+
})
27+
})
28+
}
29+
}
30+
31+
#[derive(Debug)]
32+
pub struct PythonHWIImplementation {
33+
hwilib: HWILib,
34+
hw_client: PyObject,
35+
}
36+
37+
impl Deref for PythonHWIImplementation {
38+
type Target = PyObject;
39+
40+
fn deref(&self) -> &Self::Target {
41+
&self.hw_client
42+
}
43+
}
44+
45+
impl HWIImplementation for PythonHWIImplementation {
46+
fn enumerate() -> Result<String, Error> {
47+
let libs = HWILib::initialize()?;
48+
Python::with_gil(|py| {
49+
let output = libs.commands.getattr(py, "enumerate")?.call0(py)?;
50+
let output = libs.json_dumps.call1(py, (output,))?;
51+
Ok(output.to_string())
52+
})
53+
}
54+
55+
fn get_client(device: &HWIDevice, expert: bool, chain: HWIChain) -> Result<Self, Error> {
56+
let libs = HWILib::initialize()?;
57+
let hw_client = Python::with_gil(|py| {
58+
let client_args = (
59+
device.device_type.to_string(),
60+
&device.path,
61+
"",
62+
expert,
63+
chain,
64+
);
65+
libs.commands
66+
.getattr(py, "get_client")?
67+
.call1(py, client_args)
68+
})?;
69+
70+
Ok(Self {
71+
hwilib: libs,
72+
hw_client,
73+
})
74+
}
75+
76+
fn find_device(
77+
password: Option<&str>,
78+
device_type: Option<HWIDeviceType>,
79+
fingerprint: Option<&str>,
80+
expert: bool,
81+
chain: HWIChain,
82+
) -> Result<Self, Error> {
83+
let libs = HWILib::initialize()?;
84+
let hw_client = Python::with_gil(|py| {
85+
let client_args = (
86+
password.unwrap_or(""),
87+
device_type.map_or_else(String::new, |d| d.to_string()),
88+
fingerprint.unwrap_or(""),
89+
expert,
90+
chain,
91+
);
92+
let client = libs
93+
.commands
94+
.getattr(py, "find_device")?
95+
.call1(py, client_args)?;
96+
if client.is_none(py) {
97+
return Err(Error::Hwi("device not found".to_string(), None));
98+
}
99+
Ok(client)
100+
})?;
101+
102+
Ok(Self {
103+
hwilib: libs,
104+
hw_client,
105+
})
106+
}
107+
108+
fn get_master_xpub(&self, addrtype: HWIAddressType, account: u32) -> Result<String, Error> {
109+
Python::with_gil(|py| {
110+
let func_args = (&self.hw_client, addrtype, account);
111+
let output = self
112+
.hwilib
113+
.commands
114+
.getattr(py, "getmasterxpub")?
115+
.call1(py, func_args)?;
116+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
117+
Ok(output.to_string())
118+
})
119+
}
120+
121+
fn sign_tx(&self, psbt: &Psbt) -> Result<String, Error> {
122+
Python::with_gil(|py| {
123+
let output = self
124+
.hwilib
125+
.commands
126+
.getattr(py, "signtx")?
127+
.call1(py, (&self.hw_client, psbt.to_string()))?;
128+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
129+
Ok(output.to_string())
130+
})
131+
}
132+
133+
fn get_xpub(&self, path: &str, expert: bool) -> Result<String, Error> {
134+
Python::with_gil(|py| {
135+
let func_args = (&self.hw_client, path, expert);
136+
let output = self
137+
.hwilib
138+
.commands
139+
.getattr(py, "getxpub")?
140+
.call1(py, func_args)?;
141+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
142+
Ok(output.to_string())
143+
})
144+
}
145+
146+
fn sign_message(&self, message: &str, path: &str) -> Result<String, Error> {
147+
Python::with_gil(|py| {
148+
let func_args = (&self.hw_client, message, path);
149+
let output = self
150+
.hwilib
151+
.commands
152+
.getattr(py, "signmessage")?
153+
.call1(py, func_args)?;
154+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
155+
Ok(output.to_string())
156+
})
157+
}
158+
159+
fn get_keypool(
160+
&self,
161+
keypool: bool,
162+
internal: bool,
163+
addr_type: HWIAddressType,
164+
addr_all: bool,
165+
account: u32,
166+
path: Option<String>,
167+
start: u32,
168+
end: u32,
169+
) -> Result<String, Error> {
170+
Python::with_gil(|py| {
171+
let p_str = path.map_or(py.None(), |p| p.into_py(py));
172+
let func_args = (
173+
&self.hw_client,
174+
p_str,
175+
start,
176+
end,
177+
internal,
178+
keypool,
179+
account,
180+
addr_type,
181+
addr_all,
182+
);
183+
let output = self
184+
.hwilib
185+
.commands
186+
.getattr(py, "getkeypool")?
187+
.call1(py, func_args)?;
188+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
189+
Ok(output.to_string())
190+
})
191+
}
192+
193+
fn get_descriptors(&self, account: u32) -> Result<String, Error> {
194+
Python::with_gil(|py| {
195+
let func_args = (&self.hw_client, account);
196+
let output = self
197+
.hwilib
198+
.commands
199+
.getattr(py, "getdescriptors")?
200+
.call1(py, func_args)?;
201+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
202+
Ok(output.to_string())
203+
})
204+
}
205+
206+
fn display_address_with_desc(&self, descriptor: &str) -> Result<String, Error> {
207+
Python::with_gil(|py| {
208+
let path = py.None();
209+
let func_args = (&self.hw_client, path, descriptor);
210+
let output = self
211+
.hwilib
212+
.commands
213+
.getattr(py, "displayaddress")?
214+
.call1(py, func_args)?;
215+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
216+
Ok(output.to_string())
217+
})
218+
}
219+
220+
fn display_address_with_path(
221+
&self,
222+
path: &str,
223+
address_type: HWIAddressType,
224+
) -> Result<String, Error> {
225+
Python::with_gil(|py| {
226+
let descriptor = py.None();
227+
let func_args = (&self.hw_client, path, descriptor, address_type);
228+
let output = self
229+
.hwilib
230+
.commands
231+
.getattr(py, "displayaddress")?
232+
.call1(py, func_args)?;
233+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
234+
Ok(output.to_string())
235+
})
236+
}
237+
238+
fn install_udev_rules(source: &str, location: &str) -> Result<String, Error> {
239+
let libs = HWILib::initialize()?;
240+
241+
Python::with_gil(|py| {
242+
let func_args = (source, location);
243+
let output = libs
244+
.commands
245+
.getattr(py, "install_udev_rules")?
246+
.call1(py, func_args)?;
247+
let output = libs.json_dumps.call1(py, (output,))?;
248+
Ok(output.to_string())
249+
})
250+
}
251+
252+
fn set_log_level(level: LogLevel) -> Result<(), Error> {
253+
Python::with_gil(|py| {
254+
let arg = match level {
255+
LogLevel::DEBUG => 10,
256+
LogLevel::INFO => 20,
257+
LogLevel::WARNING => 30,
258+
LogLevel::ERROR => 40,
259+
LogLevel::CRITICAL => 50,
260+
};
261+
py_run!(
262+
py,
263+
arg,
264+
r#"
265+
import logging
266+
logging.basicConfig(level=arg)
267+
"#
268+
);
269+
Ok(())
270+
})
271+
}
272+
273+
fn toggle_passphrase(&self) -> Result<String, Error> {
274+
Python::with_gil(|py| {
275+
let func_args = (&self.hw_client,);
276+
let output = self
277+
.hwilib
278+
.commands
279+
.getattr(py, "toggle_passphrase")?
280+
.call1(py, func_args)?;
281+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
282+
Ok(output.to_string())
283+
})
284+
}
285+
286+
fn setup_device(&self, label: &str, passphrase: &str) -> Result<String, Error> {
287+
Python::with_gil(|py| {
288+
let func_args = (&self.hw_client, label, passphrase);
289+
let output = self
290+
.hwilib
291+
.commands
292+
.getattr(py, "setup_device")?
293+
.call1(py, func_args)?;
294+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
295+
Ok(output.to_string())
296+
})
297+
}
298+
299+
fn restore_device(&self, label: &str, word_count: u8) -> Result<String, Error> {
300+
Python::with_gil(|py| {
301+
let func_args = (&self.hw_client, label, word_count);
302+
let output = self
303+
.hwilib
304+
.commands
305+
.getattr(py, "restore_device")?
306+
.call1(py, func_args)?;
307+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
308+
Ok(output.to_string())
309+
})
310+
}
311+
312+
fn backup_device(&self, label: &str, backup_passphrase: &str) -> Result<String, Error> {
313+
Python::with_gil(|py| {
314+
let func_args = (&self.hw_client, label, backup_passphrase);
315+
let output = self
316+
.hwilib
317+
.commands
318+
.getattr(py, "backup_device")?
319+
.call1(py, func_args)?;
320+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
321+
Ok(output.to_string())
322+
})
323+
}
324+
325+
fn wipe_device(&self) -> Result<String, Error> {
326+
Python::with_gil(|py| {
327+
let func_args = (&self.hw_client,);
328+
let output = self
329+
.hwilib
330+
.commands
331+
.getattr(py, "wipe_device")?
332+
.call1(py, func_args)?;
333+
let output = self.hwilib.json_dumps.call1(py, (output,))?;
334+
Ok(output.to_string())
335+
})
336+
}
337+
338+
fn get_version() -> Result<String, Error> {
339+
Python::with_gil(|py| {
340+
let hwilib = PyModule::import_bound(py, "hwilib")?;
341+
let version = hwilib.getattr("__version__")?.extract::<String>()?;
342+
343+
Ok(version)
344+
})
345+
}
346+
347+
fn install_hwilib(version: String) -> Result<(), Error> {
348+
let output = Command::new("pip")
349+
.args(vec!["install", "--user", &version])
350+
.output()
351+
.map_err(|e| Error::Hwi(format!("Failed to execute pip: {}", e), None))?;
352+
353+
if output.status.success() {
354+
Ok(())
355+
} else {
356+
let error_message = String::from_utf8_lossy(&output.stderr).into_owned();
357+
Err(Error::Hwi(
358+
format!("Failed to install HWI: {}", error_message),
359+
None,
360+
))
361+
}
362+
}
363+
}

0 commit comments

Comments
 (0)