Skip to content

Commit 556e6d3

Browse files
committed
feat: add 'xpub' command to ffi bindings
1 parent 77e5660 commit 556e6d3

File tree

8 files changed

+97
-21
lines changed

8 files changed

+97
-21
lines changed

Justfile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ help:
2828

2929
# start the cktap emulator on /tmp/ecard-pipe
3030
start *OPTS: setup
31-
source emulator_env/bin/activate; python3 coinkite/coinkite-tap-proto/emulator/ecard.py emulate {{OPTS}} &> emulator_env/output.log & \
32-
echo $! > emulator_env/ecard.pid
33-
echo "started emulator, pid:" `cat emulator_env/ecard.pid`
31+
if [ -f emulator_env/ecard.pid ]; then \
32+
echo "Emulator already running, pid:" `cat emulator_env/ecard.pid`; \
33+
else \
34+
source emulator_env/bin/activate; python3 coinkite/coinkite-tap-proto/emulator/ecard.py emulate {{OPTS}} &> emulator_env/output.log & \
35+
echo $! > emulator_env/ecard.pid; \
36+
echo "started emulator, pid:" `cat emulator_env/ecard.pid`; \
37+
fi
3438

3539
# stop the cktap emulator
3640
stop:

cktap-ffi/src/error.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,3 +376,26 @@ impl From<rust_cktap::ChangeError> for ChangeError {
376376
}
377377
}
378378
}
379+
380+
/// Errors returned by the `xpub` command.
381+
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error, uniffi::Error)]
382+
pub enum XpubError {
383+
#[error(transparent)]
384+
CkTap {
385+
#[from]
386+
err: CkTapError,
387+
},
388+
#[error("BIP32 error: {msg}")]
389+
Bip32 { msg: String },
390+
}
391+
392+
impl From<rust_cktap::XpubError> for XpubError {
393+
fn from(value: rust_cktap::XpubError) -> Self {
394+
match value {
395+
rust_cktap::XpubError::CkTap(err) => XpubError::CkTap { err: err.into() },
396+
rust_cktap::XpubError::Bip32(err) => XpubError::Bip32 {
397+
msg: err.to_string(),
398+
},
399+
}
400+
}
401+
}

cktap-ffi/src/lib.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use futures::lock::Mutex;
1818
use rust_cktap::Network;
1919
use rust_cktap::shared::FactoryRootKey;
2020
use rust_cktap::shared::{Certificate, Read};
21-
use std::fmt::Debug;
21+
use std::fmt::{Debug, Display, Formatter};
2222
use std::str::FromStr;
2323
use std::sync::Arc;
2424

@@ -122,6 +122,24 @@ impl Psbt {
122122
}
123123
}
124124

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+
125143
#[derive(uniffi::Enum)]
126144
pub enum CkTapCard {
127145
SatsCard(Arc<SatsCard>),

cktap-ffi/src/sats_chip.rs

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

4-
use crate::error::{CertsError, ChangeError, CkTapError, DeriveError, ReadError, SignPsbtError};
4+
use crate::error::{
5+
CertsError, ChangeError, CkTapError, DeriveError, ReadError, SignPsbtError, XpubError,
6+
};
57
use crate::tap_signer::{change, derive, init, sign_psbt};
6-
use crate::{ChainCode, Psbt, PublicKey, check_cert, read};
8+
use crate::{ChainCode, Psbt, PublicKey, Xpub, check_cert, read};
79
use futures::lock::Mutex;
810
use rust_cktap::shared::{Authentication, Nfc, Wait};
11+
use rust_cktap::tap_signer::TapSignerShared;
912
use std::sync::Arc;
1013

1114
#[derive(uniffi::Object)]
@@ -85,4 +88,10 @@ impl SatsChip {
8588
let url = card.nfc().await?;
8689
Ok(url)
8790
}
91+
92+
pub async fn xpub(&self, master: bool, cvc: String) -> Result<Xpub, XpubError> {
93+
let mut card = self.0.lock().await;
94+
let xpub = card.xpub(master, &cvc).await?;
95+
Ok(Xpub { inner: xpub })
96+
}
8897
}

