Skip to content

Commit efa7ce4

Browse files
committed
refactor(ffi): simplify ffi API
1 parent c9826bd commit efa7ce4

File tree

10 files changed

+155
-227
lines changed

10 files changed

+155
-227
lines changed

cktap-ffi/src/error.rs

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,6 @@ impl From<rust_cktap::FromSliceError> for KeyError {
2727
}
2828
}
2929

30-
#[derive(Debug, thiserror::Error, uniffi::Error)]
31-
pub enum ChainCodeError {
32-
#[error("Invalid length {len}, must be 32 bytes")]
33-
InvalidLength { len: u64 },
34-
}
35-
36-
impl From<Vec<u8>> for ChainCodeError {
37-
fn from(value: Vec<u8>) -> Self {
38-
ChainCodeError::InvalidLength {
39-
len: value.len() as u64,
40-
}
41-
}
42-
}
43-
44-
#[derive(Debug, thiserror::Error, uniffi::Error)]
45-
pub enum PsbtError {
46-
#[error("Could not parse psbt: {msg}")]
47-
Parse { msg: String },
48-
}
49-
50-
impl From<rust_cktap::PsbtParseError> for PsbtError {
51-
fn from(value: rust_cktap::PsbtParseError) -> Self {
52-
PsbtError::Parse {
53-
msg: value.to_string(),
54-
}
55-
}
56-
}
57-
5830
/// Errors returned by the CkTap card.
5931
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error, uniffi::Error)]
6032
pub enum CardError {
@@ -319,6 +291,10 @@ pub enum SignPsbtError {
319291
},
320292
#[error("Witness program error: {msg}")]
321293
WitnessProgram { msg: String },
294+
#[error("Error in internal PSBT data structure: {msg}")]
295+
PsbtEncoding { msg: String },
296+
#[error("Error in PSBT Base64 encoding: {msg}")]
297+
Base64Encoding { msg: String },
322298
}
323299

324300
impl From<rust_cktap::SignPsbtError> for SignPsbtError {
@@ -350,6 +326,20 @@ impl From<rust_cktap::SignPsbtError> for SignPsbtError {
350326
}
351327
}
352328

329+
impl From<rust_cktap::PsbtParseError> for SignPsbtError {
330+
fn from(value: rust_cktap::PsbtParseError) -> SignPsbtError {
331+
match value {
332+
rust_cktap::PsbtParseError::PsbtEncoding(err) => SignPsbtError::PsbtEncoding {
333+
msg: err.to_string(),
334+
},
335+
rust_cktap::PsbtParseError::Base64Encoding(err) => SignPsbtError::Base64Encoding {
336+
msg: err.to_string(),
337+
},
338+
_ => panic!("Unexpected error: {value:?}"),
339+
}
340+
}
341+
}
342+
353343
/// Errors returned by the `change` command.
354344
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error, uniffi::Error)]
355345
pub enum ChangeError {

cktap-ffi/src/lib.rs

Lines changed: 4 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,14 @@ mod tap_signer;
88

99
uniffi::setup_scaffolding!();
1010

11-
use crate::error::{
12-
CertsError, ChainCodeError, CkTapError, KeyError, PsbtError, ReadError, StatusError,
13-
};
11+
use crate::error::{CertsError, CkTapError, ReadError, StatusError};
1412
use crate::sats_card::SatsCard;
1513
use crate::sats_chip::SatsChip;
1614
use crate::tap_signer::TapSigner;
1715
use futures::lock::Mutex;
18-
use rust_cktap::Network;
1916
use rust_cktap::shared::FactoryRootKey;
2017
use rust_cktap::shared::{Certificate, Read};
21-
use std::fmt::{Debug, Display, Formatter};
22-
use std::str::FromStr;
18+
use std::fmt::Debug;
2319
use std::sync::Arc;
2420

2521
#[uniffi::export(callback_interface)]
@@ -43,103 +39,6 @@ impl rust_cktap::CkTransport for CkTransportWrapper {
4339
}
4440
}
4541

