diff --git a/cktap-ffi/src/error.rs b/cktap-ffi/src/error.rs index 9ae46a1..ec6db38 100644 --- a/cktap-ffi/src/error.rs +++ b/cktap-ffi/src/error.rs @@ -27,34 +27,6 @@ impl From for KeyError { } } -#[derive(Debug, thiserror::Error, uniffi::Error)] -pub enum ChainCodeError { - #[error("Invalid length {len}, must be 32 bytes")] - InvalidLength { len: u64 }, -} - -impl From> for ChainCodeError { - fn from(value: Vec) -> Self { - ChainCodeError::InvalidLength { - len: value.len() as u64, - } - } -} - -#[derive(Debug, thiserror::Error, uniffi::Error)] -pub enum PsbtError { - #[error("Could not parse psbt: {msg}")] - Parse { msg: String }, -} - -impl From for PsbtError { - fn from(value: rust_cktap::PsbtParseError) -> Self { - PsbtError::Parse { - msg: value.to_string(), - } - } -} - /// Errors returned by the CkTap card. #[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error, uniffi::Error)] pub enum CardError { @@ -319,6 +291,10 @@ pub enum SignPsbtError { }, #[error("Witness program error: {msg}")] WitnessProgram { msg: String }, + #[error("Error in internal PSBT data structure: {msg}")] + PsbtEncoding { msg: String }, + #[error("Error in PSBT Base64 encoding: {msg}")] + Base64Encoding { msg: String }, } impl From for SignPsbtError { @@ -350,6 +326,20 @@ impl From for SignPsbtError { } } +impl From for SignPsbtError { + fn from(value: rust_cktap::PsbtParseError) -> SignPsbtError { + match value { + rust_cktap::PsbtParseError::PsbtEncoding(err) => SignPsbtError::PsbtEncoding { + msg: err.to_string(), + }, + rust_cktap::PsbtParseError::Base64Encoding(err) => SignPsbtError::Base64Encoding { + msg: err.to_string(), + }, + _ => panic!("Unexpected error: {value:?}"), + } + } +} + /// Errors returned by the `change` command. #[derive(Clone, Debug, PartialEq, Eq, thiserror::Error, uniffi::Error)] pub enum ChangeError { diff --git a/cktap-ffi/src/lib.rs b/cktap-ffi/src/lib.rs index 45cc891..d043171 100644 --- a/cktap-ffi/src/lib.rs +++ b/cktap-ffi/src/lib.rs @@ -8,18 +8,14 @@ mod tap_signer; uniffi::setup_scaffolding!(); -use crate::error::{ - CertsError, ChainCodeError, CkTapError, KeyError, PsbtError, ReadError, StatusError, -}; +use crate::error::{CertsError, CkTapError, ReadError, StatusError}; use crate::sats_card::SatsCard; use crate::sats_chip::SatsChip; use crate::tap_signer::TapSigner; use futures::lock::Mutex; -use rust_cktap::Network; use rust_cktap::shared::FactoryRootKey; use rust_cktap::shared::{Certificate, Read}; -use std::fmt::{Debug, Display, Formatter}; -use std::str::FromStr; +use std::fmt::Debug; use std::sync::Arc; #[uniffi::export(callback_interface)] @@ -43,103 +39,6 @@ impl rust_cktap::CkTransport for CkTransportWrapper { } } -#[derive(uniffi::Object, Clone, Eq, PartialEq)] -pub struct PrivateKey { - inner: rust_cktap::PrivateKey, -} - -#[uniffi::export] -impl PrivateKey { - #[uniffi::constructor] - pub fn from(data: Vec) -> Result { - Ok(Self { - inner: rust_cktap::PrivateKey::from_slice(data.as_slice(), Network::Bitcoin) - .map_err(|e| KeyError::Secp256k1 { msg: e.to_string() })?, - }) - } - - pub fn to_bytes(&self) -> Vec { - self.inner.to_bytes() - } -} - -#[derive(uniffi::Object, Clone, Eq, PartialEq)] -pub struct PublicKey { - inner: rust_cktap::PublicKey, -} - -#[uniffi::export] -impl PublicKey { - #[uniffi::constructor] - pub fn from(data: Vec) -> Result { - Ok(Self { - inner: rust_cktap::PublicKey::from_slice(data.as_slice()) - .map_err(|e| KeyError::Secp256k1 { msg: e.to_string() })?, - }) - } - - pub fn to_bytes(&self) -> Vec { - self.inner.to_bytes() - } -} - -#[derive(uniffi::Object, Clone, Eq, PartialEq)] -pub struct ChainCode { - inner: rust_cktap::ChainCode, -} - -#[uniffi::export] -impl ChainCode { - #[uniffi::constructor] - pub fn from_bytes(data: Vec) -> Result { - let data: [u8; 32] = data.try_into()?; - Ok(Self { - inner: rust_cktap::ChainCode::from(data), - }) - } - - pub fn to_bytes(&self) -> Vec { - self.inner.to_bytes().to_vec() - } -} - -#[derive(uniffi::Object, Clone, Eq, PartialEq)] -pub struct Psbt { - inner: rust_cktap::Psbt, -} - -#[uniffi::export] -impl Psbt { - #[uniffi::constructor] - pub fn from_base64(data: String) -> Result { - Ok(Self { - inner: rust_cktap::Psbt::from_str(&data)?, - }) - } - - pub fn to_base64(&self) -> String { - self.inner.to_string() - } -} - -#[derive(uniffi::Object, Clone, Eq, PartialEq)] -pub struct Xpub { - inner: rust_cktap::Xpub, -} - -#[uniffi::export] -impl Xpub { - pub fn encode(&self) -> Vec { - self.inner.encode().to_vec() - } -} - -impl Display for Xpub { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner) - } -} - #[derive(uniffi::Enum)] pub enum CkTapCard { SatsCard(Arc), @@ -170,10 +69,10 @@ pub async fn to_cktap(transport: Box) -> Result, -) -> Result, ReadError> { +) -> Result { card.read(cvc) .await - .map(|pk| pk.to_bytes()) + .map(|pk| pk.to_string()) .map_err(ReadError::from) } diff --git a/cktap-ffi/src/sats_card.rs b/cktap-ffi/src/sats_card.rs index c84dda0..32e3fab 100644 --- a/cktap-ffi/src/sats_card.rs +++ b/cktap-ffi/src/sats_card.rs @@ -1,13 +1,15 @@ // Copyright (c) 2025 rust-cktap contributors // SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::check_cert; use crate::error::{ CertsError, CkTapError, DeriveError, DumpError, ReadError, SignPsbtError, UnsealError, }; -use crate::{ChainCode, PrivateKey, Psbt, PublicKey, check_cert, read}; use futures::lock::Mutex; -use rust_cktap::shared::{Authentication, Nfc, Wait}; -use std::sync::Arc; +use rust_cktap::descriptor::Wpkh; +use rust_cktap::shared::{Authentication, Nfc, Read, Wait}; +use rust_cktap::{Psbt, rand_chaincode}; +use std::str::FromStr; #[derive(uniffi::Object)] pub struct SatsCard(pub Mutex); @@ -20,21 +22,22 @@ pub struct SatsCardStatus { pub active_slot: u8, pub num_slots: u8, pub addr: Option, - pub pubkey: Vec, + pub pubkey: String, pub auth_delay: Option, } #[derive(uniffi::Record, Clone)] -pub struct UnsealedSlot { - slot: u8, - privkey: Option>, - pubkey: Arc, +pub struct SlotDetails { + privkey: Option, + pubkey: String, + pubkey_descriptor: String, } #[uniffi::export] impl SatsCard { pub async fn status(&self) -> SatsCardStatus { let card = self.0.lock().await; + let pubkey = card.pubkey().to_string(); SatsCardStatus { proto: card.proto as u64, ver: card.ver().to_string(), @@ -42,21 +45,27 @@ impl SatsCard { active_slot: card.slots.0, num_slots: card.slots.1, addr: card.addr.clone(), - pubkey: card.pubkey().to_bytes(), + pubkey, auth_delay: card.auth_delay().map(|d| d as u8), } } + /// Get the current active slot's receive address pub async fn address(&self) -> Result { let mut card = self.0.lock().await; - card.address().await.map_err(ReadError::from) + let address = card.address().await?; + Ok(address.to_string()) } - pub async fn read(&self) -> Result, ReadError> { + /// Get the current active slot's wpkh public key descriptor + pub async fn read(&self) -> Result { let mut card = self.0.lock().await; - read(&mut *card, None).await + let pubkey = card.read(None).await?; + let pubkey_desc = format!("{}", Wpkh::new(pubkey).unwrap()); + Ok(pubkey_desc) } + /// Wait 15 seconds or until auth delay timeout is done pub async fn wait(&self) -> Result<(), CkTapError> { let mut card = self.0.lock().await; // if auth delay call wait @@ -66,68 +75,68 @@ impl SatsCard { Ok(()) } + /// Verify the card has authentic Coinkite root certificate pub async fn check_cert(&self) -> Result<(), CertsError> { let mut card = self.0.lock().await; check_cert(&mut *card).await } - pub async fn new_slot( - &self, - slot: u8, - chain_code: Option>, - cvc: String, - ) -> Result { + /// Open a new slot, it will be the current active but must be unused (no address) + pub async fn new_slot(&self, cvc: String) -> Result { let mut card = self.0.lock().await; - let chain_code = chain_code.map(|cc| cc.inner); - card.new_slot(slot, chain_code, &cvc) + let (active_slot, _) = card.slots; + let new_slot_chain_code = rand_chaincode(); + let new_slot = card + .new_slot(active_slot, Some(new_slot_chain_code), &cvc) .await - .map_err(CkTapError::from) - } - - pub async fn derive(&self) -> Result { - let mut card = self.0.lock().await; - let chain_code = card.derive().await.map(|cc| ChainCode { inner: cc })?; - Ok(chain_code) + .map_err(CkTapError::from)?; + let derive_chain_code = card.derive().await?; + if derive_chain_code != new_slot_chain_code { + return Err(DeriveError::InvalidChainCode { + msg: "Chain code used by derive doesn't match new slot chain code".to_string(), + }); + } + Ok(new_slot) } - pub async fn unseal(&self, slot: u8, cvc: String) -> Result { + /// Unseal currently active slot + pub async fn unseal(&self, cvc: String) -> Result { let mut card = self.0.lock().await; - let (privkey, pubkey) = card.unseal(slot, &cvc).await?; - let pubkey = Arc::new(PublicKey { inner: pubkey }); - let privkey = Some(Arc::new(PrivateKey { inner: privkey })); - Ok(UnsealedSlot { - slot, - pubkey, - privkey, + let active_slot = card.slots.0; + let (privkey, pubkey) = card.unseal(active_slot, &cvc).await?; + Ok(SlotDetails { + privkey: Some(privkey.to_string()), + pubkey: pubkey.to_string(), + pubkey_descriptor: format!("{}", Wpkh::new(pubkey).unwrap()), }) } - pub async fn dump(&self, slot: u8, cvc: Option) -> Result { + /// This is only needed for debugging, use `sign_psbt` for signing + /// If no CVC given only pubkey and pubkey descriptor returned. + pub async fn dump(&self, slot: u8, cvc: Option) -> Result { let mut card = self.0.lock().await; let (privkey, pubkey) = card.dump(slot, cvc).await?; - let pubkey = Arc::new(PublicKey { inner: pubkey }); - let privkey = privkey.map(|sk| Arc::new(PrivateKey { inner: sk })); - Ok(UnsealedSlot { - slot, - pubkey, - privkey, + Ok(SlotDetails { + privkey: privkey.map(|sk| sk.to_string()), + pubkey: pubkey.to_string(), + pubkey_descriptor: format!("{}", Wpkh::new(pubkey).unwrap()), }) } + /// Sign PSBT, base64 encoded pub async fn sign_psbt( &self, slot: u8, - psbt: Arc, + psbt: String, cvc: String, - ) -> Result { + ) -> Result { let mut card = self.0.lock().await; - let psbt = card - .sign_psbt(slot, (*psbt).clone().inner, &cvc) - .await - .map(|psbt| Psbt { inner: psbt })?; - Ok(psbt) + let psbt = Psbt::from_str(&psbt)?; + let signed_psbt = card.sign_psbt(slot, psbt, &cvc).await?; + Ok(signed_psbt.to_string()) } + /// Return the same URL as given with a NFC tap. pub async fn nfc(&self) -> Result { let mut card = self.0.lock().await; let url = card.nfc().await?; diff --git a/cktap-ffi/src/sats_chip.rs b/cktap-ffi/src/sats_chip.rs index 78e59da..a01bde3 100644 --- a/cktap-ffi/src/sats_chip.rs +++ b/cktap-ffi/src/sats_chip.rs @@ -5,11 +5,10 @@ use crate::error::{ CertsError, ChangeError, CkTapError, DeriveError, ReadError, SignPsbtError, XpubError, }; use crate::tap_signer::{change, derive, init, sign_psbt}; -use crate::{ChainCode, Psbt, PublicKey, Xpub, check_cert, read}; +use crate::{check_cert, read}; use futures::lock::Mutex; use rust_cktap::shared::{Authentication, Nfc, Wait}; use rust_cktap::tap_signer::TapSignerShared; -use std::sync::Arc; #[derive(uniffi::Object)] pub struct SatsChip(pub Mutex); @@ -20,7 +19,7 @@ pub struct SatsChipStatus { pub ver: String, pub birth: u64, pub path: Option>, - pub pubkey: Vec, + pub pubkey: String, pub auth_delay: Option, } @@ -36,12 +35,12 @@ impl SatsChip { .path .clone() .map(|p| p.iter().map(|&p| p as u64).collect()), - pubkey: card.pubkey().to_bytes(), + pubkey: card.pubkey().to_string(), auth_delay: card.auth_delay().map(|d| d as u8), } } - pub async fn read(&self) -> Result, ReadError> { + pub async fn read(&self) -> Result { let mut card = self.0.lock().await; read(&mut *card, None).await } @@ -60,18 +59,18 @@ impl SatsChip { check_cert(&mut *card).await } - pub async fn init(&self, chain_code: Arc, cvc: String) -> Result<(), CkTapError> { + pub async fn init(&self, cvc: String) -> Result<(), CkTapError> { let mut card = self.0.lock().await; - init(&mut *card, chain_code, cvc).await + init(&mut *card, cvc).await } - pub async fn sign_psbt(&self, psbt: Arc, cvc: String) -> Result { + pub async fn sign_psbt(&self, psbt: String, cvc: String) -> Result { let mut card = self.0.lock().await; let psbt = sign_psbt(&mut *card, psbt, cvc).await?; Ok(psbt) } - pub async fn derive(&self, path: Vec, cvc: String) -> Result { + pub async fn derive(&self, path: Vec, cvc: String) -> Result { let mut card = self.0.lock().await; let pubkey = derive(&mut *card, path, cvc).await?; Ok(pubkey) @@ -89,9 +88,9 @@ impl SatsChip { Ok(url) } - pub async fn xpub(&self, master: bool, cvc: String) -> Result { + pub async fn xpub(&self, master: bool, cvc: String) -> Result { let mut card = self.0.lock().await; let xpub = card.xpub(master, &cvc).await?; - Ok(Xpub { inner: xpub }) + Ok(xpub.to_string()) } } diff --git a/cktap-ffi/src/tap_signer.rs b/cktap-ffi/src/tap_signer.rs index 97828ec..97f5a86 100644 --- a/cktap-ffi/src/tap_signer.rs +++ b/cktap-ffi/src/tap_signer.rs @@ -4,11 +4,12 @@ use crate::error::{ CertsError, ChangeError, CkTapError, DeriveError, ReadError, SignPsbtError, XpubError, }; -use crate::{ChainCode, Psbt, PublicKey, Xpub, check_cert, read}; +use crate::{check_cert, read}; use futures::lock::Mutex; use rust_cktap::shared::{Authentication, Nfc, Wait}; use rust_cktap::tap_signer::TapSignerShared; -use std::sync::Arc; +use rust_cktap::{Psbt, rand_chaincode}; +use std::str::FromStr; #[derive(uniffi::Object)] pub struct TapSigner(pub Mutex); @@ -20,7 +21,7 @@ pub struct TapSignerStatus { pub birth: u64, pub path: Option>, pub num_backups: u64, - pub pubkey: Vec, + pub pubkey: String, pub auth_delay: Option, } @@ -37,12 +38,12 @@ impl TapSigner { .clone() .map(|p| p.iter().map(|&p| p as u64).collect()), num_backups: card.num_backups.unwrap_or_default() as u64, - pubkey: card.pubkey().to_bytes(), + pubkey: card.pubkey().to_string(), auth_delay: card.auth_delay().map(|d| d as u8), } } - pub async fn read(&self, cvc: String) -> Result, ReadError> { + pub async fn read(&self, cvc: String) -> Result { let mut card = self.0.lock().await; read(&mut *card, Some(cvc)).await } @@ -61,18 +62,18 @@ impl TapSigner { check_cert(&mut *card).await } - pub async fn init(&self, chain_code: Arc, cvc: String) -> Result<(), CkTapError> { + pub async fn init(&self, cvc: String) -> Result<(), CkTapError> { let mut card = self.0.lock().await; - init(&mut *card, chain_code, cvc).await + init(&mut *card, cvc).await } - pub async fn sign_psbt(&self, psbt: Arc, cvc: String) -> Result { + pub async fn sign_psbt(&self, psbt: String, cvc: String) -> Result { let mut card = self.0.lock().await; let psbt = sign_psbt(&mut *card, psbt, cvc).await?; Ok(psbt) } - pub async fn derive(&self, path: Vec, cvc: String) -> Result { + pub async fn derive(&self, path: Vec, cvc: String) -> Result { let mut card = self.0.lock().await; let pubkey = derive(&mut *card, path, cvc).await?; Ok(pubkey) @@ -90,44 +91,42 @@ impl TapSigner { Ok(url) } - pub async fn xpub(&self, master: bool, cvc: String) -> Result { + pub async fn xpub(&self, master: bool, cvc: String) -> Result { let mut card = self.0.lock().await; let xpub = card.xpub(master, &cvc).await?; - Ok(Xpub { inner: xpub }) + Ok(xpub.to_string()) } } +/// Initialize a new TAPSIGNER card. pub async fn init( card: &mut (impl TapSignerShared + Send + Sync), - chain_code: Arc, cvc: String, ) -> Result<(), CkTapError> { - card.init(chain_code.inner, &cvc) - .await - .map_err(CkTapError::from) + let chain_code = rand_chaincode(); + card.init(chain_code, &cvc).await.map_err(CkTapError::from) } +/// Sign (but not finalize) the psbt +/// +/// PSBT argument and return are encoded as base64 strings. pub async fn sign_psbt( card: &mut (impl TapSignerShared + Send + Sync), - psbt: Arc, + psbt: String, cvc: String, -) -> Result { - let psbt = card - .sign_psbt((*psbt).clone().inner, &cvc) - .await - .map(|psbt| Psbt { inner: psbt })?; - Ok(psbt) +) -> Result { + let unsigned_psbt = Psbt::from_str(&psbt)?; + let psbt = card.sign_psbt(unsigned_psbt, &cvc).await?; + Ok(psbt.to_string()) } +/// Derive the pubkey at the given derivation path, return as hex serialized string pub async fn derive( card: &mut (impl TapSignerShared + Send + Sync), path: Vec, cvc: String, -) -> Result { - let pubkey = card - .derive(path, &cvc) - .await - .map(|pk| PublicKey { inner: pk })?; +) -> Result { + let pubkey = card.derive(path, &cvc).await.map(|pk| pk.to_string())?; Ok(pubkey) } diff --git a/cktap-swift/Tests/CKTapTests/CKTapTests.swift b/cktap-swift/Tests/CKTapTests/CKTapTests.swift index 4f49630..da80d75 100644 --- a/cktap-swift/Tests/CKTapTests/CKTapTests.swift +++ b/cktap-swift/Tests/CKTapTests/CKTapTests.swift @@ -17,6 +17,22 @@ final class CKTapTests: XCTestCase { let address: String = try await satsCard.address() print("SatsCard address: \(address)") XCTAssertEqual(address, "bc1qdu05evh9kw0w482lfl2ktxm6ylp060km28z8fr") + let cardPubkey: String = await satsCard.status().pubkey + print("SatsCard card pubkey: \(cardPubkey)") + let pubkeyDesc: String = try await satsCard.read() + print("SatsCard pubkey desc: \(pubkeyDesc)") + let activeSlot = await satsCard.status().activeSlot + print("SatsCard active slot: \(activeSlot)") + let unsealResult: SlotDetails = try await satsCard.unseal(cvc: "123456") + print("SatsCard unseal result: \(unsealResult)") + let dumpResult: SlotDetails = try await satsCard.dump(slot: activeSlot, cvc: "123456") + print("SatsCard dump result: \(dumpResult)") + let activeSlot2 = await satsCard.status().activeSlot + print("SatsCard active slot2: \(activeSlot2)") + let new_slot = try await satsCard.newSlot(cvc: "123456") + print("SatsCard new slot: \(new_slot)") + let activeSlot3 = await satsCard.status().activeSlot + print("SatsCard active slot3: \(activeSlot3)") case .tapSigner(let tapSigner): let status = await tapSigner.status() print("Handling TapSigner with status: \(status)") @@ -51,10 +67,10 @@ final class CKTapTests: XCTestCase { case .satsCard(_): print("SatsCard does not support he xpub command.") case .tapSigner(let tapSigner): - let xpub: String = try await tapSigner.xpub(master: true, cvc: "123456").toString() + let xpub: String = try await tapSigner.xpub(master: true, cvc: "123456") print("TapSigner master xpub: \(xpub)") case .satsChip(let satsChip): - let xpub: String = try await satsChip.xpub(master: false, cvc: "123456").toString() + let xpub: String = try await satsChip.xpub(master: false, cvc: "123456") print("SatsChip xpub: \(xpub)") } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 5c5ecff..5178dcc 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -188,6 +188,7 @@ async fn main() -> Result<(), CliError> { let slot = sc.slot().expect("current slot number"); let chain_code = Some(rand_chaincode()); let response = &sc.new_slot(slot, chain_code, &cvc()).await?; + println!("chain_code: {chain_code:?}"); println!("{response}") } SatsCardCommand::Unseal => { diff --git a/lib/Cargo.toml b/lib/Cargo.toml index aa99e4c..be5672a 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -25,6 +25,7 @@ thiserror = "2.0" # bitcoin bitcoin = { version = "0.32.7", features = ["rand-std", "base64"] } +miniscript = { version = "12.3" } bitcoin_hashes = "0.16.0" # logging diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 872bc35..0b3228a 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -3,12 +3,13 @@ extern crate core; +pub use bitcoin::Network; pub use bitcoin::bip32::ChainCode; pub use bitcoin::key::FromSliceError; pub use bitcoin::psbt::{Psbt, PsbtParseError}; pub use bitcoin::secp256k1::{Error as SecpError, rand}; -pub use bitcoin::{Network, PrivateKey, PublicKey, bip32::Xpub}; pub use bitcoin_hashes::sha256::Hash; +pub use miniscript::descriptor; pub use error::{ CardError, CertsError, ChangeError, CkTapError, DeriveError, DumpError, ReadError, diff --git a/lib/src/sats_card.rs b/lib/src/sats_card.rs index f74b63a..44e4981 100644 --- a/lib/src/sats_card.rs +++ b/lib/src/sats_card.rs @@ -3,8 +3,9 @@ use crate::CkTapError; use crate::apdu::{ - CommandApdu as _, DeriveCommand, DeriveResponse, DumpCommand, DumpResponse, NewCommand, - NewResponse, SignCommand, SignResponse, StatusResponse, UnsealCommand, UnsealResponse, + AppletSelect, CommandApdu as _, DeriveCommand, DeriveResponse, DumpCommand, DumpResponse, + NewCommand, NewResponse, SignCommand, SignResponse, StatusResponse, UnsealCommand, + UnsealResponse, }; use crate::error::{CardError, DeriveError, DumpError, ReadError, UnsealError}; use crate::error::{SignPsbtError, StatusError}; @@ -102,7 +103,13 @@ impl SatsCard { let new_command = NewCommand::new(Some(slot), chain_code, epubkey, xcvc); let new_response: NewResponse = transmit(self.transport(), &new_command).await?; self.card_nonce = new_response.card_nonce; - self.slots.0 = new_response.slot; + + // Update card fields that changed by re-getting status + let status_cmd = AppletSelect::default(); + let status_response: StatusResponse = transmit(self.transport(), &status_cmd).await?; + self.slots = status_response.slots.unwrap(); + self.addr = status_response.addr; + self.card_nonce = status_response.card_nonce; Ok(new_response.slot) } @@ -194,6 +201,13 @@ impl SatsCard { let unseal_response: UnsealResponse = transmit(self.transport(), &unseal_command).await?; self.set_card_nonce(unseal_response.card_nonce); + // Update card fields that changed by re-getting status + let status_cmd = AppletSelect::default(); + let status_response: StatusResponse = transmit(self.transport(), &status_cmd).await?; + self.slots = status_response.slots.unwrap(); + self.addr = status_response.addr; + self.set_card_nonce(status_response.card_nonce); + // private key for spending (for addr), 32 bytes // the private key is encrypted, XORed with the session key, need to decrypt let session_key = secp256k1::ecdh::SharedSecret::new(&self.pubkey().inner, &eprivkey); @@ -206,7 +220,6 @@ impl SatsCard { .collect(); let privkey = PrivateKey::from_slice(&privkey, Network::Bitcoin)?; let pubkey = PublicKey::from_slice(&unseal_response.pubkey)?; - // TODO should verify user provided chain code was used similar to above `derive`. Ok((privkey, pubkey)) }