cktap-ffi/src/tap_signer.rs

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

4-
use crate::error::{CertsError, ChangeError, CkTapError, DeriveError, ReadError, SignPsbtError};
5-
use crate::{ChainCode, Psbt, PublicKey, check_cert, read};
4+
use crate::error::{
5+
CertsError, ChangeError, CkTapError, DeriveError, ReadError, SignPsbtError, XpubError,
6+
};
7+
use crate::{ChainCode, Psbt, PublicKey, Xpub, check_cert, read};
68
use futures::lock::Mutex;
79
use rust_cktap::shared::{Authentication, Nfc, Wait};
810
use rust_cktap::tap_signer::TapSignerShared;
@@ -87,6 +89,12 @@ impl TapSigner {
8789
let url = card.nfc().await?;
8890
Ok(url)
8991
}
92+
93+
pub async fn xpub(&self, master: bool, cvc: String) -> Result<Xpub, XpubError> {
94+
let mut card = self.0.lock().await;
95+
let xpub = card.xpub(master, &cvc).await?;
96+
Ok(Xpub { inner: xpub })
97+
}
9098
}
9199

92100
pub async fn init(

cktap-swift/Tests/CKTapTests/CKTapTests.swift

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,29 @@ final class CKTapTests: XCTestCase {
3333
let cardEmulator = CardEmulator()
3434
let card = try await toCktap(transport: cardEmulator)
3535
switch card {
36-
case .satsCard(let satsCard):
37-
let url: String = try await satsCard.nfc()
38-
print("SatsCard url: \(url)")
39-
case .tapSigner(let tapSigner):
40-
let url: String = try await tapSigner.nfc()
41-
print("TapSigner url: \(url)")
42-
case .satsChip(let satsChip):
43-
let url: String = try await satsChip.nfc()
44-
print("SatsChip url: \(url)")
45-
}
36+
case .satsCard(let satsCard):
37+
let url: String = try await satsCard.nfc()
38+
print("SatsCard url: \(url)")
39+
case .tapSigner(let tapSigner):
40+
let url: String = try await tapSigner.nfc()
41+
print("TapSigner url: \(url)")
42+
case .satsChip(let satsChip):
43+
let url: String = try await satsChip.nfc()
44+
print("SatsChip url: \(url)")
45+
}
46+
}
47+
func testXpub() async throws {
48+
let cardEmulator = CardEmulator()
49+
let card = try await toCktap(transport: cardEmulator)
50+
switch card {
51+
case .satsCard(_):
52+
print("SatsCard does not support he xpub command.")
53+
case .tapSigner(let tapSigner):
54+
let xpub: String = try await tapSigner.xpub(master: true, cvc: "123456").toString()
55+
print("TapSigner master xpub: \(xpub)")
56+
case .satsChip(let satsChip):
57+
let xpub: String = try await satsChip.xpub(master: false, cvc: "123456").toString()
58+
print("SatsChip xpub: \(xpub)")
59+
}
4660
}
4761
}

cli/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,5 +358,5 @@ where
358358
dbg!(master);
359359
let xpub = card.xpub(master, &cvc()).await.expect("xpub failed");
360360
dbg!(&xpub);
361-
println!("{}", xpub.to_string());
361+
println!("{xpub}");
362362
}

lib/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ pub use bitcoin::bip32::ChainCode;
77
pub use bitcoin::key::FromSliceError;
88
pub use bitcoin::psbt::{Psbt, PsbtParseError};
99
pub use bitcoin::secp256k1::{Error as SecpError, rand};
10-
pub use bitcoin::{Network, PrivateKey, PublicKey};
10+
pub use bitcoin::{Network, PrivateKey, PublicKey, bip32::Xpub};
1111
pub use bitcoin_hashes::sha256::Hash;
1212

1313
pub use error::{
1414
CardError, CertsError, ChangeError, CkTapError, DeriveError, DumpError, ReadError,
15-
SignPsbtError, StatusError, UnsealError,
15+
SignPsbtError, StatusError, UnsealError, XpubError,
1616
};
1717
pub use shared::CkTransport;
1818

0 commit comments

Comments
 (0)