Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cktap-ffi/src/sats_card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub struct SatsCardStatus {
pub num_slots: u8,
pub addr: Option<String>,
pub pubkey: String,
pub card_ident: String,
pub auth_delay: Option<u8>,
}

Expand All @@ -46,6 +47,7 @@ impl SatsCard {
num_slots: card.slots.1,
addr: card.addr.clone(),
pubkey,
card_ident: card.card_ident(),
auth_delay: card.auth_delay().map(|d| d as u8),
}
}
Expand Down
2 changes: 2 additions & 0 deletions cktap-ffi/src/sats_chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct SatsChipStatus {
pub birth: u64,
pub path: Option<Vec<u64>>,
pub pubkey: String,
pub card_ident: String,
pub auth_delay: Option<u8>,
}

Expand All @@ -36,6 +37,7 @@ impl SatsChip {
.clone()
.map(|p| p.iter().map(|&p| p as u64).collect()),
pubkey: card.pubkey().to_string(),
card_ident: card.card_ident(),
auth_delay: card.auth_delay().map(|d| d as u8),
}
}
Expand Down
2 changes: 2 additions & 0 deletions cktap-ffi/src/tap_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct TapSignerStatus {
pub path: Option<Vec<u64>>,
pub num_backups: u64,
pub pubkey: String,
pub card_ident: String,
pub auth_delay: Option<u8>,
}

