Skip to content

Commit da263ab

Browse files
committed
feat: card id
1 parent 1d3561d commit da263ab

9 files changed

Lines changed: 63 additions & 4 deletions

File tree

cktap-ffi/src/sats_card.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub struct SatsCardStatus {
2323
pub num_slots: u8,
2424
pub addr: Option<String>,
2525
pub pubkey: String,
26+
pub card_ident: String,
2627
pub auth_delay: Option<u8>,
2728
}
2829

@@ -46,6 +47,7 @@ impl SatsCard {
4647
num_slots: card.slots.1,
4748
addr: card.addr.clone(),
4849
pubkey,
50+
card_ident: card.card_ident(),
4951
auth_delay: card.auth_delay().map(|d| d as u8),
5052
}
5153
}

cktap-ffi/src/sats_chip.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub struct SatsChipStatus {
2020
pub birth: u64,
2121
pub path: Option<Vec<u64>>,
2222
pub pubkey: String,
23+
pub card_ident: String,
2324
pub auth_delay: Option<u8>,
2425
}
2526

@@ -36,6 +37,7 @@ impl SatsChip {
3637
.clone()
3738
.map(|p| p.iter().map(|&p| p as u64).collect()),
3839
pubkey: card.pubkey().to_string(),
40+
card_ident: card.card_ident(),
3941
auth_delay: card.auth_delay().map(|d| d as u8),
4042
}
4143
}

cktap-ffi/src/tap_signer.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct TapSignerStatus {
2222
pub path: Option<Vec<u64>>,
2323
pub num_backups: u64,
2424
pub pubkey: String,
25+
pub card_ident: String,
2526
pub auth_delay: Option<u8>,
2627
}
2728

@@ -39,6 +40,7 @@ impl TapSigner {
3940
.map(|p| p.iter().map(|&p| p as u64).collect()),
4041
num_backups: card.num_backups.unwrap_or_default() as u64,
4142
pubkey: card.pubkey().to_string(),
43+
card_ident: card.card_ident(),
4244
auth_delay: card.auth_delay().map(|d| d as u8),
4345
}
4446
}

lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ thiserror = "2.0"
3131
bitcoin = { version = "0.32.7", features = ["rand-std", "base64"] }
3232
miniscript = { version = "12.3" }
3333
bitcoin_hashes = "0.16.0"
34+
data-encoding = "2.6"
3435

3536
# logging
3637
log = "0.4"

lib/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use error::{
1515
CardError, CertsError, ChangeError, CkTapError, DeriveError, DumpError, ReadError,
1616
SignPsbtError, StatusError, UnsealError, XpubError,
1717
};
18-
pub use shared::CkTransport;
18+
pub use shared::{CkTransport, card_pubkey_to_ident};
1919

2020
use bitcoin::key::rand::Rng as _;
2121

lib/src/sats_card.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use crate::apdu::{
99
};
1010
use crate::error::{CardError, DeriveError, DumpError, ReadError, UnsealError};
1111
use crate::error::{SignPsbtError, StatusError};
12-
use crate::shared::{Authentication, Certificate, CkTransport, Nfc, Read, Wait, transmit};
12+
use crate::shared::{
13+
Authentication, Certificate, CkTransport, Nfc, Read, Wait, card_pubkey_to_ident, transmit,
14+
};
1315
use async_trait::async_trait;
1416
use bitcoin::bip32::{ChainCode, DerivationPath, Fingerprint, Xpub};
1517
use bitcoin::secp256k1;
@@ -68,6 +70,10 @@ impl Authentication for SatsCard {
6870
}
6971