46-
#[derive(uniffi::Object, Clone, Eq, PartialEq)]
47-
pub struct PrivateKey {
48-
inner: rust_cktap::PrivateKey,
49-
}
50-
51-
#[uniffi::export]
52-
impl PrivateKey {
53-
#[uniffi::constructor]
54-
pub fn from(data: Vec<u8>) -> Result<Self, KeyError> {
55-
Ok(Self {
56-
inner: rust_cktap::PrivateKey::from_slice(data.as_slice(), Network::Bitcoin)
57-
.map_err(|e| KeyError::Secp256k1 { msg: e.to_string() })?,
58-
})
59-
}
60-
61-
pub fn to_bytes(&self) -> Vec<u8> {
62-
self.inner.to_bytes()
63-
}
64-
}
65-
66-
#[derive(uniffi::Object, Clone, Eq, PartialEq)]
67-
pub struct PublicKey {
68-
inner: rust_cktap::PublicKey,
69-
}
70-
71-
#[uniffi::export]
72-
impl PublicKey {
73-
#[uniffi::constructor]
74-
pub fn from(data: Vec<u8>) -> Result<Self, KeyError> {
75-
Ok(Self {
76-
inner: rust_cktap::PublicKey::from_slice(data.as_slice())
77-
.map_err(|e| KeyError::Secp256k1 { msg: e.to_string() })?,
78-
})
79-
}
80-
81-
pub fn to_bytes(&self) -> Vec<u8> {
82-
self.inner.to_bytes()
83-
}
84-
}
85-
86-
#[derive(uniffi::Object, Clone, Eq, PartialEq)]
87-
pub struct ChainCode {
88-
inner: rust_cktap::ChainCode,
89-
}
90-
91-
#[uniffi::export]
92-
impl ChainCode {
93-
#[uniffi::constructor]
94-
pub fn from_bytes(data: Vec<u8>) -> Result<Self, ChainCodeError> {
95-
let data: [u8; 32] = data.try_into()?;
96-
Ok(Self {
97-
inner: rust_cktap::ChainCode::from(data),
98-
})
99-
}
100-
101-
pub fn to_bytes(&self) -> Vec<u8> {
102-
self.inner.to_bytes().to_vec()
103-
}
104-
}
105-
106-
#[derive(uniffi::Object, Clone, Eq, PartialEq)]
107-
pub struct Psbt {
108-
inner: rust_cktap::Psbt,
109-
}
110-
111-
#[uniffi::export]
112-
impl Psbt {
113-
#[uniffi::constructor]
114-
pub fn from_base64(data: String) -> Result<Self, PsbtError> {
115-
Ok(Self {
116-
inner: rust_cktap::Psbt::from_str(&data)?,
117-
})
118-
}
119-
120-
pub fn to_base64(&self) -> String {
121-
self.inner.to_string()
122-
}
123-
}
124-
125-
#[derive(uniffi::Object, Clone, Eq, PartialEq)]
126-
pub struct Xpub {
127-
inner: rust_cktap::Xpub,
128-
}
129-
130-
#[uniffi::export]
131-
impl Xpub {
132-
pub fn encode(&self) -> Vec<u8> {
133-
self.inner.encode().to_vec()
134-
}
135-
}
136-
137-
impl Display for Xpub {
138-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
139-
write!(f, "{}", self.inner)
140-
}
141-
}
142-
14342
#[derive(uniffi::Enum)]
14443
pub enum CkTapCard {
14544
SatsCard(Arc<SatsCard>),
@@ -170,10 +69,10 @@ pub async fn to_cktap(transport: Box<dyn CkTransport>) -> Result<CkTapCard, Stat
17069
async fn read(
17170
card: &mut (impl Read + Send + Sync),
17271
cvc: Option<String>,
173-
) -> Result<Vec<u8>, ReadError> {
72+
) -> Result<String, ReadError> {
17473
card.read(cvc)
17574
.await
176-
.map(|pk| pk.to_bytes())
75+
.map(|pk| pk.to_string())
17776
.map_err(ReadError::from)
17877
}
17978

cktap-ffi/src/sats_card.rs

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// Copyright (c) 2025 rust-cktap contributors
22
// SPDX-License-Identifier: MIT OR Apache-2.0
33

4+
use crate::check_cert;
45
use crate::error::{
56
CertsError, CkTapError, DeriveError, DumpError, ReadError, SignPsbtError, UnsealError,
67
};
7-
use crate::{ChainCode, PrivateKey, Psbt, PublicKey, check_cert, read};
88
use futures::lock::Mutex;
9-
use rust_cktap::shared::{Authentication, Nfc, Wait};
10-
use std::sync::Arc;
9+
use rust_cktap::descriptor::Wpkh;
10+
use rust_cktap::shared::{Authentication, Nfc, Read, Wait};
11+
use rust_cktap::{Psbt, rand_chaincode};
12+
use std::str::FromStr;
1113

1214
#[derive(uniffi::Object)]
1315
pub struct SatsCard(pub Mutex<rust_cktap::SatsCard>);
@@ -20,43 +22,50 @@ pub struct SatsCardStatus {
2022
pub active_slot: u8,
2123
pub num_slots: u8,
2224
pub addr: Option<String>,
23-
pub pubkey: Vec<u8>,
25+
pub pubkey: String,
2426
pub auth_delay: Option<u8>,
2527
}
2628

2729
#[derive(uniffi::Record, Clone)]
28-
pub struct UnsealedSlot {
29-
slot: u8,
30-
privkey: Option<Arc<PrivateKey>>,
31-
pubkey: Arc<PublicKey>,
30+
pub struct SlotDetails {
31+
privkey: Option<String>,
32+
pubkey: String,
33+
pubkey_descriptor: String,
3234
}
3335

3436
#[uniffi::export]
3537
impl SatsCard {
3638
pub async fn status(&self) -> SatsCardStatus {
3739
let card = self.0.lock().await;
40+
let pubkey = card.pubkey().to_string();
3841
SatsCardStatus {
3942
proto: card.proto as u64,
4043
ver: card.ver().to_string(),
4144
birth: card.birth as u64,
4245
active_slot: card.slots.0,
4346
num_slots: card.slots.1,
4447
addr: card.addr.clone(),
45-
pubkey: card.pubkey().to_bytes(),
48+
pubkey,
4649
auth_delay: card.auth_delay().map(|d| d as u8),
4750
}
4851
}
4952

53+
/// Get the current active slot's receive address
5054
pub async fn address(&self) -> Result<String, ReadError> {
5155
let mut card = self.0.lock().await;
52-
card.address().await.map_err(ReadError::from)
56+
let address = card.address().await?;
57+
Ok(address.to_string())
5358
}
5459

55-
pub async fn read(&self) -> Result<Vec<u8>, ReadError> {
60+
/// Get the current active slot's wpkh public key descriptor
61+
pub async fn read(&self) -> Result<String, ReadError> {
5662
let mut card = self.0.lock().await;
57-
read(&mut *card, None).await
63+
let pubkey = card.read(None).await?;
64+
let pubkey_desc = format!("{}", Wpkh::new(pubkey).unwrap());
65+
Ok(pubkey_desc)
5866
}
5967

68+
/// Wait 15 seconds or until auth delay timeout is done
6069
pub async fn wait(&self) -> Result<(), CkTapError> {
6170
let mut card = self.0.lock().await;
6271
// if auth delay call wait
@@ -66,68 +75,68 @@ impl SatsCard {
6675
Ok(())
6776
}
6877

78+
/// Verify the card has authentic Coinkite root certificate
6979
pub async fn check_cert(&self) -> Result<(), CertsError> {
7080
let mut card = self.0.lock().await;
7181
check_cert(&mut *card).await
7282
}
7383

74-
pub async fn new_slot(
75-
&self,
76-
slot: u8,
77-
chain_code: Option<Arc<ChainCode>>,
78-
cvc: String,
79-
) -> Result<u8, CkTapError> {
84+
/// Open a new slot, it will be the current active but must be unused (no address)
85+
pub async fn new_slot(&self, cvc: String) -> Result<u8, DeriveError> {
8086
let mut card = self.0.lock().await;
81-
let chain_code = chain_code.map(|cc| cc.inner);
82-
card.new_slot(slot, chain_code, &cvc)
87+
let (active_slot, _) = card.slots;
88+
let new_slot_chain_code = rand_chaincode();
89+
let new_slot = card
90+
.new_slot(active_slot, Some(new_slot_chain_code), &cvc)
8391
.await
84-
.map_err(CkTapError::from)
85-
}
86-
87-
pub async fn derive(&self) -> Result<ChainCode, DeriveError> {
88-
let mut card = self.0.lock().await;
89-
let chain_code = card.derive().await.map(|cc| ChainCode { inner: cc })?;
90-
Ok(chain_code)
92+
.map_err(CkTapError::from)?;
93+
let derive_chain_code = card.derive().await?;
94+
if derive_chain_code != new_slot_chain_code {
95+
return Err(DeriveError::InvalidChainCode {
96+
msg: "Chain code used by derive doesn't match new slot chain code".to_string(),
97+
});
98+
}
99+
Ok(new_slot)
91100
}
92101

93-
pub async fn unseal(&self, slot: u8, cvc: String) -> Result<UnsealedSlot, UnsealError> {
102+
/// Unseal currently active slot
103+
pub async fn unseal(&self, cvc: String) -> Result<SlotDetails, UnsealError> {
94104
let mut card = self.0.lock().await;
95-
let (privkey, pubkey) = card.unseal(slot, &cvc).await?;
96-
let pubkey = Arc::new(PublicKey { inner: pubkey });
97-
let privkey = Some(Arc::new(PrivateKey { inner: privkey }));
98-
Ok(UnsealedSlot {
99-
slot,
100-
pubkey,
101-
privkey,
105+
let active_slot = card.slots.0;
106+
let (privkey, pubkey) = card.unseal(active_slot, &cvc).await?;
107+
Ok(SlotDetails {
108+
privkey: Some(privkey.to_string()),
109+
pubkey: pubkey.to_string(),
110+
pubkey_descriptor: format!("{}", Wpkh::new(pubkey).unwrap()),
102111
})
103112
}
104113

105-
pub async fn dump(&self, slot: u8, cvc: Option<String>) -> Result<UnsealedSlot, DumpError> {
114+
/// This is only needed for debugging, use `sign_psbt` for signing
115+
/// If no CVC given only pubkey and pubkey descriptor returned.
116+
pub async fn dump(&self, slot: u8, cvc: Option<String>) -> Result<SlotDetails, DumpError> {
106117
let mut card = self.0.lock().await;
107118
let (privkey, pubkey) = card.dump(slot, cvc).await?;
108-
let pubkey = Arc::new(PublicKey { inner: pubkey });
109-
let privkey = privkey.map(|sk| Arc::new(PrivateKey { inner: sk }));
110-
Ok(UnsealedSlot {
111-
slot,
112-
pubkey,
113-
privkey,
119+
Ok(SlotDetails {
120+
privkey: privkey.map(|sk| sk.to_string()),
121+
pubkey: pubkey.to_string(),
122+
pubkey_descriptor: format!("{}", Wpkh::new(pubkey).unwrap()),
114123
})
115124
}
116125

126+
/// Sign PSBT, base64 encoded
117127
pub async fn sign_psbt(
118128
&self,
119129
slot: u8,
120-
psbt: Arc<Psbt>,
130+
psbt: String,
121131
cvc: String,
122-
) -> Result<Psbt, SignPsbtError> {
132+
) -> Result<String, SignPsbtError> {
123133
let mut card = self.0.lock().await;
124-
let psbt = card
125-
.sign_psbt(slot, (*psbt).clone().inner, &cvc)
126-
.await
127-
.map(|psbt| Psbt { inner: psbt })?;
128-
Ok(psbt)
134+
let psbt = Psbt::from_str(&psbt)?;
135+
let signed_psbt = card.sign_psbt(slot, psbt, &cvc).await?;
136+
Ok(signed_psbt.to_string())
129137
}
130138

139+
/// Return the same URL as given with a NFC tap.
131140
pub async fn nfc(&self) -> Result<String, CkTapError> {
132141
let mut card = self.0.lock().await;
133142
let url = card.nfc().await?;

0 commit comments

Comments
 (0)