Expand All @@ -39,6 +40,7 @@ impl TapSigner {
.map(|p| p.iter().map(|&p| p as u64).collect()),
num_backups: card.num_backups.unwrap_or_default() as u64,
pubkey: card.pubkey().to_string(),
card_ident: card.card_ident(),
auth_delay: card.auth_delay().map(|d| d as u8),
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ thiserror = "2.0"
bitcoin = { version = "0.32.7", features = ["rand-std", "base64"] }
miniscript = { version = "12.3" }
bitcoin_hashes = "0.16.0"
data-encoding = "2.6"

# logging
log = "0.4"
Expand Down
2 changes: 1 addition & 1 deletion lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use error::{
CardError, CertsError, ChangeError, CkTapError, DeriveError, DumpError, ReadError,
SignPsbtError, StatusError, UnsealError, XpubError,
};
pub use shared::CkTransport;
pub use shared::{CkTransport, card_pubkey_to_ident};

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

Expand Down
9 changes: 8 additions & 1 deletion lib/src/sats_card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use crate::apdu::{
};
use crate::error::{CardError, DeriveError, DumpError, ReadError, UnsealError};
use crate::error::{SignPsbtError, StatusError};
use crate::shared::{Authentication, Certificate, CkTransport, Nfc, Read, Wait, transmit};
use crate::shared::{
Authentication, Certificate, CkTransport, Nfc, Read, Wait, card_pubkey_to_ident, transmit,
};
use async_trait::async_trait;
use bitcoin::bip32::{ChainCode, DerivationPath, Fingerprint, Xpub};
use bitcoin::secp256k1;
Expand Down Expand Up @@ -68,6 +70,10 @@ impl Authentication for SatsCard {
}

impl SatsCard {
pub fn card_ident(&self) -> String {
card_pubkey_to_ident(&self.pubkey)
}

pub fn from_status(
transport: Arc<dyn CkTransport>,
status_response: StatusResponse,
Expand Down Expand Up @@ -449,6 +455,7 @@ impl core::fmt::Debug for SatsCard {
.field("birth", &self.birth)
.field("slots", &self.slots)
.field("addr", &self.addr)
.field("card_ident", &self.card_ident())
.field("pubkey", &self.pubkey)
.field("card_nonce", &self.card_nonce.to_lower_hex_string())
.field("auth_delay", &self.auth_delay)
Expand Down
9 changes: 8 additions & 1 deletion lib/src/sats_chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use std::sync::Arc;

use crate::apdu::StatusResponse;
use crate::error::{ReadError, StatusError};
use crate::shared::{Authentication, Certificate, CkTransport, Nfc, Read, Wait};
use crate::shared::{
Authentication, Certificate, CkTransport, Nfc, Read, Wait, card_pubkey_to_ident,
};
use crate::tap_signer::TapSignerShared;

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

impl SatsChip {
pub fn card_ident(&self) -> String {
card_pubkey_to_ident(&self.pubkey)
}

pub fn try_from_status(
transport: Arc<dyn CkTransport>,
status_response: StatusResponse,
Expand Down Expand Up @@ -121,6 +127,7 @@ impl core::fmt::Debug for SatsChip {
.field("birth", &self.birth)
.field("path", &self.path)
.field("num_backups", &self.num_backups)
.field("card_ident", &self.card_ident())
.field("pubkey", &self.pubkey)
.field("card_nonce", &self.card_nonce)
.field("auth_delay", &self.auth_delay)
Expand Down
31 changes: 31 additions & 0 deletions lib/src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret;
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId, Signature};
use bitcoin::secp256k1::{All, Message, Secp256k1};
use bitcoin_hashes::sha256;
use data_encoding::BASE32_NOPAD;

use crate::error::{CertsError, ReadError, StatusError};
use crate::sats_chip::SatsChip;
Expand Down Expand Up @@ -78,6 +79,9 @@ pub trait Authentication {
fn auth_delay(&self) -> &Option<usize>;
fn set_auth_delay(&mut self, auth_delay: Option<usize>);
fn transport(&self) -> Arc<dyn CkTransport>;
fn card_ident(&self) -> String {
card_pubkey_to_ident(self.pubkey())
}

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

/// Convert a card's compressed pubkey into the standard public identifier.
pub fn card_pubkey_to_ident(pubkey: &PublicKey) -> String {
let serialized = pubkey.inner.serialize();
let digest = sha256::Hash::hash(&serialized);
let digest_bytes = digest.to_byte_array();
let encoded = BASE32_NOPAD.encode(&digest_bytes[8..]);
let ident = &encoded[..20];

[&ident[0..5], &ident[5..10], &ident[10..15], &ident[15..20]].join("-")
}

#[cfg(test)]
mod card_ident_tests {
use super::*;
use bitcoin::hex::FromHex;

#[test]
fn converts_pubkey_to_ident() {
let pubkey_bytes = Vec::<u8>::from_hex(
"0312d005ca1501b1603c3b00412eefe27c6b20a74c29377263b357b3aff12de6fa",
)
.expect("valid pubkey hex");
let pubkey = PublicKey::from_slice(&pubkey_bytes).expect("compressed pubkey");
assert_eq!(card_pubkey_to_ident(&pubkey), "IYKC5-XN6ZN-3AGAA-BWABB");
}
}

/// Trait for exchanging APDU data with cktap cards.
#[async_trait]
pub trait CkTransport: Sync + Send {
Expand Down
9 changes: 8 additions & 1 deletion lib/src/tap_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use crate::apdu::{
tap_signer::{BackupCommand, BackupResponse, ChangeCommand, ChangeResponse},
};
use crate::error::{ChangeError, DeriveError, ReadError, SignPsbtError, StatusError, XpubError};
use crate::shared::{Authentication, Certificate, CkTransport, Nfc, Read, Wait, transmit};
use crate::shared::{
Authentication, Certificate, CkTransport, Nfc, Read, Wait, card_pubkey_to_ident, transmit,
};
use crate::{BIP32_HARDENED_MASK, CkTapError};
use async_trait::async_trait;
use bitcoin::PublicKey;
Expand Down Expand Up @@ -332,6 +334,10 @@ impl Nfc for TapSigner {}
impl TapSignerShared for TapSigner {}

impl TapSigner {
pub fn card_ident(&self) -> String {
card_pubkey_to_ident(&self.pubkey)
}

pub fn try_from_status(
transport: Arc<dyn CkTransport>,
status_response: StatusResponse,
Expand Down Expand Up @@ -394,6 +400,7 @@ impl core::fmt::Debug for TapSigner {
.field("birth", &self.birth)
.field("path", &self.path)
.field("num_backups", &self.num_backups)
.field("card_ident", &self.card_ident())
.field("pubkey", &self.pubkey)
.field("card_nonce", &self.card_nonce.to_lower_hex_string())
.field("auth_delay", &self.auth_delay)
Expand Down
Loading