Skip to main content

rust_cktap/
sats_chip.rs

1// Copyright (c) 2025 rust-cktap contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use async_trait::async_trait;
5use bitcoin::PublicKey;
6use bitcoin::secp256k1::{All, Secp256k1};
7use std::sync::Arc;
8
9use crate::apdu::StatusResponse;
10use crate::error::{ReadError, StatusError};
11use crate::shared::{
12    Authentication, Certificate, CkTransport, Nfc, Read, Wait, card_pubkey_to_ident,
13};
14use crate::tap_signer::TapSignerShared;
15
16/// - SATSCHIP model: this product variant is a TAPSIGNER in all respects,
17///   except, as of v1.0.0: `num_backups` in status field is omitted, and
18///   a flag `satschip=True` will be present instead. The "backup" command
19///   is not supported and will fail with 404 error.
20pub struct SatsChip {
21    pub transport: Arc<dyn CkTransport>,
22    pub secp: Secp256k1<All>,
23    pub proto: u32,
24    pub ver: String,
25    pub birth: u32,
26    pub path: Option<Vec<u32>>,
27    // [(1<<31)+84, (1<<31), (1<<31)], user-defined, will be omitted if not yet setup
28    pub num_backups: Option<u32>,
29    pub pubkey: PublicKey,
30    pub card_nonce: [u8; 16],
31    pub auth_delay: Option<u8>,
32}
33
34impl Authentication for SatsChip {
35    fn secp(&self) -> &Secp256k1<All> {
36        &self.secp
37    }
38
39    fn ver(&self) -> &str {
40        &self.ver
41    }
42
43    fn pubkey(&self) -> &PublicKey {
44        &self.pubkey
45    }
46
47    fn card_nonce(&self) -> &[u8; 16] {
48        &self.card_nonce
49    }
50
51    fn set_card_nonce(&mut self, new_nonce: [u8; 16]) {
52        self.card_nonce = new_nonce;
53    }
54
55    fn auth_delay(&self) -> Option<u8> {
56        self.auth_delay
57    }
58
59    fn set_auth_delay(&mut self, auth_delay: Option<u8>) {
60        self.auth_delay = auth_delay;
61    }
62
63    fn transport(&self) -> Arc<dyn CkTransport> {
64        self.transport.clone()
65    }
66}
67
68#[async_trait]
69impl TapSignerShared for SatsChip {}
70
71impl SatsChip {
72    pub fn card_ident(&self) -> String {
73        card_pubkey_to_ident(&self.pubkey)
74    }
75
76    pub fn try_from_status(
77        transport: Arc<dyn CkTransport>,
78        status_response: StatusResponse,
79    ) -> Result<Self, StatusError> {
80        let pubkey = status_response.pubkey.as_slice();
81        let pubkey = PublicKey::from_slice(pubkey).map_err(StatusError::from)?;
82
83        Ok(SatsChip {
84            transport,
85            secp: Secp256k1::new(),
86            proto: status_response.proto,
87            ver: status_response.ver,
88            birth: status_response.birth,
89            path: status_response.path,
90            num_backups: status_response.num_backups,
91            pubkey,
92            card_nonce: status_response.card_nonce,
93            auth_delay: status_response.auth_delay,
94        })
95    }
96}
97
98#[async_trait]
99impl Wait for SatsChip {}
100
101#[async_trait]
102impl Read for SatsChip {
103    fn requires_auth(&self) -> bool {
104        true
105    }
106
107    fn slot(&self) -> Option<u8> {
108        None
109    }
110}
111
112#[async_trait]
113impl Certificate for SatsChip {
114    async fn slot_pubkey(&mut self) -> Result<Option<PublicKey>, ReadError> {
115        Ok(None)
116    }
117}
118
119#[async_trait]
120impl Nfc for SatsChip {}
121
122impl core::fmt::Debug for SatsChip {
123    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124        f.debug_struct("SatsChip")
125            .field("proto", &self.proto)
126            .field("ver", &self.ver)
127            .field("birth", &self.birth)
128            .field("path", &self.path)
129            .field("num_backups", &self.num_backups)
130            .field("card_ident", &self.card_ident())
131            .field("pubkey", &self.pubkey)
132            .field("card_nonce", &self.card_nonce)
133            .field("auth_delay", &self.auth_delay)
134            .finish()
135    }
136}