7072
impl SatsCard {
73+
pub fn card_ident(&self) -> String {
74+
card_pubkey_to_ident(&self.pubkey)
75+
}
76+
7177
pub fn from_status(
7278
transport: Arc<dyn CkTransport>,
7379
status_response: StatusResponse,
@@ -449,6 +455,7 @@ impl core::fmt::Debug for SatsCard {
449455
.field("birth", &self.birth)
450456
.field("slots", &self.slots)
451457
.field("addr", &self.addr)
458+
.field("card_ident", &self.card_ident())
452459
.field("pubkey", &self.pubkey)
453460
.field("card_nonce", &self.card_nonce.to_lower_hex_string())
454461
.field("auth_delay", &self.auth_delay)

lib/src/sats_chip.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use std::sync::Arc;
88

99
use crate::apdu::StatusResponse;
1010
use crate::error::{ReadError, StatusError};
11-
use crate::shared::{Authentication, Certificate, CkTransport, Nfc, Read, Wait};
11+
use crate::shared::{
12+
Authentication, Certificate, CkTransport, Nfc, Read, Wait, card_pubkey_to_ident,
13+
};
1214
use crate::tap_signer::TapSignerShared;
1315

1416
/// - SATSCHIP model: this product variant is a TAPSIGNER in all respects,
@@ -67,6 +69,10 @@ impl Authentication for SatsChip {
6769
impl TapSignerShared for SatsChip {}
6870

6971
impl SatsChip {
72+
pub fn card_ident(&self) -> String {
73+
card_pubkey_to_ident(&self.pubkey)
74+
}
75+
7076
pub fn try_from_status(
7177
transport: Arc<dyn CkTransport>,
7278
status_response: StatusResponse,
@@ -121,6 +127,7 @@ impl core::fmt::Debug for SatsChip {
121127
.field("birth", &self.birth)
122128
.field("path", &self.path)
123129
.field("num_backups", &self.num_backups)
130+
.field("card_ident", &self.card_ident())
124131
.field("pubkey", &self.pubkey)
125132
.field("card_nonce", &self.card_nonce)
126133
.field("auth_delay", &self.auth_delay)

lib/src/shared.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret;
1010
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId, Signature};
1111
use bitcoin::secp256k1::{All, Message, Secp256k1};
1212
use bitcoin_hashes::sha256;
13+
use data_encoding::BASE32_NOPAD;
1314

1415
use crate::error::{CertsError, ReadError, StatusError};
1516
use crate::sats_chip::SatsChip;
@@ -78,6 +79,9 @@ pub trait Authentication {
7879
fn auth_delay(&self) -> &Option<usize>;
7980
fn set_auth_delay(&mut self, auth_delay: Option<usize>);
8081
fn transport(&self) -> Arc<dyn CkTransport>;
82+
fn card_ident(&self) -> String {
83+
card_pubkey_to_ident(self.pubkey())
84+
}
8185

8286
/// Calculate ephemeral key pair and XOR'd CVC.
8387
/// ref: ["Authenticating Commands with CVC"](https://github.com/coinkite/coinkite-tap-proto/blob/master/docs/protocol.md#authenticating-commands-with-cvc)
@@ -111,6 +115,33 @@ pub trait Authentication {
111115
}
112116
}
113117

118+
/// Convert a card's compressed pubkey into the standard public identifier.
119+
pub fn card_pubkey_to_ident(pubkey: &PublicKey) -> String {
120+
let serialized = pubkey.inner.serialize();
121+
let digest = sha256::Hash::hash(&serialized);
122+
let digest_bytes = digest.to_byte_array();
123+
let encoded = BASE32_NOPAD.encode(&digest_bytes[8..]);
124+
let ident = &encoded[..20];
125+
126+
[&ident[0..5], &ident[5..10], &ident[10..15], &ident[15..20]].join("-")
127+
}
128+
129+
#[cfg(test)]
130+
mod card_ident_tests {
131+
use super::*;
132+
use bitcoin::hex::FromHex;
133+
134+
#[test]
135+
fn converts_pubkey_to_ident() {
136+
let pubkey_bytes = Vec::<u8>::from_hex(
137+
"0312d005ca1501b1603c3b00412eefe27c6b20a74c29377263b357b3aff12de6fa",
138+
)
139+
.expect("valid pubkey hex");
140+
let pubkey = PublicKey::from_slice(&pubkey_bytes).expect("compressed pubkey");
141+
assert_eq!(card_pubkey_to_ident(&pubkey), "IYKC5-XN6ZN-3AGAA-BWABB");
142+
}
143+
}
144+
114145
/// Trait for exchanging APDU data with cktap cards.
115146
#[async_trait]
116147
pub trait CkTransport: Sync + Send {

lib/src/tap_signer.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use crate::apdu::{
88
tap_signer::{BackupCommand, BackupResponse, ChangeCommand, ChangeResponse},
99
};
1010
use crate::error::{ChangeError, DeriveError, ReadError, SignPsbtError, StatusError, XpubError};
11-
use crate::shared::{Authentication, Certificate, CkTransport, Nfc, Read, Wait, transmit};
11+
use crate::shared::{
12+
Authentication, Certificate, CkTransport, Nfc, Read, Wait, card_pubkey_to_ident, transmit,
13+
};
1214
use crate::{BIP32_HARDENED_MASK, CkTapError};
1315
use async_trait::async_trait;
1416
use bitcoin::PublicKey;
@@ -332,6 +334,10 @@ impl Nfc for TapSigner {}
332334
impl TapSignerShared for TapSigner {}
333335

334336
impl TapSigner {
337+
pub fn card_ident(&self) -> String {
338+
card_pubkey_to_ident(&self.pubkey)
339+
}
340+
335341
pub fn try_from_status(
336342
transport: Arc<dyn CkTransport>,
337343
status_response: StatusResponse,
@@ -394,6 +400,7 @@ impl core::fmt::Debug for TapSigner {
394400
.field("birth", &self.birth)
395401
.field("path", &self.path)
396402
.field("num_backups", &self.num_backups)
403+
.field("card_ident", &self.card_ident())
397404
.field("pubkey", &self.pubkey)
398405
.field("card_nonce", &self.card_nonce.to_lower_hex_string())
399406
.field("auth_delay", &self.auth_delay)

0 commit comments

Comments
 (0)