1use std::fmt::Debug;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use bitcoin::{
6 bip32::{DerivationPath, Fingerprint, Xpub},
7 psbt::Psbt,
8 taproot,
9};
10
11use serialport::{available_ports, SerialPort, SerialPortType};
12use tokio::{
13 io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt},
14 net::TcpStream,
15 sync::Mutex,
16};
17use tokio_serial::SerialPortBuilderExt;
18pub use tokio_serial::SerialStream;
19
20use super::{AddressScript, DeviceKind, Error as HWIError, HWI};
21use async_trait::async_trait;
22
23#[derive(Debug)]
24pub struct Specter<T> {
25 transport: T,
26 kind: DeviceKind,
27}
28
29impl<T: Transport> Specter<T> {
30 pub async fn fingerprint(&self) -> Result<Fingerprint, SpecterError> {
31 self.transport
32 .request("\r\n\r\nfingerprint\r\n")
33 .await
34 .and_then(|resp| {
35 Fingerprint::from_str(&resp).map_err(|e| SpecterError::Device(e.to_string()))
36 })
37 }
38
39 pub async fn get_extended_pubkey(&self, path: &DerivationPath) -> Result<Xpub, SpecterError> {
40 self.transport
41 .request(&format!("\r\n\r\nxpub {}\r\n", path))
42 .await
43 .and_then(|resp| Xpub::from_str(&resp).map_err(|e| SpecterError::Device(e.to_string())))
44 }
45
46 pub async fn add_wallet(&self, name: &str, policy: &str) -> Result<(), SpecterError> {
52 self.transport
53 .request(&format!(
54 "\r\n\r\naddwallet {}&{}\r\n",
55 name,
56 policy
57 .replace("/**", "/{0,1}/*")
58 .replace('<', "{")
60 .replace(';', ",")
61 .replace('>', "}")
62 ))
63 .await
64 .and_then(|resp| {
65 if resp.is_empty() || resp == "success" {
66 Ok(())
67 } else if resp == "error: User cancelled" {
68 Err(SpecterError::UserCancelled)
69 } else {
70 Err(SpecterError::Device(resp))
71 }
72 })
73 }
74
75 pub async fn sign(&self, psbt: &Psbt) -> Result<Psbt, SpecterError> {
76 self.transport
77 .request(&format!("\r\n\r\nsign {}\r\n", psbt))
78 .await
79 .and_then(|resp| {
80 if resp == "error: User cancelled" {
81 Err(SpecterError::UserCancelled)
82 } else {
83 Psbt::from_str(&resp).map_err(|e| SpecterError::Device(e.to_string()))
84 }
85 })
86 }
87}
88
89#[async_trait]
90impl<T: Transport + Sync + Send> HWI for Specter<T> {
91 fn device_kind(&self) -> DeviceKind {
92 self.kind
93 }
94
95 async fn get_version(&self) -> Result<super::Version, HWIError> {
96 Err(HWIError::UnimplementedMethod)
97 }
98
99 async fn get_master_fingerprint(&self) -> Result<Fingerprint, HWIError> {
100 Ok(self.fingerprint().await?)
101 }
102
103 async fn get_extended_pubkey(&self, path: &DerivationPath) -> Result<Xpub, HWIError> {
104 Ok(self.get_extended_pubkey(path).await?)
105 }
106
107 async fn display_address(&self, _script: &AddressScript) -> Result<(), HWIError> {
108 Err(HWIError::UnimplementedMethod)
109 }
110
111 async fn register_wallet(
112 &self,
113 name: &str,
114 policy: &str,
115 ) -> Result<Option<[u8; 32]>, HWIError> {
116 self.add_wallet(name, policy).await?;
117 Ok(None)
118 }
119
120 async fn is_wallet_registered(&self, _name: &str, _policy: &str) -> Result<bool, HWIError> {
121 Err(HWIError::UnimplementedMethod)
122 }
123
124 async fn sign_tx(&self, psbt: &mut Psbt) -> Result<(), HWIError> {
125 let mut new_psbt = self.sign(psbt).await?;
126 let mut has_signed = false;
131 for i in 0..new_psbt.inputs.len() {
132 if !new_psbt.inputs[i].partial_sigs.is_empty() {
133 has_signed = true;
134 psbt.inputs[i]
135 .partial_sigs
136 .append(&mut new_psbt.inputs[i].partial_sigs)
137 }
138 if !new_psbt.inputs[i].tap_script_sigs.is_empty() {
139 has_signed = true;
140 psbt.inputs[i]
141 .tap_script_sigs
142 .append(&mut new_psbt.inputs[i].tap_script_sigs)
143 }
144 if new_psbt.inputs[i].tap_key_sig.is_some() {
145 has_signed = true;
146 psbt.inputs[i].tap_key_sig = new_psbt.inputs[i].tap_key_sig;
147 } else {
148 if let Some(witness) = &new_psbt.inputs[i].final_script_witness {
151 if let Some(sig) = witness.nth(0) {
152 if let Ok(sig) = taproot::Signature::from_slice(sig) {
153 psbt.inputs[i].tap_key_sig = Some(sig);
154 has_signed = true;
155 }
156 }
157 }
158 }
159 }
160 if !has_signed {
161 return Err(SpecterError::DeviceDidNotSign.into());
162 }
163
164 Ok(())
165 }
166}
167
168impl<T: 'static + Transport + Sync + Send> From<Specter<T>> for Box<dyn HWI + Send> {
169 fn from(s: Specter<T>) -> Box<dyn HWI + Send> {
170 Box::new(s)
171 }
172}
173
174async fn exchange<T: Unpin + AsyncRead + AsyncWrite>(
175 transport: &mut T,
176 req: &str,
177) -> Result<String, SpecterError> {
178 transport
179 .write_all(req.as_bytes())
180 .await
181 .map_err(|e| SpecterError::Device(e.to_string()))?;
182
183 let reader = tokio::io::BufReader::new(transport);
184 let mut lines = reader.lines();
185 if let Some(line) = lines
186 .next_line()
187 .await
188 .map_err(|e| SpecterError::Device(e.to_string()))?
189 {
190 if line != "ACK" {
191 return Err(SpecterError::Device(
192 "Received an incorrect answer".to_string(),
193 ));
194 }
195 }
196
197 if let Some(line) = lines
198 .next_line()
199 .await
200 .map_err(|e| SpecterError::Device(e.to_string()))?
201 {
202 return Ok(line);
203 }
204 Err(SpecterError::Device("Unexpected".to_string()))
205}
206
207#[async_trait]
208pub trait Transport: Debug {
209 async fn request(&self, req: &str) -> Result<String, SpecterError>;
210}
211
212#[derive(Debug)]
213pub struct TcpTransport;
214pub const DEFAULT_ADDRESS: &str = "127.0.0.1:8789";
215
216#[async_trait]
217impl Transport for TcpTransport {
218 async fn request(&self, req: &str) -> Result<String, SpecterError> {
219 let mut transport = TcpStream::connect(DEFAULT_ADDRESS)
220 .await
221 .map_err(|e| SpecterError::Device(e.to_string()))?;
222 let res = exchange(&mut transport, req).await;
223 transport
224 .shutdown()
225 .await
226 .map_err(|e| SpecterError::Device(e.to_string()))?;
227 res
228 }
229}
230
231pub type SpecterSimulator = Specter<TcpTransport>;
232
233impl SpecterSimulator {
234 pub async fn try_connect() -> Result<Self, HWIError> {
235 let s = SpecterSimulator {
236 transport: TcpTransport {},
237 kind: DeviceKind::SpecterSimulator,
238 };
239 let _ = s.get_master_fingerprint().await?;
240 Ok(s)
241 }
242}
243
244impl Specter<SerialTransport> {
245 pub fn new(port_name: String) -> Result<Self, SpecterError> {
246 let transport = SerialTransport::new(port_name)?;
247 Ok(Self {
248 transport,
249 kind: DeviceKind::Specter,
250 })
251 }
252 pub async fn enumerate() -> Result<Vec<Self>, SpecterError> {
253 let mut res = Vec::new();
254 for port_name in SerialTransport::enumerate_potential_ports()? {
255 let specter = Specter::<SerialTransport>::new(port_name)?;
256 if specter.get_master_fingerprint().await.is_ok() {
257 res.push(specter);
258 }
259 }
260 Ok(res)
261 }
262}
263
264#[derive(Debug)]
265pub struct SerialTransport {
266 stream: Arc<Mutex<SerialStream>>,
267}
268
269impl SerialTransport {
270 pub const SPECTER_VID: u16 = 61525;
271 pub const SPECTER_PID: u16 = 38914;
272
273 pub fn new(port_name: String) -> Result<Self, SpecterError> {
274 let mut stream = tokio_serial::new(port_name, 9600)
275 .open_native_async()
276 .map_err(|e| SpecterError::Device(e.to_string()))?;
277 stream
278 .write_data_terminal_ready(true)
279 .map_err(|e| SpecterError::Device(e.to_string()))?;
280 Ok(Self {
281 stream: Arc::new(Mutex::new(stream)),
282 })
283 }
284
285 pub fn enumerate_potential_ports() -> Result<Vec<String>, SpecterError> {
286 match available_ports() {
287 Ok(ports) => Ok(ports
288 .into_iter()
289 .filter_map(|p| match p.port_type {
290 SerialPortType::UsbPort(info) => {
291 if info.vid == SerialTransport::SPECTER_VID
292 && info.pid == SerialTransport::SPECTER_PID
293 {
294 Some(p.port_name)
295 } else {
296 None
297 }
298 }
299 _ => None,
300 })
301 .collect()),
302 Err(e) => Err(SpecterError::Device(format!(
303 "Error listing serial ports: {}",
304 e
305 ))),
306 }
307 }
308}
309
310async fn exchange_serial(transport: &mut SerialStream, req: &str) -> Result<String, SpecterError> {
311 exchange(transport, req).await
312}
313
314#[async_trait]
315impl Transport for SerialTransport {
316 async fn request(&self, req: &str) -> Result<String, SpecterError> {
317 let mut transport = self.stream.lock().await;
318 exchange_serial(&mut transport, req).await
319 }
320}
321
322#[derive(Debug)]
323pub enum SpecterError {
324 DeviceNotFound,
325 DeviceDidNotSign,
326 Device(String),
327 UserCancelled,
328}
329
330impl std::fmt::Display for SpecterError {
331 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
332 match self {
333 Self::DeviceNotFound => write!(f, "Specter not found"),
334 Self::DeviceDidNotSign => write!(f, "Specter did not sign the psbt"),
335 Self::Device(e) => write!(f, "Specter error: {}", e),
336 Self::UserCancelled => write!(f, "User cancelled operation"),
337 }
338 }
339}
340
341impl From<SpecterError> for HWIError {
342 fn from(e: SpecterError) -> HWIError {
343 match e {
344 SpecterError::DeviceNotFound => HWIError::DeviceNotFound,
345 SpecterError::DeviceDidNotSign => HWIError::DeviceDidNotSign,
346 SpecterError::Device(e) => HWIError::Device(e),
347 SpecterError::UserCancelled => HWIError::UserRefused,
348 }
349 }
350}