1use std::{
2 str::FromStr,
3 sync::{Arc, Mutex, MutexGuard},
4};
5
6use async_trait::async_trait;
7use bitcoin::{
8 bip32::{DerivationPath, Fingerprint, Xpub},
9 psbt::Psbt,
10};
11use hidapi::DeviceInfo;
12
13use crate::{parse_version, AddressScript, DeviceKind, Error as HWIError, Version, HWI};
14pub use coldcard as api;
15
16#[derive(Debug)]
17pub struct Coldcard {
18 device: Arc<Mutex<coldcard::Coldcard>>,
19 wallet_name: Option<String>,
20}
21
22impl Coldcard {
23 pub fn with_wallet_name(mut self, wallet_name: String) -> Self {
24 self.wallet_name = Some(wallet_name);
25 self
26 }
27
28 fn device(&self) -> Result<MutexGuard<'_, coldcard::Coldcard>, HWIError> {
29 self.device
30 .lock()
31 .map_err(|_| HWIError::Unexpected("Failed to unlock"))
32 }
33}
34
35impl From<coldcard::Coldcard> for Coldcard {
36 fn from(cc: coldcard::Coldcard) -> Self {
37 Coldcard {
38 device: Arc::new(Mutex::new(cc)),
39 wallet_name: None,
40 }
41 }
42}
43
44#[async_trait]
45impl HWI for Coldcard {
46 fn device_kind(&self) -> DeviceKind {
47 DeviceKind::Coldcard
48 }
49
50 async fn get_version(&self) -> Result<Version, HWIError> {
52 let s = self.device()?.version()?;
53 for line in s.split('\n') {
54 if let Ok(version) = parse_version(line) {
55 return Ok(version);
56 }
57 }
58 Err(HWIError::UnsupportedVersion)
59 }
60
61 async fn get_master_fingerprint(&self) -> Result<Fingerprint, HWIError> {
62 let s = self.device()?.xpub(None)?;
63 let xpub = Xpub::from_str(&s).map_err(|e| HWIError::Device(e.to_string()))?;
64 Ok(xpub.fingerprint())
65 }
66
67 async fn get_extended_pubkey(&self, path: &DerivationPath) -> Result<Xpub, HWIError> {
68 let path = path.to_string();
69 let path = if path.starts_with("m/") {
70 path
71 } else {
72 format!("m/{}", path)
73 };
74 let path = coldcard::protocol::DerivationPath::new(&path)
75 .map_err(|e| HWIError::InvalidParameter("path", format!("{:?}", e)))?;
76 let s = self.device()?.xpub(Some(path))?;
77 Xpub::from_str(&s).map_err(|e| HWIError::Device(e.to_string()))
78 }
79
80 async fn display_address(&self, script: &AddressScript) -> Result<(), HWIError> {
81 if let Some(name) = &self.wallet_name {
82 let descriptor_name = coldcard::protocol::DescriptorName::new(name)
83 .map_err(|_| HWIError::UnsupportedInput)?;
84 if let AddressScript::Miniscript { index, change } = script {
85 self.device()?
86 .miniscript_address(descriptor_name, *change, *index)?;
87 Ok(())
88 } else {
89 Err(HWIError::UnimplementedMethod)
90 }
91 } else {
92 Err(HWIError::UnimplementedMethod)
93 }
94 }
95
96 async fn register_wallet(
97 &self,
98 name: &str,
99 policy: &str,
100 ) -> Result<Option<[u8; 32]>, HWIError> {
101 let payload = format!("{{\"name\":\"{}\",\"desc\":\"{}\"}}", name, policy);
102 let _ = self.device()?.miniscript_enroll(payload.as_bytes())?;
103 Ok(None)
104 }
105
106 async fn is_wallet_registered(&self, name: &str, policy: &str) -> Result<bool, HWIError> {
107 let descriptor_name = coldcard::protocol::DescriptorName::new(name)
108 .map_err(|_| HWIError::UnsupportedInput)?;
109 let desc = self.device()?.miniscript_get(descriptor_name)?;
110 if let Some(desc) = desc {
111 if let Some((policy, _)) = policy.replace('\'', "h").split_once('#') {
112 Ok(desc.contains(policy))
113 } else {
114 Ok(desc.contains(policy))
115 }
116 } else {
117 Ok(false)
118 }
119 }
120
121 async fn sign_tx(&self, psbt: &mut Psbt) -> Result<(), HWIError> {
122 let mut cc = self.device()?;
123
124 let _ = cc.sign_psbt(&psbt.serialize(), api::SignMode::Signed)?;
125
126 let tx = loop {
127 if let Some(tx) = cc.get_signed_tx()? {
128 break tx;
129 }
130 };
131
132 let mut new_psbt = Psbt::deserialize(&tx).map_err(|e| HWIError::Device(e.to_string()))?;
133
134 for i in 0..new_psbt.inputs.len() {
135 psbt.inputs[i]
136 .partial_sigs
137 .append(&mut new_psbt.inputs[i].partial_sigs);
138 psbt.inputs[i]
139 .tap_script_sigs
140 .append(&mut new_psbt.inputs[i].tap_script_sigs);
141 if let Some(sig) = new_psbt.inputs[i].tap_key_sig {
142 psbt.inputs[i].tap_key_sig = Some(sig);
143 }
144 }
145
146 Ok(())
147 }
148}
149
150impl From<api::Error> for HWIError {
151 fn from(e: api::Error) -> Self {
152 if let api::Error::UnexpectedResponse(api::protocol::Response::Refused) = e {
153 HWIError::UserRefused
154 } else {
155 HWIError::Device(e.to_string())
156 }
157 }
158}
159
160impl From<Coldcard> for Box<dyn HWI + Send> {
161 fn from(s: Coldcard) -> Box<dyn HWI + Send> {
162 Box::new(s)
163 }
164}
165
166impl From<Coldcard> for Arc<dyn HWI + Sync + Send> {
167 fn from(s: Coldcard) -> Arc<dyn HWI + Sync + Send> {
168 Arc::new(s)
169 }
170}
171
172pub fn is_coldcard(device_info: &DeviceInfo) -> bool {
173 device_info.vendor_id() == api::COINKITE_VID && device_info.product_id() == api::CKCC_PID
174}