11// Copyright (c) 2025 rust-cktap contributors
22// SPDX-License-Identifier: MIT OR Apache-2.0
33
4+ use crate :: check_cert;
45use crate :: error:: {
56 CertsError , CkTapError , DeriveError , DumpError , ReadError , SignPsbtError , UnsealError ,
67} ;
7- use crate :: { ChainCode , PrivateKey , Psbt , PublicKey , check_cert, read} ;
88use 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 ) ]
1315pub 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]
3537impl 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