1use crate::apdu::tap_signer::{XpubCommand, XpubResponse};
5use crate::apdu::{
6 CommandApdu as _, DeriveCommand, DeriveResponse, NewCommand, NewResponse, SignCommand,
7 SignResponse, StatusCommand, StatusResponse,
8 tap_signer::{BackupCommand, BackupResponse, ChangeCommand, ChangeResponse},
9};
10use crate::error::{ChangeError, DeriveError, ReadError, SignPsbtError, StatusError, XpubError};
11use crate::shared::{
12 Authentication, Certificate, CkTransport, Nfc, Read, Wait, card_pubkey_to_ident, transmit,
13};
14use crate::{BIP32_HARDENED_MASK, CkTapError};
15use async_trait::async_trait;
16use bitcoin::PublicKey;
17use bitcoin::bip32::{ChainCode, Xpub};
18use bitcoin::hex::DisplayHex;
19use bitcoin::secp256k1::{self, All, Message, Secp256k1, ecdsa::Signature};
20use bitcoin_hashes::sha256;
21use std::sync::Arc;
22
23const BIP84_PATH_LEN: u32 = 5;
24
25const BIP84_HARDENED_SUBPATH: [u32; 3] = [0, 1, 2];
29
30pub struct TapSigner {
31 pub transport: Arc<dyn CkTransport>,
32 pub secp: Secp256k1<All>,
33 pub proto: u32,
34 pub ver: String,
35 pub birth: u32,
36 pub path: Option<Vec<u32>>,
37 pub num_backups: Option<u32>,
39 pub pubkey: PublicKey,
40 pub card_nonce: [u8; 16],
41 pub auth_delay: Option<u8>,
42}
43
44impl Authentication for TapSigner {
45 fn secp(&self) -> &Secp256k1<All> {
46 &self.secp
47 }
48
49 fn ver(&self) -> &str {
50 &self.ver
51 }
52
53 fn pubkey(&self) -> &PublicKey {
54 &self.pubkey
55 }
56
57 fn card_nonce(&self) -> &[u8; 16] {
58 &self.card_nonce
59 }
60
61 fn set_card_nonce(&mut self, new_nonce: [u8; 16]) {
62 self.card_nonce = new_nonce;
63 }
64
65 fn auth_delay(&self) -> Option<u8> {
66 self.auth_delay
67 }
68
69 fn set_auth_delay(&mut self, auth_delay: Option<u8>) {
70 self.auth_delay = auth_delay;
71 }
72
73 fn transport(&self) -> Arc<dyn CkTransport> {
74 self.transport.clone()
75 }
76}
77
78#[async_trait]
80pub trait TapSignerShared: Authentication {
81 async fn init(&mut self, chain_code: ChainCode, cvc: &str) -> Result<(), CkTapError> {
83 let (_, epubkey, xcvc) = self.calc_ekeys_xcvc(cvc, NewCommand::name());
84 let new_command = NewCommand::new(Some(0), Some(chain_code), epubkey, xcvc);
85 let new_response: NewResponse = transmit(self.transport(), &new_command).await?;
86 self.set_card_nonce(new_response.card_nonce);
87 Ok(())
88 }
89
90 async fn status(&mut self) -> Result<StatusResponse, CkTapError> {
92 let cmd = StatusCommand::default();
93 let status_response: StatusResponse = transmit(self.transport(), &cmd).await?;
94 self.set_card_nonce(status_response.card_nonce);
95 Ok(status_response)
96 }
97
98 async fn sign(
100 &mut self,
101 digest: [u8; 32],
102 sub_path: Vec<u32>,
103 cvc: &str,
104 ) -> Result<SignResponse, CkTapError> {
105 let (eprivkey, epubkey, xcvc) = self.calc_ekeys_xcvc(cvc, SignCommand::name());
106
107 let session_key = secp256k1::ecdh::SharedSecret::new(&self.pubkey().inner, &eprivkey);
109
110 let xdigest_vec: Vec<u8> = session_key
112 .as_ref()
113 .iter()
114 .zip(digest)
115 .map(|(session_key_byte, digest_byte)| session_key_byte ^ digest_byte)
116 .collect();
117
118 let xdigest: [u8; 32] = xdigest_vec.try_into().expect("input is also 32 bytes");
119
120 let sign_command =
121 SignCommand::for_tapsigner(sub_path.clone(), xdigest, epubkey, xcvc.clone());
122
123 let mut sign_response: Result<SignResponse, CkTapError> =
124 transmit(self.transport(), &sign_command).await;
125
126 let mut unlucky_number_retries = 0;
127 while let Err(CkTapError::Card(crate::CardError::UnluckyNumber)) = sign_response {
128 let sign_command =
129 SignCommand::for_tapsigner(sub_path.clone(), xdigest, epubkey, xcvc.clone());
130
131 sign_response = transmit(self.transport(), &sign_command).await;
132 unlucky_number_retries += 1;
133
134 if unlucky_number_retries > 3 {
135 break;
136 }
137 }
138
139 let sign_response = sign_response?;
140 self.set_card_nonce(sign_response.card_nonce);
141 Ok(sign_response)
142 }
143
144 async fn sign_psbt(
148 &mut self,
149 mut psbt: bitcoin::Psbt,
150 cvc: &str,
151 ) -> Result<bitcoin::Psbt, SignPsbtError> {
152 use bitcoin::{
153 secp256k1::ecdsa,
154 sighash::{EcdsaSighashType, SighashCache},
155 };
156
157 let unsigned_tx = psbt.unsigned_tx.clone();
158 let mut sighash_cache = SighashCache::new(&unsigned_tx);
159
160 for (input_index, input) in psbt.inputs.iter_mut().enumerate() {
161 let witness_utxo = input
163 .witness_utxo
164 .as_ref()
165 .ok_or(SignPsbtError::MissingUtxo(input_index as u32))?;
166
167 let amount = witness_utxo.value;
168
169 let script_pubkey = &witness_utxo.script_pubkey;
171 if !script_pubkey.is_p2wpkh() {
172 return Err(SignPsbtError::InvalidScript(input_index as u32));
173 }
174
175 let key_pairs = &input.bip32_derivation;
177 let (psbt_pubkey, (_fingerprint, path)) = key_pairs
178 .iter()
179 .next()
180 .ok_or(SignPsbtError::MissingPubkey(input_index as u32))?;
181
182 let path = path.to_u32_vec();
183
184 if path.len() as u32 != BIP84_PATH_LEN {
185 return Err(SignPsbtError::InvalidPath(input_index as u32));
186 }
187
188 let sub_path = BIP84_HARDENED_SUBPATH.map(|i| path[i as usize]);
189 if sub_path.iter().any(|p| *p > BIP32_HARDENED_MASK) {
190 return Err(SignPsbtError::InvalidPath(input_index as u32));
191 }
192
193 let script = script_pubkey.as_script();
195 let sighash = sighash_cache
196 .p2wpkh_signature_hash(input_index, script, amount, EcdsaSighashType::All)
197 .map_err(|e| SignPsbtError::SighashError(e.to_string()))?;
198
199 let digest: &[u8; 32] = sighash.as_ref();
201
202 let mut sign_response = self.sign(*digest, sub_path.to_vec(), cvc).await?;
204 let mut signature_raw = sign_response.sig;
205
206 if sign_response.pubkey != psbt_pubkey.serialize() {
208 let path: Vec<u32> = path
211 .into_iter()
212 .map(|p| p ^ BIP32_HARDENED_MASK)
213 .take(BIP84_HARDENED_SUBPATH.len())
214 .collect();
215 let derive_response = self.derive(path, cvc).await;
216 if derive_response.is_err() {
217 return Err(SignPsbtError::PubkeyMismatch(input_index as u32));
218 }
219
220 sign_response = self.sign(*digest, sub_path.to_vec(), cvc).await?;
222 signature_raw = sign_response.sig;
223
224 if sign_response.pubkey != psbt_pubkey.serialize() {
226 return Err(SignPsbtError::PubkeyMismatch(input_index as u32));
227 }
228 }
229
230 let ecdsa_sig = ecdsa::Signature::from_compact(&signature_raw)
232 .map_err(|e| SignPsbtError::SignatureError(e.to_string()))?;
233
234 let final_sig = bitcoin::ecdsa::Signature::sighash_all(ecdsa_sig);
235 input.partial_sigs.insert((*psbt_pubkey).into(), final_sig);
236 }
237
238 Ok(psbt)
239 }
240
241 async fn derive(&mut self, path: Vec<u32>, cvc: &str) -> Result<PublicKey, DeriveError> {
250 let path = path.iter().map(|p| p ^ (1 << 31)).collect::<Vec<_>>();
252 let app_nonce = crate::rand_nonce();
253 let (_, epubkey, xcvc) = self.calc_ekeys_xcvc(cvc, DeriveCommand::name());
254 let cmd = DeriveCommand::for_tapsigner(app_nonce, path, epubkey, xcvc);
255 let derive_response: DeriveResponse = transmit(self.transport(), &cmd).await?;
256 self.set_card_nonce(derive_response.card_nonce);
257
258 let master_pubkey = PublicKey::from_slice(&derive_response.master_pubkey)?;
259 let pubkey = match &derive_response.pubkey {
260 Some(pubkey) => PublicKey::from_slice(pubkey)?,
261 None => master_pubkey,
262 };
263
264 if pubkey == master_pubkey {
266 let card_nonce = self.card_nonce();
267 let sig = &derive_response.sig;
268
269 let mut message_bytes: Vec<u8> = Vec::new();
270 message_bytes.extend("OPENDIME".as_bytes());
271 message_bytes.extend(card_nonce);
272 message_bytes.extend(app_nonce);
273 message_bytes.extend(&derive_response.chain_code);
274
275 let message_bytes_hash = sha256::Hash::hash(message_bytes.as_slice());
276 let message = Message::from_digest(message_bytes_hash.to_byte_array());
277
278 let signature = Signature::from_compact(sig)?;
279
280 self.secp()
281 .verify_ecdsa(&message, &signature, &master_pubkey.inner)?;
282 }
283 Ok(pubkey)
284 }
285
286 async fn change(&mut self, new_cvc: &str, cvc: &str) -> Result<(), ChangeError> {
288 if new_cvc.len() < 6 {
289 return Err(ChangeError::TooShort(new_cvc.len() as u32));
290 }
291
292 if new_cvc.len() > 32 {
293 return Err(ChangeError::TooLong(new_cvc.len() as u32));
294 }
295
296 if new_cvc == cvc {
297 return Err(ChangeError::SameAsOld);
298 }
299
300 let (eprivkey, epubkey, xcvc) = self.calc_ekeys_xcvc(cvc, ChangeCommand::name());
302
303 let session_key = secp256k1::ecdh::SharedSecret::new(&self.pubkey().inner, &eprivkey);
305
306 let xnew_cvc: Vec<u8> = session_key
308 .as_ref()
309 .iter()
310 .zip(new_cvc.as_bytes().iter())
311 .map(|(session_key_byte, cvc_byte)| session_key_byte ^ cvc_byte)
312 .collect();
313
314 let change_command = ChangeCommand::new(xnew_cvc, epubkey, xcvc);
315 let change_response: ChangeResponse = transmit(self.transport(), &change_command).await?;
316
317 self.set_card_nonce(change_response.card_nonce);
318 Ok(())
319 }
320
321 async fn xpub(&mut self, master: bool, cvc: &str) -> Result<Xpub, XpubError> {
322 let (_, epubkey, xcvc) = self.calc_ekeys_xcvc(cvc, XpubCommand::name());
323 let xpub_command = XpubCommand::new(master, epubkey, xcvc);
324 let xpub_response: XpubResponse = transmit(self.transport(), &xpub_command).await?;
325 self.set_card_nonce(xpub_response.card_nonce);
326 let xpub = Xpub::decode(xpub_response.xpub.as_slice())?;
327 Ok(xpub)
328 }
329}
330
331#[async_trait]
332impl Nfc for TapSigner {}
333
334impl TapSignerShared for TapSigner {}
335
336impl TapSigner {
337 pub fn card_ident(&self) -> String {
338 card_pubkey_to_ident(&self.pubkey)
339 }
340
341 pub fn try_from_status(
342 transport: Arc<dyn CkTransport>,
343 status_response: StatusResponse,
344 ) -> Result<Self, StatusError> {
345 let pubkey = status_response.pubkey.as_slice();
346 let pubkey = PublicKey::from_slice(pubkey)?;
347
348 Ok(TapSigner {
349 transport,
350 secp: Secp256k1::new(),
351 proto: status_response.proto,
352 ver: status_response.ver,
353 birth: status_response.birth,
354 path: status_response.path,
355 num_backups: status_response.num_backups,
356 pubkey,
357 card_nonce: status_response.card_nonce,
358 auth_delay: status_response.auth_delay,
359 })
360 }
361
362 pub async fn backup(&mut self, cvc: &str) -> Result<Vec<u8>, ChangeError> {
364 let (_, epubkey, xcvc) = self.calc_ekeys_xcvc(cvc, "backup");
365
366 let backup_command = BackupCommand::new(epubkey, xcvc);
367 let backup_response: BackupResponse = transmit(self.transport(), &backup_command).await?;
368
369 self.card_nonce = backup_response.card_nonce;
370 Ok(backup_response.data)
371 }
372}
373
374#[async_trait]
375impl Wait for TapSigner {}
376
377#[async_trait]
378impl Read for TapSigner {
379 fn requires_auth(&self) -> bool {
380 true
381 }
382
383 fn slot(&self) -> Option<u8> {
384 None
385 }
386}
387
388#[async_trait]
389impl Certificate for TapSigner {
390 async fn slot_pubkey(&mut self) -> Result<Option<PublicKey>, ReadError> {
391 Ok(None)
392 }
393}
394
395impl core::fmt::Debug for TapSigner {
396 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
397 f.debug_struct("TapSigner")
398 .field("proto", &self.proto)
399 .field("ver", &self.ver)
400 .field("birth", &self.birth)
401 .field("path", &self.path)
402 .field("num_backups", &self.num_backups)
403 .field("card_ident", &self.card_ident())
404 .field("pubkey", &self.pubkey)
405 .field("card_nonce", &self.card_nonce.to_lower_hex_string())
406 .field("auth_delay", &self.auth_delay)
407 .finish()
408 }
409}
410
411#[cfg(feature = "emulator")]
412#[cfg(test)]
413mod test {
414 use crate::emulator::find_emulator;
415 use crate::emulator::test::{CardTypeOption, EcardSubprocess};
416 use crate::tap_signer::TapSignerShared;
417 use crate::{CkTapCard, rand_chaincode};
418 use std::path::Path;
419
420 #[tokio::test]
422 async fn test_tap_signer_xpub() {
423 let card_type = CardTypeOption::TapSigner;
424 let pipe_path = "/tmp/test-tapsigner-xpub-pipe";
425 let pipe_path = Path::new(&pipe_path);
426 let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
427 let emulator = find_emulator(pipe_path).await.unwrap();
428 if let CkTapCard::TapSigner(mut ts) = emulator {
429 ts.init(rand_chaincode(), "123456").await.unwrap();
430 let xpub = ts.xpub(false, "123456").await.unwrap();
431 assert_eq!(xpub.depth, 3);
432 let master_xpub = ts.xpub(true, "123456").await.unwrap();
433 assert_eq!(master_xpub.depth, 0);
434 }
435 drop(python);
436 }
437}