1use crate::{CardError, CkTapCard, CkTapError, SatsCard, TapSigner};
5use crate::{apdu::*, rand_nonce};
6
7use bitcoin::key::{PublicKey, rand};
8use bitcoin::secp256k1;
9use bitcoin::secp256k1::ecdh::SharedSecret;
10use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId, Signature};
11use bitcoin::secp256k1::{All, Message, Secp256k1};
12use bitcoin_hashes::sha256;
13use data_encoding::BASE32_NOPAD;
14
15use crate::error::{CertsError, ReadError, StatusError};
16use crate::sats_chip::SatsChip;
17use async_trait::async_trait;
18use bitcoin_hashes::hex::DisplayHex;
19use std::convert::TryFrom;
20use std::fmt;
21use std::fmt::Debug;
22use std::sync::Arc;
23
24const PUB_FACTORY_ROOT_KEY: &str =
26 "03028a0e89e70d0ec0d932053a89ab1da7d9182bdc6d2f03e706ee99517d05d9e1";
27const DEV_FACTORY_ROOT_KEY: &str =
29 "027722ef208e681bac05f1b4b3cc478d6bf353ac9a09ff0c843430138f65c27bab";
30
31pub enum FactoryRootKey {
32 Pub(secp256k1::PublicKey),
33 Dev(secp256k1::PublicKey),
34}
35
36impl TryFrom<secp256k1::PublicKey> for FactoryRootKey {
37 type Error = CertsError;
38
39 fn try_from(pubkey: secp256k1::PublicKey) -> Result<Self, CertsError> {
40 match pubkey.serialize().to_lower_hex_string().as_str() {
41 PUB_FACTORY_ROOT_KEY => Ok(FactoryRootKey::Pub(pubkey)),
42 DEV_FACTORY_ROOT_KEY => Ok(FactoryRootKey::Dev(pubkey)),
43 _ => Err(CertsError::InvalidRootCert(
44 pubkey.serialize().to_lower_hex_string(),
45 )),
46 }
47 }
48}
49
50impl FactoryRootKey {
51 pub const fn is_production(&self) -> bool {
53 matches!(self, Self::Pub(_))
54 }
55
56 pub fn name(&self) -> String {
57 match &self {
58 FactoryRootKey::Pub(_) => "Root Factory Certificate".to_string(),
59 FactoryRootKey::Dev(_) => "Root Factory Certificate (TESTING ONLY)".to_string(),
60 }
61 }
62}
63
64impl Debug for FactoryRootKey {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match &self {
67 FactoryRootKey::Pub(pk) => {
68 write!(f, "FactoryRootKey::Pub({pk:?})")
69 }
70 FactoryRootKey::Dev(pk) => {
71 write!(f, "FactoryRootKey::Dev({pk:?})")
72 }
73 }
74 }
75}
76
77pub trait Authentication {
79 fn secp(&self) -> &Secp256k1<All>;
80 fn ver(&self) -> &str;
81 fn pubkey(&self) -> &PublicKey;
82 fn card_nonce(&self) -> &[u8; 16];
83 fn set_card_nonce(&mut self, new_nonce: [u8; 16]);
84 fn auth_delay(&self) -> Option<u8>;
85 fn set_auth_delay(&mut self, auth_delay: Option<u8>);
86 fn transport(&self) -> Arc<dyn CkTransport>;
87 fn card_ident(&self) -> String {
88 card_pubkey_to_ident(self.pubkey())
89 }
90
91 fn calc_ekeys_xcvc(
94 &self,
95 cvc: &str,
96 command: &str,
97 ) -> (secp256k1::SecretKey, secp256k1::PublicKey, Vec<u8>) {
98 let secp = Self::secp(self);
99 let pubkey = Self::pubkey(self);
100 let nonce = Self::card_nonce(self);
101 let cvc_bytes = cvc.as_bytes();
102 let card_nonce_command = [nonce, command.as_bytes()].concat();
103 let (ephemeral_private_key, ephemeral_public_key) =
104 secp.generate_keypair(&mut rand::thread_rng());
105
106 let session_key = SharedSecret::new(&pubkey.inner, &ephemeral_private_key);
107 let md = sha256::Hash::hash(card_nonce_command.as_slice());
108 let md: &[u8; 32] = md.as_ref();
109
110 let mask: Vec<u8> = session_key
111 .as_ref()
112 .iter()
113 .zip(md)
114 .map(|(x, y)| x ^ y)
115 .take(cvc_bytes.len())
116 .collect();
117
118 let xcvc = cvc_bytes.iter().zip(mask).map(|(x, y)| x ^ y).collect();
119 (ephemeral_private_key, ephemeral_public_key, xcvc)
120 }
121}
122
123pub fn card_pubkey_to_ident(pubkey: &PublicKey) -> String {
125 let serialized = pubkey.inner.serialize();
126 let digest = sha256::Hash::hash(&serialized);
127 let digest_bytes = digest.to_byte_array();
128 let encoded = BASE32_NOPAD.encode(&digest_bytes[8..]);
129 let ident = &encoded[..20];
130
131 [&ident[0..5], &ident[5..10], &ident[10..15], &ident[15..20]].join("-")
132}
133
134#[cfg(test)]
135mod card_ident_tests {
136 use super::*;
137 use bitcoin::hex::FromHex;
138
139 #[test]
140 fn converts_pubkey_to_ident() {
141 let pubkey_bytes = Vec::<u8>::from_hex(
142 "0312d005ca1501b1603c3b00412eefe27c6b20a74c29377263b357b3aff12de6fa",
143 )
144 .expect("valid pubkey hex");
145 let pubkey = PublicKey::from_slice(&pubkey_bytes).expect("compressed pubkey");
146 assert_eq!(card_pubkey_to_ident(&pubkey), "IYKC5-XN6ZN-3AGAA-BWABB");
147 }
148}
149
150#[async_trait]
152pub trait CkTransport: Sync + Send {
153 async fn transmit_apdu(&self, command_apdu: Vec<u8>) -> Result<Vec<u8>, CkTapError>;
154}
155
156pub(crate) async fn transmit<C, R>(
158 transport: Arc<dyn CkTransport>,
159 command: &C,
160) -> Result<R, CkTapError>
161where
162 C: CommandApdu + serde::Serialize + Debug + Send + Sync,
163 R: ResponseApdu + serde::de::DeserializeOwned + Debug + Send,
164{
165 let command_apdu = command.apdu_bytes();
166 let rapdu = transport.transmit_apdu(command_apdu).await?;
167 let response = R::from_cbor(rapdu.to_vec())?;
168 Ok(response)
169}
170
171pub async fn to_cktap(transport: Arc<dyn CkTransport>) -> Result<CkTapCard, StatusError> {
172 let cmd = AppletSelect::default();
174 let status_response: StatusResponse = transmit(transport.clone(), &cmd).await?;
175
176 match (status_response.tapsigner, status_response.satschip) {
178 (Some(true), None) => {
179 let tap_signer = TapSigner::try_from_status(transport, status_response)?;
180 Ok(CkTapCard::TapSigner(tap_signer))
181 }
182 (Some(true), Some(true)) => {
183 let sats_chip = SatsChip::try_from_status(transport, status_response)?;
184 Ok(CkTapCard::SatsChip(sats_chip))
185 }
186 (None, None) => {
187 let sats_card = SatsCard::from_status(transport, status_response)?;
188 Ok(CkTapCard::SatsCard(sats_card))
189 }
190 (_, _) => Err(StatusError::CkTap(CkTapError::UnknownCardType)),
191 }
192}
193
194#[async_trait]
198pub trait Read: Authentication {
199 fn requires_auth(&self) -> bool;
200
201 fn slot(&self) -> Option<u8>;
202
203 async fn read(&mut self, cvc: Option<String>) -> Result<PublicKey, ReadError> {
204 let card_nonce = *self.card_nonce();
205 let app_nonce = rand_nonce();
206
207 let mut message_bytes: Vec<u8> = Vec::new();
209 message_bytes.extend("OPENDIME".as_bytes());
210 message_bytes.extend(card_nonce);
211 message_bytes.extend(app_nonce);
212 if let Some(slot) = self.slot() {
213 message_bytes.push(slot);
214 } else {
215 message_bytes.push(0);
216 }
217 let hash = sha256::Hash::hash(message_bytes.as_slice());
218 let message_digest = Message::from_digest(hash.to_byte_array());
219
220 let (cmd, session_key) = if self.requires_auth() {
222 let cvc = cvc.ok_or(CkTapError::Card(CardError::NeedsAuth))?;
223 let (eprivkey, epubkey, xcvc) = self.calc_ekeys_xcvc(&cvc, ReadCommand::name());
224 (
225 ReadCommand::authenticated(app_nonce, epubkey, xcvc),
226 Some(SharedSecret::new(&self.pubkey().inner, &eprivkey)),
227 )
228 } else {
229 (ReadCommand::unauthenticated(app_nonce), None)
230 };
231
232 let read_response: ReadResponse = transmit(self.transport(), &cmd).await?;
234
235 let pubkey = read_response.pubkey(session_key)?;
237 self.secp().verify_ecdsa(
238 &message_digest,
239 &read_response.signature()?, &pubkey.inner,
241 )?;
242
243 self.set_card_nonce(read_response.card_nonce);
245
246 Ok(pubkey)
247 }
248}
249
250#[async_trait]
251pub trait Wait: Authentication {
252 async fn wait(&mut self, cvc: Option<String>) -> Result<Option<u8>, CkTapError> {
253 let epubkey_xcvc = cvc.map(|cvc| {
254 let (_, epubkey, xcvc) = self.calc_ekeys_xcvc(&cvc, WaitCommand::name());
255 (epubkey, xcvc)
256 });
257
258 let (epubkey, xcvc) = epubkey_xcvc
259 .map(|(epubkey, xcvc)| (Some(epubkey), Some(xcvc)))
260 .unwrap_or((None, None));
261
262 let wait_command = WaitCommand::new(epubkey, xcvc);
263
264 let wait_response: WaitResponse = transmit(self.transport(), &wait_command).await?;
265 if wait_response.auth_delay > 0 {
267 let auth_delay = Some(wait_response.auth_delay);
268 self.set_auth_delay(auth_delay);
269 Ok(auth_delay)
270 } else {
271 self.set_auth_delay(None);
272 Ok(None)
273 }
274 }
275}
276
277#[async_trait]
278pub trait Certificate: Read {
279 async fn check_production_certificate(&mut self) -> Result<(), CertsError> {
281 let root = self.check_certificate().await?;
282
283 if root.is_production() {
284 return Ok(());
285 }
286
287 Err(CertsError::InvalidRootCert(
288 "development factory root".to_string(),
289 ))
290 }
291
292 async fn check_certificate(&mut self) -> Result<FactoryRootKey, CertsError> {
293 let app_nonce = rand_nonce();
294 let card_nonce = *self.card_nonce();
295
296 let certs_cmd = CertsCommand::default();
297 let certs_response: CertsResponse = transmit(self.transport(), &certs_cmd).await?;
298 let cert_chain = CertificateChain::try_from(certs_response.cert_chain())?;
299
300 let check_cmd = CheckCommand::new(app_nonce);
301 let check_response: CheckResponse = transmit(self.transport(), &check_cmd).await?;
302 self.set_card_nonce(check_response.card_nonce);
303
304 let signature = parse_auth_signature(&check_response.auth_sig)?;
305
306 let slot_pubkey = self.slot_pubkey().await?;
307
308 let mut message_bytes: Vec<u8> = Vec::new();
310 message_bytes.extend("OPENDIME".as_bytes());
311 message_bytes.extend(card_nonce);
312 message_bytes.extend(app_nonce);
313 if let Some(pubkey) = slot_pubkey {
314 if self.ver() != "0.9.0" {
315 let slot_pubkey_bytes = pubkey.inner.serialize();
316 message_bytes.extend(slot_pubkey_bytes);
317 }
318 }
319 let message_bytes_hash = sha256::Hash::hash(message_bytes.as_slice());
320 let message = Message::from_digest(message_bytes_hash.to_byte_array());
321
322 self.secp()
324 .verify_ecdsa(&message, &signature, &self.pubkey().inner)?;
325
326 let root_pubkey = cert_chain.recover_root(self.secp(), *self.pubkey())?;
327 FactoryRootKey::try_from(root_pubkey.inner)
328 }
329
330 async fn slot_pubkey(&mut self) -> Result<Option<PublicKey>, ReadError>;
331}
332
333#[derive(Debug)]
334struct CertificateChain(Vec<RecoverableSignature>);
335
336impl TryFrom<Vec<Vec<u8>>> for CertificateChain {
337 type Error = CertsError;
338
339 fn try_from(certificates: Vec<Vec<u8>>) -> Result<Self, Self::Error> {
340 if certificates.is_empty() {
341 return Err(bitcoin::secp256k1::Error::InvalidSignature.into());
342 }
343
344 let signatures = certificates
345 .into_iter()
346 .map(|certificate| parse_certificate_signature(&certificate))
347 .collect::<Result<_, _>>()?;
348
349 Ok(Self(signatures))
350 }
351}
352
353impl CertificateChain {
354 fn recover_root(
355 &self,
356 secp: &Secp256k1<All>,
357 card_pubkey: PublicKey,
358 ) -> Result<PublicKey, CertsError> {
359 self.0.iter().try_fold(card_pubkey, |pubkey, signature| {
360 let pubkey_hash = sha256::Hash::hash(&pubkey.inner.serialize());
361 let message = Message::from_digest(pubkey_hash.to_byte_array());
362 let recovered = secp.recover_ecdsa(&message, signature)?;
363
364 Ok(PublicKey::new(recovered))
365 })
366 }
367}
368
369fn parse_auth_signature(signature: &[u8]) -> Result<Signature, CertsError> {
370 Ok(Signature::from_compact(signature)?)
371}
372
373fn parse_certificate_signature(certificate: &[u8]) -> Result<RecoverableSignature, CertsError> {
374 let (header, compact_signature) = certificate
375 .split_first()
376 .ok_or(bitcoin::secp256k1::Error::InvalidSignature)?;
377
378 let recovery_id = match header {
380 27..=30 => header - 27, 31..=34 => header - 31, 35..=38 => header - 35, 39..=42 => header - 39, _ => return Err(bitcoin::secp256k1::Error::InvalidRecoveryId.into()),
385 };
386 let recovery_id = RecoveryId::from_i32(i32::from(recovery_id))?;
387
388 Ok(RecoverableSignature::from_compact(
389 compact_signature,
390 recovery_id,
391 )?)
392}
393
394#[cfg(test)]
395mod certificate_tests {
396 use super::*;
397 use std::collections::VecDeque;
398 use std::sync::Mutex;
399
400 #[derive(serde::Serialize)]
401 struct CertsResponseBody {
402 cert_chain: Vec<serde_bytes::ByteBuf>,
403 }
404
405 #[derive(serde::Serialize)]
406 struct CheckResponseBody {
407 #[serde(with = "serde_bytes")]
408 auth_sig: Vec<u8>,
409 #[serde(with = "serde_bytes")]
410 card_nonce: [u8; 16],
411 }
412
413 struct QueuedTransport(Mutex<VecDeque<Vec<u8>>>);
414
415 #[async_trait]
416 impl CkTransport for QueuedTransport {
417 async fn transmit_apdu(&self, _command_apdu: Vec<u8>) -> Result<Vec<u8>, CkTapError> {
418 self.0
419 .lock()
420 .expect("response queue lock")
421 .pop_front()
422 .ok_or_else(|| CkTapError::Transport("response queue is empty".to_string()))
423 }
424 }
425
426 fn serialize_response(response: &impl serde::Serialize) -> Vec<u8> {
427 let mut bytes = Vec::new();
428 ciborium::ser::into_writer(response, &mut bytes).expect("serializable response");
429 bytes
430 }
431
432 fn certificate_signature(secp: &Secp256k1<All>) -> Vec<u8> {
433 let secret_key = secp256k1::SecretKey::from_slice(&[1; 32]).expect("valid secret key");
434 let message = Message::from_digest([2; 32]);
435 let signature = secp.sign_ecdsa_recoverable(&message, &secret_key);
436 let (recovery_id, compact_signature) = signature.serialize_compact();
437 let mut certificate = Vec::with_capacity(65);
438 certificate.push(27 + recovery_id.to_i32() as u8);
439 certificate.extend(compact_signature);
440 certificate
441 }
442
443 #[test]
444 fn rejects_invalid_auth_signature_length() {
445 let error = parse_auth_signature(&[]).unwrap_err();
446 assert!(matches!(
447 error,
448 CertsError::Secp256k1(bitcoin::secp256k1::Error::InvalidSignature)
449 ));
450 }
451
452 #[tokio::test]
453 async fn stores_response_nonce_before_parsing_auth_signature() {
454 let initial_nonce = [3; 16];
455 let response_nonce = [4; 16];
456 let secp = Secp256k1::new();
457 let secret_key = secp256k1::SecretKey::from_slice(&[1; 32]).expect("valid secret key");
458 let pubkey = PublicKey::new(secret_key.public_key(&secp));
459 let responses = VecDeque::from([
460 serialize_response(&CertsResponseBody {
461 cert_chain: vec![serde_bytes::ByteBuf::from(certificate_signature(&secp))],
462 }),
463 serialize_response(&CheckResponseBody {
464 auth_sig: Vec::new(),
465 card_nonce: response_nonce,
466 }),
467 ]);
468 let transport = Arc::new(QueuedTransport(Mutex::new(responses)));
469 let mut card = SatsCard {
470 transport,
471 secp,
472 proto: 1,
473 ver: "1.0.0".to_string(),
474 birth: 0,
475 slots: (0, 10),
476 addr: None,
477 pubkey,
478 card_nonce: initial_nonce,
479 auth_delay: None,
480 };
481
482 let error = card.check_certificate().await.unwrap_err();
483
484 assert!(matches!(
485 error,
486 CertsError::Secp256k1(bitcoin::secp256k1::Error::InvalidSignature)
487 ));
488 assert_eq!(card.card_nonce, response_nonce);
489 }
490
491 #[test]
492 fn rejects_empty_certificate_chain() {
493 let error = CertificateChain::try_from(Vec::new()).unwrap_err();
494 assert!(matches!(
495 error,
496 CertsError::Secp256k1(bitcoin::secp256k1::Error::InvalidSignature)
497 ));
498 }
499
500 #[test]
501 fn rejects_empty_certificate_signature() {
502 let error = parse_certificate_signature(&[]).unwrap_err();
503 assert!(matches!(
504 error,
505 CertsError::Secp256k1(bitcoin::secp256k1::Error::InvalidSignature)
506 ));
507 }
508
509 #[test]
510 fn rejects_invalid_certificate_header() {
511 let error = parse_certificate_signature(&[0; 65]).unwrap_err();
512 assert!(matches!(
513 error,
514 CertsError::Secp256k1(bitcoin::secp256k1::Error::InvalidRecoveryId)
515 ));
516 }
517
518 #[test]
519 fn rejects_invalid_certificate_signature_length() {
520 let error = parse_certificate_signature(&[31; 64]).unwrap_err();
521 assert!(matches!(
522 error,
523 CertsError::Secp256k1(bitcoin::secp256k1::Error::InvalidSignature)
524 ));
525 }
526}
527
528#[async_trait]
529pub trait Nfc: Authentication {
530 async fn nfc(&mut self) -> Result<String, CkTapError> {
531 let nfc_cmd = NfcCommand::default();
532 let nfc_response: NfcResponse = transmit(self.transport(), &nfc_cmd).await?;
533 Ok(nfc_response.url)
534 }
535}
536
537#[cfg(feature = "emulator")]
538#[cfg(test)]
539mod tests {
540 use super::*;
541 use std::path::Path;
542
543 use crate::emulator::CVC;
544 use crate::emulator::find_emulator;
545 use crate::emulator::test::{CardTypeOption, EcardSubprocess};
546 use crate::rand_chaincode;
547 use crate::tap_signer::TapSignerShared;
548
549 #[tokio::test]
550 async fn test_new_command() {
551 let chain_code = rand_chaincode();
552 for card_type in CardTypeOption::values() {
553 let pipe_path = format!("/tmp/test-new-command-pipe{card_type}");
554 let pipe_path = Path::new(&pipe_path);
555 let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
556 let emulator = find_emulator(pipe_path).await.unwrap();
557 match emulator {
558 CkTapCard::SatsCard(mut sc) => {
559 assert_eq!(card_type, CardTypeOption::SatsCard);
560 let current_slot = sc.slots.0;
561 let response = sc.unseal(current_slot, CVC).await;
562 assert!(response.is_ok());
563 let response = sc.new_slot(current_slot + 1, Some(chain_code), CVC).await;
564 assert!(response.is_ok());
565 assert_eq!(sc.slots.0, current_slot + 1);
566 let current_slot = sc.slots.0;
568 let response = sc.unseal(current_slot, CVC).await;
569 assert!(response.is_ok());
570 let response = sc.new_slot(current_slot + 1, None, CVC).await;
571 assert!(response.is_ok());
572 assert_eq!(sc.slots.0, current_slot + 1);
573 }
574 CkTapCard::TapSigner(mut ts) => {
575 assert_eq!(card_type, CardTypeOption::TapSigner);
576 let response = ts.init(chain_code, CVC).await;
577 assert!(response.is_ok())
578 }
579 CkTapCard::SatsChip(mut sc) => {
580 assert_eq!(card_type, CardTypeOption::SatsChip);
581 let response = sc.init(chain_code, CVC).await;
582 assert!(response.is_ok())
583 }
584 };
585 drop(python);
586 }
587 }
588
589 #[tokio::test]
590 async fn test_cert_command() {
591 for card_type in CardTypeOption::values() {
592 let emulator_root_pubkey =
593 "0312d005ca1501b1603c3b00412eefe27c6b20a74c29377263b357b3aff12de6fa".to_string();
594 let pipe_path = format!("/tmp/test-cert-command-pipe{card_type}");
595 let pipe_path = Path::new(&pipe_path);
596 let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
597 let emulator = find_emulator(pipe_path).await.unwrap();
598 match emulator {
599 CkTapCard::SatsCard(mut sc) => {
600 assert_eq!(card_type, CardTypeOption::SatsCard);
601 let response = sc.check_certificate().await;
602 assert!(response.is_err());
603 matches!(response, Err(CertsError::InvalidRootCert(pubkey)) if pubkey == emulator_root_pubkey);
604 }
605 CkTapCard::TapSigner(mut ts) => {
606 assert_eq!(card_type, CardTypeOption::TapSigner);
607 let response = ts.check_certificate().await;
608 assert!(response.is_err());
609 matches!(response, Err(CertsError::InvalidRootCert(pubkey)) if pubkey == emulator_root_pubkey);
610 }
611 CkTapCard::SatsChip(mut sc) => {
612 assert_eq!(card_type, CardTypeOption::SatsChip);
613 let response = sc.check_certificate().await;
614 assert!(response.is_err());
615 matches!(response, Err(CertsError::InvalidRootCert(pubkey)) if pubkey == emulator_root_pubkey);
616 }
617 };
618 drop(python);
619 }
620 }
621
622 #[tokio::test]
623 async fn test_nfc_command() {
624 for card_type in CardTypeOption::values() {
625 let pipe_path = format!("/tmp/test-nfc-command-pipe{card_type}");
626 let pipe_path = Path::new(&pipe_path);
627 let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
628 let emulator = find_emulator(pipe_path).await.unwrap();
629 match emulator {
630 CkTapCard::SatsCard(mut sc) => {
631 assert_eq!(card_type, CardTypeOption::SatsCard);
632 let response = sc.nfc().await;
633 assert!(response.is_ok());
634 assert!(
635 response
636 .unwrap()
637 .starts_with("https://getsatscard.com/start")
638 )
639 }
640 CkTapCard::TapSigner(mut ts) => {
641 assert_eq!(card_type, CardTypeOption::TapSigner);
642 let response = ts.nfc().await;
643 assert!(response.is_ok());
644 assert!(response.unwrap().starts_with("https://tapsigner.com/start"))
645 }
646 CkTapCard::SatsChip(mut sc) => {
647 assert_eq!(card_type, CardTypeOption::SatsChip);
648 let response = sc.nfc().await;
649 assert!(response.is_ok());
650 assert!(response.unwrap().starts_with("https://satschip.com/start"))
651 }
652 };
653 drop(python);
654 }
655 }
656}