1
1
// Copyright (c) 2025 rust-cktap contributors
2
2
// SPDX-License-Identifier: MIT OR Apache-2.0
3
3
4
+ use crate :: check_cert;
4
5
use crate :: error:: {
5
6
CertsError , CkTapError , DeriveError , DumpError , ReadError , SignPsbtError , UnsealError ,
6
7
} ;
7
- use crate :: { ChainCode , PrivateKey , Psbt , PublicKey , check_cert, read} ;
8
8
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 ;
11
13
12
14
#[ derive( uniffi:: Object ) ]
13
15
pub struct SatsCard ( pub Mutex < rust_cktap:: SatsCard > ) ;
@@ -20,43 +22,50 @@ pub struct SatsCardStatus {
20
22
pub active_slot : u8 ,
21
23
pub num_slots : u8 ,
22
24
pub addr : Option < String > ,
23
- pub pubkey : Vec < u8 > ,
25
+ pub pubkey : String ,
24
26
pub auth_delay : Option < u8 > ,
25
27
}
26
28
27
29
#[ 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 ,
32
34
}
33
35
34
36
#[ uniffi:: export]
35
37
impl SatsCard {
36
38
pub async fn status ( & self ) -> SatsCardStatus {
37
39
let card = self . 0 . lock ( ) . await ;
40
+ let pubkey = card. pubkey ( ) . to_string ( ) ;
38
41
SatsCardStatus {
39
42
proto : card. proto as u64 ,
40
43
ver : card. ver ( ) . to_string ( ) ,
41
44
birth : card. birth as u64 ,
42
45
active_slot : card. slots . 0 ,
43
46
num_slots : card. slots . 1 ,
44
47
addr : card. addr . clone ( ) ,
45
- pubkey : card . pubkey ( ) . to_bytes ( ) ,
48
+ pubkey,
46
49
auth_delay : card. auth_delay ( ) . map ( |d| d as u8 ) ,
47
50
}
48
51
}
49
52
53
+ /// Get the current active slot's receive address
50
54
pub async fn address ( & self ) -> Result < String , ReadError > {
51
55
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 ( ) )
53
58
}
54
59
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 > {
56
62
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)
58
66
}
59
67
68
+ /// Wait 15 seconds or until auth delay timeout is done
60
69
pub async fn wait ( & self ) -> Result < ( ) , CkTapError > {
61
70
let mut card = self . 0 . lock ( ) . await ;
62
71
// if auth delay call wait
@@ -66,68 +75,68 @@ impl SatsCard {
66
75
Ok ( ( ) )
67
76
}
68
77
78
+ /// Verify the card has authentic Coinkite root certificate
69
79
pub async fn check_cert ( & self ) -> Result < ( ) , CertsError > {
70
80
let mut card = self . 0 . lock ( ) . await ;
71
81
check_cert ( & mut * card) . await
72
82
}
73
83
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 > {
80
86
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)
83
91
. 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)
91
100
}
92
101
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 > {
94
104
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( ) ) ,
102
111
} )
103
112
}
104
113
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 > {
106
117
let mut card = self . 0 . lock ( ) . await ;
107
118
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( ) ) ,
114
123
} )
115
124
}
116
125
126
+ /// Sign PSBT, base64 encoded
117
127
pub async fn sign_psbt (
118
128
& self ,
119
129
slot : u8 ,
120
- psbt : Arc < Psbt > ,
130
+ psbt : String ,
121
131
cvc : String ,
122
- ) -> Result < Psbt , SignPsbtError > {
132
+ ) -> Result < String , SignPsbtError > {
123
133
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 ( ) )
129
137
}
130
138
139
+ /// Return the same URL as given with a NFC tap.
131
140
pub async fn nfc ( & self ) -> Result < String , CkTapError > {
132
141
let mut card = self . 0 . lock ( ) . await ;
133
142
let url = card. nfc ( ) . await ?;
0 commit comments