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;
13
14use crate::error::{CertsError, ReadError, StatusError};
15use crate::sats_chip::SatsChip;
16use async_trait::async_trait;
17use bitcoin_hashes::hex::DisplayHex;
18use std::convert::TryFrom;
19use std::fmt;
20use std::fmt::Debug;
21use std::sync::Arc;
22
23const PUB_FACTORY_ROOT_KEY: &str =
25 "03028a0e89e70d0ec0d932053a89ab1da7d9182bdc6d2f03e706ee99517d05d9e1";
26const DEV_FACTORY_ROOT_KEY: &str =
28 "027722ef208e681bac05f1b4b3cc478d6bf353ac9a09ff0c843430138f65c27bab";
29
30pub enum FactoryRootKey {
31 Pub(secp256k1::PublicKey),
32 Dev(secp256k1::PublicKey),
33}
34
35impl TryFrom<secp256k1::PublicKey> for FactoryRootKey {
36 type Error = CertsError;
37
38 fn try_from(pubkey: secp256k1::PublicKey) -> Result<Self, CertsError> {
39 match pubkey.serialize().to_lower_hex_string().as_str() {
40 PUB_FACTORY_ROOT_KEY => Ok(FactoryRootKey::Pub(pubkey)),
41 DEV_FACTORY_ROOT_KEY => Ok(FactoryRootKey::Dev(pubkey)),
42 _ => Err(CertsError::InvalidRootCert(
43 pubkey.serialize().to_lower_hex_string(),
44 )),
45 }
46 }
47}
48
49impl FactoryRootKey {
50 pub fn name(&self) -> String {
51 match &self {
52 FactoryRootKey::Pub(_) => "Root Factory Certificate".to_string(),
53 FactoryRootKey::Dev(_) => "Root Factory Certificate (TESTING ONLY)".to_string(),
54 }
55 }
56}
57
58impl Debug for FactoryRootKey {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match &self {
61 FactoryRootKey::Pub(pk) => {
62 write!(f, "FactoryRootKey::Pub({pk:?})")
63 }
64 FactoryRootKey::Dev(pk) => {
65 write!(f, "FactoryRootKey::Dev({pk:?})")
66 }
67 }
68 }
69}
70
71pub trait Authentication {
73 fn secp(&self) -> &Secp256k1<All>;
74 fn ver(&self) -> &str;
75 fn pubkey(&self) -> &PublicKey;
76 fn card_nonce(&self) -> &[u8; 16];
77 fn set_card_nonce(&mut self, new_nonce: [u8; 16]);
78 fn auth_delay(&self) -> &Option<usize>;
79 fn set_auth_delay(&mut self, auth_delay: Option<usize>);
80 fn transport(&self) -> Arc<dyn CkTransport>;
81
82 fn calc_ekeys_xcvc(
85 &self,
86 cvc: &str,
87 command: &str,
88 ) -> (secp256k1::SecretKey, secp256k1::PublicKey, Vec<u8>) {
89 let secp = Self::secp(self);
90 let pubkey = Self::pubkey(self);
91 let nonce = Self::card_nonce(self);
92 let cvc_bytes = cvc.as_bytes();
93 let card_nonce_command = [nonce, command.as_bytes()].concat();
94 let (ephemeral_private_key, ephemeral_public_key) =
95 secp.generate_keypair(&mut rand::thread_rng());
96
97 let session_key = SharedSecret::new(&pubkey.inner, &ephemeral_private_key);
98 let md = sha256::Hash::hash(card_nonce_command.as_slice());
99 let md: &[u8; 32] = md.as_ref();
100
101 let mask: Vec<u8> = session_key
102 .as_ref()
103 .iter()
104 .zip(md)
105 .map(|(x, y)| x ^ y)
106 .take(cvc_bytes.len())
107 .collect();
108
109 let xcvc = cvc_bytes.iter().zip(mask).map(|(x, y)| x ^ y).collect();
110 (ephemeral_private_key, ephemeral_public_key, xcvc)
111 }
112}
113
114#[async_trait]
116pub trait CkTransport: Sync + Send {
117 async fn transmit_apdu(&self, command_apdu: Vec<u8>) -> Result<Vec<u8>, CkTapError>;
118}
119
120pub(crate) async fn transmit<C, R>(
122 transport: Arc<dyn CkTransport>,
123 command: &C,
124) -> Result<R, CkTapError>
125where
126 C: CommandApdu + serde::Serialize + Debug + Send + Sync,
127 R: ResponseApdu + serde::de::DeserializeOwned + Debug + Send,
128{
129 let command_apdu = command.apdu_bytes();
130 let rapdu = transport.transmit_apdu(command_apdu).await?;
131 let response = R::from_cbor(rapdu.to_vec())?;
132 Ok(response)
133}
134
135pub async fn to_cktap(transport: Arc<dyn CkTransport>) -> Result<CkTapCard, StatusError> {
136 let cmd = AppletSelect::default();
138 let status_response: StatusResponse = transmit(transport.clone(), &cmd).await?;
139
140 match (status_response.tapsigner, status_response.satschip) {
142 (Some(true), None) => {
143 let tap_signer = TapSigner::try_from_status(transport, status_response)?;
144 Ok(CkTapCard::TapSigner(tap_signer))
145 }
146 (Some(true), Some(true)) => {
147 let sats_chip = SatsChip::try_from_status(transport, status_response)?;
148 Ok(CkTapCard::SatsChip(sats_chip))
149 }
150 (None, None) => {
151 let sats_card = SatsCard::from_status(transport, status_response)?;
152 Ok(CkTapCard::SatsCard(sats_card))
153 }
154 (_, _) => Err(StatusError::CkTap(CkTapError::UnknownCardType)),
155 }
156}
157
158#[async_trait]
162pub trait Read: Authentication {
163 fn requires_auth(&self) -> bool;
164
165 fn slot(&self) -> Option<u8>;
166
167 async fn read(&mut self, cvc: Option<String>) -> Result<PublicKey, ReadError> {
168 let card_nonce = *self.card_nonce();
169 let app_nonce = rand_nonce();
170
171 let mut message_bytes: Vec<u8> = Vec::new();
173 message_bytes.extend("OPENDIME".as_bytes());
174 message_bytes.extend(card_nonce);
175 message_bytes.extend(app_nonce);
176 if let Some(slot) = self.slot() {
177 message_bytes.push(slot);
178 } else {
179 message_bytes.push(0);
180 }
181 let hash = sha256::Hash::hash(message_bytes.as_slice());
182 let message_digest = Message::from_digest(hash.to_byte_array());
183
184 let (cmd, session_key) = if self.requires_auth() {
186 let cvc = cvc.ok_or(CkTapError::Card(CardError::NeedsAuth))?;
187 let (eprivkey, epubkey, xcvc) = self.calc_ekeys_xcvc(&cvc, ReadCommand::name());
188 (
189 ReadCommand::authenticated(app_nonce, epubkey, xcvc),
190 Some(SharedSecret::new(&self.pubkey().inner, &eprivkey)),
191 )
192 } else {
193 (ReadCommand::unauthenticated(app_nonce), None)
194 };
195
196 let read_response: ReadResponse = transmit(self.transport(), &cmd).await?;
198
199 let pubkey = read_response.pubkey(session_key)?;
201 self.secp().verify_ecdsa(
202 &message_digest,
203 &read_response.signature()?, &pubkey.inner,
205 )?;
206
207 self.set_card_nonce(read_response.card_nonce);
209
210 Ok(pubkey)
211 }
212}
213
214#[async_trait]
215pub trait Wait: Authentication {
216 async fn wait(&mut self, cvc: Option<String>) -> Result<Option<usize>, CkTapError> {
217 let epubkey_xcvc = cvc.map(|cvc| {
218 let (_, epubkey, xcvc) = self.calc_ekeys_xcvc(&cvc, WaitCommand::name());
219 (epubkey, xcvc)
220 });
221
222 let (epubkey, xcvc) = epubkey_xcvc
223 .map(|(epubkey, xcvc)| (Some(epubkey), Some(xcvc)))
224 .unwrap_or((None, None));
225
226 let wait_command = WaitCommand::new(epubkey, xcvc);
227
228 let wait_response: WaitResponse = transmit(self.transport(), &wait_command).await?;
229 if wait_response.auth_delay > 0 {
231 let auth_delay = Some(wait_response.auth_delay);
232 self.set_auth_delay(auth_delay);
233 Ok(auth_delay)
234 } else {
235 self.set_auth_delay(None);
236 Ok(None)
237 }
238 }
239}
240
241#[async_trait]
242pub trait Certificate: Read {
243 async fn check_certificate(&mut self) -> Result<FactoryRootKey, CertsError> {
244 let app_nonce = rand_nonce();
245 let card_nonce = *self.card_nonce();
246
247 let certs_cmd = CertsCommand::default();
248 let certs_response: CertsResponse = transmit(self.transport(), &certs_cmd).await?;
249
250 let check_cmd = CheckCommand::new(app_nonce);
251 let check_response: CheckResponse = transmit(self.transport(), &check_cmd).await?;
252 self.set_card_nonce(check_response.card_nonce);
253
254 let slot_pubkey = self.slot_pubkey().await?;
255
256 let mut message_bytes: Vec<u8> = Vec::new();
258 message_bytes.extend("OPENDIME".as_bytes());
259 message_bytes.extend(card_nonce);
260 message_bytes.extend(app_nonce);
261 if let Some(pubkey) = slot_pubkey {
262 if self.ver() != "0.9.0" {
263 let slot_pubkey_bytes = pubkey.inner.serialize();
264 message_bytes.extend(slot_pubkey_bytes);
265 }
266 }
267 let message_bytes_hash = sha256::Hash::hash(message_bytes.as_slice());
268 let message = Message::from_digest(message_bytes_hash.to_byte_array());
269
270 let signature = Signature::from_compact(check_response.auth_sig.as_slice())
272 .expect("Failed to construct ECDSA signature from check response");
273 self.secp()
274 .verify_ecdsa(&message, &signature, &self.pubkey().inner)?;
275
276 let mut pubkey = *self.pubkey();
277 for sig in &certs_response.cert_chain() {
278 let subtract_by = match sig[0] {
280 27..=30 => 27, 31..=34 => 31, 35..=38 => 35, 39..=42 => 39, _ => panic!("Unrecognized BIP-137 address"),
285 };
286 let rec_id = RecoveryId::from_i32((sig[0] as i32) - subtract_by)?;
287 let (_, sig) = sig.split_at(1);
288 let result = RecoverableSignature::from_compact(sig, rec_id);
289 let rec_sig = result?;
290 let pubkey_hash = sha256::Hash::hash(&pubkey.inner.serialize());
291 let md = Message::from_digest(pubkey_hash.to_byte_array());
292 let result = self.secp().recover_ecdsa(&md, &rec_sig);
293 pubkey = PublicKey::new(result?);
294 }
295
296 FactoryRootKey::try_from(pubkey.inner)
297 }
298
299 async fn slot_pubkey(&mut self) -> Result<Option<PublicKey>, ReadError>;
300}
301
302#[async_trait]
303pub trait Nfc: Authentication {
304 async fn nfc(&mut self) -> Result<String, CkTapError> {
305 let nfc_cmd = NfcCommand::default();
306 let nfc_response: NfcResponse = transmit(self.transport(), &nfc_cmd).await?;
307 Ok(nfc_response.url)
308 }
309}
310
311#[cfg(feature = "emulator")]
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use std::path::Path;
316
317 use crate::emulator::CVC;
318 use crate::emulator::find_emulator;
319 use crate::emulator::test::{CardTypeOption, EcardSubprocess};
320 use crate::rand_chaincode;
321 use crate::tap_signer::TapSignerShared;
322
323 #[tokio::test]
324 async fn test_new_command() {
325 let chain_code = rand_chaincode();
326 for card_type in CardTypeOption::values() {
327 let pipe_path = format!("/tmp/test-new-command-pipe{card_type}");
328 let pipe_path = Path::new(&pipe_path);
329 let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
330 let emulator = find_emulator(pipe_path).await.unwrap();
331 match emulator {
332 CkTapCard::SatsCard(mut sc) => {
333 assert_eq!(card_type, CardTypeOption::SatsCard);
334 let current_slot = sc.slots.0;
335 let response = sc.unseal(current_slot, CVC).await;
336 assert!(response.is_ok());
337 let response = sc.new_slot(current_slot + 1, Some(chain_code), CVC).await;
338 assert!(response.is_ok());
339 assert_eq!(sc.slots.0, current_slot + 1);
340 let current_slot = sc.slots.0;
342 let response = sc.unseal(current_slot, CVC).await;
343 assert!(response.is_ok());
344 let response = sc.new_slot(current_slot + 1, None, CVC).await;
345 assert!(response.is_ok());
346 assert_eq!(sc.slots.0, current_slot + 1);
347 }
348 CkTapCard::TapSigner(mut ts) => {
349 assert_eq!(card_type, CardTypeOption::TapSigner);
350 let response = ts.init(chain_code, CVC).await;
351 assert!(response.is_ok())
352 }
353 CkTapCard::SatsChip(mut sc) => {
354 assert_eq!(card_type, CardTypeOption::SatsChip);
355 let response = sc.init(chain_code, CVC).await;
356 assert!(response.is_ok())
357 }
358 };
359 drop(python);
360 }
361 }
362
363 #[tokio::test]
364 async fn test_cert_command() {
365 for card_type in CardTypeOption::values() {
366 let emulator_root_pubkey =
367 "0312d005ca1501b1603c3b00412eefe27c6b20a74c29377263b357b3aff12de6fa".to_string();
368 let pipe_path = format!("/tmp/test-cert-command-pipe{card_type}");
369 let pipe_path = Path::new(&pipe_path);
370 let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
371 let emulator = find_emulator(pipe_path).await.unwrap();
372 match emulator {
373 CkTapCard::SatsCard(mut sc) => {
374 assert_eq!(card_type, CardTypeOption::SatsCard);
375 let response = sc.check_certificate().await;
376 assert!(response.is_err());
377 matches!(response, Err(CertsError::InvalidRootCert(pubkey)) if pubkey == emulator_root_pubkey);
378 }
379 CkTapCard::TapSigner(mut ts) => {
380 assert_eq!(card_type, CardTypeOption::TapSigner);
381 let response = ts.check_certificate().await;
382 assert!(response.is_err());
383 matches!(response, Err(CertsError::InvalidRootCert(pubkey)) if pubkey == emulator_root_pubkey);
384 }
385 CkTapCard::SatsChip(mut sc) => {
386 assert_eq!(card_type, CardTypeOption::SatsChip);
387 let response = sc.check_certificate().await;
388 assert!(response.is_err());
389 matches!(response, Err(CertsError::InvalidRootCert(pubkey)) if pubkey == emulator_root_pubkey);
390 }
391 };
392 drop(python);
393 }
394 }
395
396 #[tokio::test]
397 async fn test_nfc_command() {
398 for card_type in CardTypeOption::values() {
399 let pipe_path = format!("/tmp/test-nfc-command-pipe{card_type}");
400 let pipe_path = Path::new(&pipe_path);
401 let python = EcardSubprocess::new(pipe_path, &card_type).unwrap();
402 let emulator = find_emulator(pipe_path).await.unwrap();
403 match emulator {
404 CkTapCard::SatsCard(mut sc) => {
405 assert_eq!(card_type, CardTypeOption::SatsCard);
406 let response = sc.nfc().await;
407 assert!(response.is_ok());
408 assert!(
409 response
410 .unwrap()
411 .starts_with("https://getsatscard.com/start")
412 )
413 }
414 CkTapCard::TapSigner(mut ts) => {
415 assert_eq!(card_type, CardTypeOption::TapSigner);
416 let response = ts.nfc().await;
417 assert!(response.is_ok());
418 assert!(response.unwrap().starts_with("https://tapsigner.com/start"))
419 }
420 CkTapCard::SatsChip(mut sc) => {
421 assert_eq!(card_type, CardTypeOption::SatsChip);
422 let response = sc.nfc().await;
423 assert!(response.is_ok());
424 assert!(response.unwrap().starts_with("https://satschip.com/start"))
425 }
426 };
427 drop(python);
428 }
429 }
430}