1
1
use std:: str:: FromStr ;
2
2
3
+ use crate :: { constants:: CURRENCY_CRSAT , util} ;
3
4
use async_trait:: async_trait;
4
5
use bcr_ebill_core:: {
5
6
PostalAddress , ServiceTraitBounds ,
@@ -9,8 +10,9 @@ use bcr_ebill_core::{
9
10
} ;
10
11
use bcr_wdc_key_client:: KeyClient ;
11
12
use bcr_wdc_quote_client:: QuoteClient ;
13
+ use bcr_wdc_swap_client:: SwapClient ;
12
14
use bcr_wdc_webapi:: quotes:: { BillInfo , ResolveOffer , StatusReply } ;
13
- use cashu:: { nut00 as cdk00, nut01 as cdk01, nut02 as cdk02} ;
15
+ use cashu:: { ProofsMethods , State , nut00 as cdk00, nut01 as cdk01, nut02 as cdk02} ;
14
16
use thiserror:: Error ;
15
17
16
18
/// Generic result type
@@ -43,34 +45,51 @@ pub enum Error {
43
45
/// all errors originating from invalid keyset ids
44
46
#[ error( "External Mint Invalid KeySet Id Error" ) ]
45
47
InvalidKeySetId ,
48
+ /// all errors originating from invalid tokens
49
+ #[ error( "External Mint Invalid Token Error" ) ]
50
+ InvalidToken ,
51
+ /// all errors originating from tokens and mints not matching
52
+ #[ error( "External Mint Token and Mint don't match Error" ) ]
53
+ TokenAndMintDontMatch ,
46
54
/// all errors originating from blind message generation
47
55
#[ error( "External Mint BlindMessage Error" ) ]
48
56
BlindMessage ,
57
+ /// an error constructing proofs from minting
58
+ #[ error( "External Mint ProofConstruction Error" ) ]
59
+ ProofConstruction ,
60
+ /// an error minting
61
+ #[ error( "External Mint Minting Error" ) ]
62
+ Minting ,
49
63
/// all errors originating from the quote client
50
64
#[ error( "External Mint Quote Client Error" ) ]
51
65
QuoteClient ,
52
66
/// all errors originating from the key client
53
67
#[ error( "External Mint Key Client Error" ) ]
54
68
KeyClient ,
69
+ /// all errors originating from the swap client
70
+ #[ error( "External Mint Swap Client Error" ) ]
71
+ SwapClient ,
55
72
}
56
73
57
74
#[ cfg( test) ]
58
75
use mockall:: automock;
59
76
60
- use crate :: { constants:: CURRENCY_CRSAT , util} ;
61
-
62
77
#[ cfg_attr( test, automock) ]
63
78
#[ cfg_attr( target_arch = "wasm32" , async_trait( ?Send ) ) ]
64
79
#[ cfg_attr( not( target_arch = "wasm32" ) , async_trait) ]
65
80
pub trait MintClientApi : ServiceTraitBounds {
81
+ /// Check if the given proofs were already spent
82
+ async fn check_if_proofs_are_spent ( & self , mint_url : & str , proofs : & str ) -> Result < bool > ;
66
83
/// Mint and return encoded token
67
84
async fn mint (
68
85
& self ,
69
86
mint_url : & str ,
70
87
keyset : cdk02:: KeySet ,
71
- discounted_amount : u64 ,
72
88
quote_id : & str ,
73
89
private_key : & str ,
90
+ blinded_messages : Vec < cashu:: BlindedMessage > ,
91
+ secrets : Vec < cashu:: secret:: Secret > ,
92
+ rs : Vec < cashu:: SecretKey > ,
74
93
) -> Result < String > ;
75
94
/// Check keyset info for a given keyset id with a given mint
76
95
async fn get_keyset_info ( & self , mint_url : & str , keyset_id : & str ) -> Result < cdk02:: KeySet > ;
@@ -125,47 +144,86 @@ impl MintClient {
125
144
) ;
126
145
Ok ( key_client)
127
146
}
147
+
148
+ pub fn swap_client ( & self , mint_url : & str ) -> Result < SwapClient > {
149
+ let swap_client = bcr_wdc_swap_client:: SwapClient :: new (
150
+ reqwest:: Url :: parse ( mint_url) . map_err ( |_| Error :: InvalidMintUrl ) ?,
151
+ ) ;
152
+ Ok ( swap_client)
153
+ }
128
154
}
129
155
130
156
#[ cfg_attr( target_arch = "wasm32" , async_trait( ?Send ) ) ]
131
157
#[ cfg_attr( not( target_arch = "wasm32" ) , async_trait) ]
132
158
impl MintClientApi for MintClient {
159
+ async fn check_if_proofs_are_spent ( & self , mint_url : & str , proofs : & str ) -> Result < bool > {
160
+ let token_mint_url =
161
+ cashu:: MintUrl :: from_str ( mint_url) . map_err ( |_| Error :: InvalidMintUrl ) ?;
162
+ let token = cashu:: Token :: from_str ( proofs) . map_err ( |_| Error :: InvalidToken ) ?;
163
+
164
+ if let cashu:: Token :: TokenV3 ( token_v3) = token {
165
+ if let Some ( token_for_mint) = token_v3
166
+ . token
167
+ . into_iter ( )
168
+ . find ( |t| t. mint == token_mint_url)
169
+ {
170
+ let ys = token_for_mint. proofs . ys ( ) . map_err ( |_| Error :: PubKey ) ?;
171
+ let proof_states =
172
+ self . swap_client ( mint_url) ?
173
+ . check_state ( ys)
174
+ . await
175
+ . map_err ( |e| {
176
+ log:: error!( "Error checking if proofs are spent at {mint_url}: {e}" ) ;
177
+ Error :: SwapClient
178
+ } ) ?;
179
+ // all proofs have to be spent
180
+ let proofs_spent = proof_states
181
+ . iter ( )
182
+ . all ( |ps| matches ! ( ps. state, State :: Spent ) ) ;
183
+ Ok ( proofs_spent)
184
+ } else {
185
+ Err ( Error :: InvalidToken . into ( ) )
186
+ }
187
+ } else {
188
+ Err ( Error :: InvalidToken . into ( ) )
189
+ }
190
+ }
191
+
133
192
async fn mint (
134
193
& self ,
135
194
mint_url : & str ,
136
195
keyset : cdk02:: KeySet ,
137
- discounted_amount : u64 ,
138
196
quote_id : & str ,
139
197
private_key : & str ,
198
+ blinded_messages : Vec < cashu:: BlindedMessage > ,
199
+ secrets : Vec < cashu:: secret:: Secret > ,
200
+ rs : Vec < cashu:: SecretKey > ,
140
201
) -> Result < String > {
202
+ let token_mint_url =
203
+ cashu:: MintUrl :: from_str ( mint_url) . map_err ( |_| Error :: InvalidMintUrl ) ?;
141
204
let secret_key = cdk01:: SecretKey :: from_hex ( private_key) . map_err ( |_| Error :: PrivateKey ) ?;
142
205
let qid = uuid:: Uuid :: from_str ( quote_id) . map_err ( |_| Error :: InvalidMintRequestId ) ?;
143
206
144
- // create blinded messages
145
- let amounts: Vec < cashu:: Amount > = cashu:: Amount :: from ( discounted_amount) . split ( ) ;
146
- let blinds = generate_blinds ( keyset. id , & amounts) ?;
147
- let blinded_messages = blinds. iter ( ) . map ( |b| b. 0 . clone ( ) ) . collect :: < Vec < _ > > ( ) ;
148
-
149
207
// mint
150
208
let blinded_signatures = self
151
209
. key_client ( mint_url) ?
152
210
. mint ( qid, blinded_messages, secret_key)
153
211
. await
154
212
. map_err ( |e| {
155
213
log:: error!( "Error minting at mint {mint_url}: {e}" ) ;
156
- Error :: KeyClient
214
+ Error :: Minting
157
215
} ) ?;
158
216
159
217
// create proofs
160
- let secrets = blinds. iter ( ) . map ( |b| b. 1 . clone ( ) ) . collect :: < Vec < _ > > ( ) ;
161
- let rs = blinds. iter ( ) . map ( |b| b. 2 . clone ( ) ) . collect :: < Vec < _ > > ( ) ;
162
- let proofs =
163
- cashu:: dhke:: construct_proofs ( blinded_signatures, rs, secrets, & keyset. keys ) . unwrap ( ) ;
218
+ let proofs = cashu:: dhke:: construct_proofs ( blinded_signatures, rs, secrets, & keyset. keys )
219
+ . map_err ( |e| {
220
+ log:: error!( "Couldn't construct proofs for {quote_id}: {e}" ) ;
221
+ Error :: ProofConstruction
222
+ } ) ?;
164
223
165
224
// generate token from proofs
166
- let mint_url = cashu:: MintUrl :: from_str ( mint_url) . map_err ( |_| Error :: InvalidMintUrl ) ?;
167
225
let token = cdk00:: Token :: new (
168
- mint_url ,
226
+ token_mint_url ,
169
227
proofs,
170
228
None ,
171
229
cashu:: CurrencyUnit :: Custom ( CURRENCY_CRSAT . into ( ) ) ,
@@ -276,20 +334,25 @@ impl MintClientApi for MintClient {
276
334
277
335
pub fn generate_blinds (
278
336
keyset_id : cashu:: Id ,
279
- amounts : & [ cashu:: Amount ] ,
280
- ) -> Result <
281
- Vec < (
282
- cashu:: BlindedMessage ,
283
- cashu:: secret:: Secret ,
284
- cashu:: SecretKey ,
285
- ) > ,
286
- > {
287
- let mut blinds = Vec :: new ( ) ;
337
+ discounted_amount : u64 ,
338
+ ) -> Result < (
339
+ Vec < cashu:: BlindedMessage > ,
340
+ Vec < cashu:: secret:: Secret > ,
341
+ Vec < cashu:: SecretKey > ,
342
+ ) > {
343
+ let amounts: Vec < cashu:: Amount > = cashu:: Amount :: from ( discounted_amount) . split ( ) ;
344
+ let mut blinded_messages = Vec :: with_capacity ( amounts. len ( ) ) ;
345
+ let mut secrets = Vec :: with_capacity ( amounts. len ( ) ) ;
346
+ let mut rs = Vec :: with_capacity ( amounts. len ( ) ) ;
347
+
288
348
for amount in amounts {
289
- let blind = generate_blind ( keyset_id, * amount) ?;
290
- blinds. push ( blind) ;
349
+ let blind = generate_blind ( keyset_id, amount) ?;
350
+ blinded_messages. push ( blind. 0 ) ;
351
+ secrets. push ( blind. 1 ) ;
352
+ rs. push ( blind. 2 ) ;
291
353
}
292
- Ok ( blinds)
354
+
355
+ Ok ( ( blinded_messages, secrets, rs) )
293
356
}
294
357
295
358
pub fn generate_blind (
@@ -300,7 +363,7 @@ pub fn generate_blind(
300
363
cashu:: secret:: Secret ,
301
364
cashu:: SecretKey ,
302
365
) > {
303
- let secret = cashu:: secret:: Secret :: new ( rand:: random :: < u64 > ( ) . to_string ( ) ) ;
366
+ let secret = cashu:: secret:: Secret :: new ( hex :: encode ( rand:: random :: < [ u8 ; 32 ] > ( ) ) ) ;
304
367
let ( b_, r) =
305
368
cashu:: dhke:: blind_message ( secret. as_bytes ( ) , None ) . map_err ( |_| Error :: BlindMessage ) ?;
306
369
Ok ( ( cashu:: BlindedMessage :: new ( amount, kid, b_) , secret, r) )
0 commit comments