Skip to main content

async_hwi/
specter.rs

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    /// If the descriptor contains master public keys but doesn't contain wildcard derivations,
47    /// the default derivation /{0,1}/* will be added by the device to all extended keys in the descriptor.
48    /// See: https://github.com/cryptoadvance/specter-diy/blob/master/docs/descriptors.md#default-derivations
49    /// If at least one of the xpubs has a wildcard derivation the descriptor will not be changed.
50    /// /** is an equivalent of /{0,1}/*.
51    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                    // currently specter does not support <0;1> but {0,1}
59                    .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        // Psbt returned by specter wallet has all unnecessary fields removed,
127        // only global transaction and partial signatures for all inputs remain in it.
128        // In order to have the full Psbt, the partial_sigs are extracted and appended
129        // to the original psbt.
130        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                // Specter does not populate PSBT_TAP_KEY_SIG at v1.9.0
149                // see https://github.com/cryptoadvance/specter-diy/issues/277#issuecomment-2183906271
150                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}