Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023-2025 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_withdraw.c
19 : * @brief Implementation of /withdraw requests
20 : * @author Özgür Kesim
21 : */
22 : /**
23 : * We want the "dangerous" exports here as these are OUR exports
24 : * and we want to check that the prototypes match.
25 : */
26 : #define TALER_TESTING_EXPORTS_DANGEROUS 1
27 : #include "platform.h"
28 : #include <gnunet/gnunet_common.h>
29 : #include <jansson.h>
30 : #include <microhttpd.h> /* just for HTTP status codes */
31 : #include <gnunet/gnunet_util_lib.h>
32 : #include <gnunet/gnunet_json_lib.h>
33 : #include <gnunet/gnunet_curl_lib.h>
34 : #include <sys/wait.h>
35 : #include "taler_curl_lib.h"
36 : #include "taler_error_codes.h"
37 : #include "taler_json_lib.h"
38 : #include "taler_exchange_service.h"
39 : #include "exchange_api_common.h"
40 : #include "exchange_api_handle.h"
41 : #include "taler_signatures.h"
42 : #include "exchange_api_curl_defaults.h"
43 : #include "taler_util.h"
44 :
45 : /**
46 : * A CoinCandidate is populated from a master secret.
47 : * The data is copied from and generated out of the client's input.
48 : */
49 : struct CoinCandidate
50 : {
51 : /**
52 : * The details derived form the master secrets
53 : */
54 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;
55 :
56 : /**
57 : * Blinded hash of the coin
58 : **/
59 : struct TALER_BlindedCoinHashP blinded_coin_h;
60 :
61 : };
62 :
63 :
64 : /**
65 : * Closure for a call to /blinding-prepare, contains data that is needed to process
66 : * the result.
67 : */
68 : struct BlindingPrepareClosure
69 : {
70 : /**
71 : * Number of coins in the blinding-prepare step.
72 : * Not that this number might be smaller than the total number
73 : * of coins in the withdraw, as the prepare is only necessary
74 : * for CS denominations
75 : */
76 : size_t num_prepare_coins;
77 :
78 : /**
79 : * Array of @e num_prepare_coins of data per coin
80 : */
81 : struct BlindingPrepareCoinData
82 : {
83 : /**
84 : * Pointer to the candidate in CoinData.candidates,
85 : * to continue to build its contents based on the results from /blinding-prepare
86 : */
87 : struct CoinCandidate *candidate;
88 :
89 : /**
90 : * Planchet to finally generate in the corresponding candidate
91 : * in CoindData.planchet_details
92 : */
93 : struct TALER_PlanchetDetail *planchet;
94 :
95 : /**
96 : * Denomination information, needed for the
97 : * step after /blinding-prepare
98 : */
99 : const struct TALER_DenominationPublicKey *denom_pub;
100 :
101 : /**
102 : * True, if denomination supports age restriction
103 : */
104 : bool age_denom;
105 :
106 : /**
107 : * The index into the array of returned values from the call to
108 : * /blinding-prepare that are to be used for this coin.
109 : */
110 : size_t cs_idx;
111 :
112 : } *coins;
113 :
114 : /**
115 : * Number of seeds requested. This may differ from @e num_prepare_coins
116 : * in case of a withdraw with required age proof, in which case
117 : * @e num_prepare_coins = TALER_CNC_KAPPA * @e num_seeds
118 : */
119 : size_t num_nonces;
120 :
121 : /**
122 : * Array of @e num_nonces calculated nonces.
123 : */
124 : union GNUNET_CRYPTO_BlindSessionNonce *nonces;
125 :
126 : /**
127 : * Handler to the originating call to /withdraw, needed to either
128 : * cancel the running withdraw request (on failure of the current call
129 : * to /blinding-prepare), or to eventually perform the protocol, once all
130 : * blinding-prepare requests have successfully finished.
131 : */
132 : struct TALER_EXCHANGE_WithdrawHandle *withdraw_handle;
133 :
134 : };
135 :
136 :
137 : /**
138 : * Data we keep per coin in the batch.
139 : * This is copied from and generated out of the input provided
140 : * by the client.
141 : */
142 : struct CoinData
143 : {
144 : /**
145 : * The denomination of the coin.
146 : */
147 : struct TALER_EXCHANGE_DenomPublicKey denom_pub;
148 :
149 : /**
150 : * The Candidates for the coin. If the batch is not age-restricted,
151 : * only index 0 is used.
152 : */
153 : struct CoinCandidate candidates[TALER_CNC_KAPPA];
154 :
155 : /**
156 : * Details of the planchet(s). If the batch is not age-restricted,
157 : * only index 0 is used.
158 : */
159 : struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
160 : };
161 :
162 :
163 : /**
164 : * A /withdraw request-handle for calls with pre-blinded planchets.
165 : * Returned by TALER_EXCHANGE_withdraw_blinded.
166 : */
167 : struct TALER_EXCHANGE_WithdrawBlindedHandle
168 : {
169 :
170 : /**
171 : * Reserve private key.
172 : */
173 : const struct TALER_ReservePrivateKeyP *reserve_priv;
174 :
175 : /**
176 : * Reserve public key, calculated
177 : */
178 : struct TALER_ReservePublicKeyP reserve_pub;
179 :
180 : /**
181 : * Signature of the reserve for the request, calculated after all
182 : * parameters for the coins are collected.
183 : */
184 : struct TALER_ReserveSignatureP reserve_sig;
185 :
186 : /*
187 : * The denomination keys of the exchange
188 : */
189 : struct TALER_EXCHANGE_Keys *keys;
190 :
191 : /**
192 : * The hash of all the planchets
193 : */
194 : struct TALER_HashBlindedPlanchetsP planchets_h;
195 :
196 : /**
197 : * Seed used for the derival of blinding factors for denominations
198 : * with Clause-Schnorr cipher. We derive this from the master seed
199 : * for the withdraw, but independent from the other planchet seeds.
200 : */
201 : const struct TALER_BlindingMasterSeedP *blinding_seed;
202 :
203 : /**
204 : * Total amount requested (without fee).
205 : */
206 : struct TALER_Amount amount;
207 :
208 : /**
209 : * Total withdraw fee
210 : */
211 : struct TALER_Amount fee;
212 :
213 : /**
214 : * Is this call for age-restriced coins, with age proof?
215 : */
216 : bool with_age_proof;
217 :
218 : /**
219 : * If @e with_age_proof is true or @max_age is > 0,
220 : * the age mask to use, extracted from the denominations.
221 : * MUST be the same for all denominations.
222 : */
223 : struct TALER_AgeMask age_mask;
224 :
225 : /**
226 : * The maximum age to commit to. If @e with_age_proof
227 : * is true, the client will need to proof the correct setting
228 : * of age-restriction on the coins via an additional call
229 : * to /reveal-withdraw.
230 : */
231 : uint8_t max_age;
232 :
233 : /**
234 : * If @e with_age_proof is true, the hash of all the selected planchets
235 : */
236 : struct TALER_HashBlindedPlanchetsP selected_h;
237 :
238 : /**
239 : * Length of the either the @e blinded.input or
240 : * the @e blinded.with_age_proof_input array,
241 : * depending on @e with_age_proof.
242 : */
243 : size_t num_input;
244 :
245 : union
246 : {
247 : /**
248 : * The blinded planchet input candidates for age-restricted coins
249 : * for the call to /withdraw
250 : */
251 : const struct
252 : TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input;
253 :
254 : /**
255 : * The blinded planchet input for the call to /withdraw via
256 : * TALER_EXCHANGE_withdraw_blinded, for age-unrestricted coins.
257 : */
258 : const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input;
259 :
260 : } blinded;
261 :
262 : /**
263 : * The url for this request.
264 : */
265 : char *request_url;
266 :
267 : /**
268 : * Context for curl.
269 : */
270 : struct GNUNET_CURL_Context *curl_ctx;
271 :
272 : /**
273 : * CURL handle for the request job.
274 : */
275 : struct GNUNET_CURL_Job *job;
276 :
277 : /**
278 : * Post Context
279 : */
280 : struct TALER_CURL_PostContext post_ctx;
281 :
282 : /**
283 : * Function to call with withdraw response results.
284 : */
285 : TALER_EXCHANGE_WithdrawBlindedCallback callback;
286 :
287 : /**
288 : * Closure for @e blinded_callback
289 : */
290 : void *callback_cls;
291 : };
292 :
293 : /**
294 : * A /withdraw request-handle for calls from
295 : * a wallet, i. e. when blinding data is available.
296 : */
297 : struct TALER_EXCHANGE_WithdrawHandle
298 : {
299 :
300 : /**
301 : * The base-URL of the exchange.
302 : */
303 : const char *exchange_url;
304 :
305 : /**
306 : * Seed to derive of all seeds for the coins.
307 : */
308 : struct TALER_WithdrawMasterSeedP seed;
309 :
310 : /**
311 : * If @e with_age_proof is true, the derived TALER_CNC_KAPPA many
312 : * seeds for candidate batches.
313 : */
314 : struct TALER_KappaWithdrawMasterSeedP kappa_seed;
315 :
316 : /**
317 : * True if @e blinding_seed is filled, that is, if
318 : * any of the denominations is of cipher type CS
319 : */
320 : bool has_blinding_seed;
321 :
322 : /**
323 : * Seed used for the derivation of blinding factors for denominations
324 : * with Clause-Schnorr cipher. We derive this from the master seed
325 : * for the withdraw, but independent from the other planchet seeds.
326 : * Only valid when @e has_blinding_seed is true;
327 : */
328 : struct TALER_BlindingMasterSeedP blinding_seed;
329 :
330 : /**
331 : * Reserve private key.
332 : */
333 : const struct TALER_ReservePrivateKeyP *reserve_priv;
334 :
335 : /**
336 : * Reserve public key, calculated
337 : */
338 : struct TALER_ReservePublicKeyP reserve_pub;
339 :
340 : /**
341 : * Signature of the reserve for the request, calculated after all
342 : * parameters for the coins are collected.
343 : */
344 : struct TALER_ReserveSignatureP reserve_sig;
345 :
346 : /*
347 : * The denomination keys of the exchange
348 : */
349 : struct TALER_EXCHANGE_Keys *keys;
350 :
351 : /**
352 : * True, if the withdraw is for age-restricted coins, with age-proof.
353 : * The denominations MUST support age restriction.
354 : */
355 : bool with_age_proof;
356 :
357 : /**
358 : * If @e with_age_proof is true, the age mask, extracted
359 : * from the denominations.
360 : * MUST be the same for all denominations.
361 : *
362 : */
363 : struct TALER_AgeMask age_mask;
364 :
365 : /**
366 : * The maximum age to commit to. If @e with_age_proof
367 : * is true, the client will need to proof the correct setting
368 : * of age-restriction on the coins via an additional call
369 : * to /reveal-withdraw.
370 : */
371 : uint8_t max_age;
372 :
373 : /**
374 : * Length of the @e coin_data Array
375 : */
376 : size_t num_coins;
377 :
378 : /**
379 : * Array of per-coin data
380 : */
381 : struct CoinData *coin_data;
382 :
383 : /**
384 : * Context for curl.
385 : */
386 : struct GNUNET_CURL_Context *curl_ctx;
387 :
388 : /**
389 : * Function to call with withdraw response results.
390 : */
391 : TALER_EXCHANGE_WithdrawCallback callback;
392 :
393 : /**
394 : * Closure for @e callback
395 : */
396 : void *callback_cls;
397 :
398 : /* The handler for the call to /blinding-prepare, needed for CS denominations */
399 : struct TALER_EXCHANGE_BlindingPrepareHandle *blinding_prepare_handle;
400 :
401 : /* The Handler for the actual call to the exchange */
402 : struct TALER_EXCHANGE_WithdrawBlindedHandle *withdraw_blinded_handle;
403 : };
404 :
405 :
406 : /**
407 : * We got a 200 OK response for the /withdraw operation.
408 : * Extract the signatures and return them to the caller.
409 : *
410 : * @param wbh operation handle
411 : * @param j_response reply from the exchange
412 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
413 : */
414 : static enum GNUNET_GenericReturnValue
415 61 : withdraw_blinded_ok (
416 : struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh,
417 : const json_t *j_response)
418 : {
419 61 : struct TALER_EXCHANGE_WithdrawBlindedResponse response = {
420 : .hr.reply = j_response,
421 : .hr.http_status = MHD_HTTP_OK,
422 : };
423 : const json_t *j_sigs;
424 : struct GNUNET_JSON_Specification spec[] = {
425 61 : GNUNET_JSON_spec_array_const ("ev_sigs",
426 : &j_sigs),
427 61 : GNUNET_JSON_spec_end ()
428 : };
429 :
430 61 : if (GNUNET_OK !=
431 61 : GNUNET_JSON_parse (j_response,
432 : spec,
433 : NULL, NULL))
434 : {
435 0 : GNUNET_break_op (0);
436 0 : return GNUNET_SYSERR;
437 : }
438 :
439 61 : if (wbh->num_input != json_array_size (j_sigs))
440 : {
441 : /* Number of coins generated does not match our expectation */
442 0 : GNUNET_break_op (0);
443 0 : return GNUNET_SYSERR;
444 : }
445 :
446 61 : {
447 61 : struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input];
448 :
449 61 : memset (denoms_sig,
450 : 0,
451 : sizeof(denoms_sig));
452 :
453 : /* Reconstruct the coins and unblind the signatures */
454 : {
455 : json_t *j_sig;
456 : size_t i;
457 :
458 124 : json_array_foreach (j_sigs, i, j_sig)
459 : {
460 : struct GNUNET_JSON_Specification ispec[] = {
461 63 : TALER_JSON_spec_blinded_denom_sig (NULL,
462 : &denoms_sig[i]),
463 63 : GNUNET_JSON_spec_end ()
464 : };
465 :
466 63 : if (GNUNET_OK !=
467 63 : GNUNET_JSON_parse (j_sig,
468 : ispec,
469 : NULL, NULL))
470 : {
471 0 : GNUNET_break_op (0);
472 0 : return GNUNET_SYSERR;
473 : }
474 : }
475 : }
476 :
477 61 : response.details.ok.num_sigs = wbh->num_input;
478 61 : response.details.ok.blinded_denom_sigs = denoms_sig;
479 61 : response.details.ok.planchets_h = wbh->planchets_h;
480 61 : wbh->callback (
481 : wbh->callback_cls,
482 : &response);
483 : /* Make sure the callback isn't called again */
484 61 : wbh->callback = NULL;
485 : /* Free resources */
486 124 : for (size_t i = 0; i < wbh->num_input; i++)
487 63 : TALER_blinded_denom_sig_free (&denoms_sig[i]);
488 : }
489 :
490 61 : return GNUNET_OK;
491 : }
492 :
493 :
494 : /**
495 : * We got a 201 CREATED response for the /withdraw operation.
496 : * Extract the noreveal_index and return it to the caller.
497 : *
498 : * @param wbh operation handle
499 : * @param j_response reply from the exchange
500 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
501 : */
502 : static enum GNUNET_GenericReturnValue
503 3 : withdraw_blinded_created (
504 : struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh,
505 : const json_t *j_response)
506 : {
507 3 : struct TALER_EXCHANGE_WithdrawBlindedResponse response = {
508 : .hr.reply = j_response,
509 : .hr.http_status = MHD_HTTP_CREATED,
510 : .details.created.planchets_h = wbh->planchets_h,
511 3 : .details.created.num_coins = wbh->num_input,
512 : };
513 : struct TALER_ExchangeSignatureP exchange_sig;
514 : struct GNUNET_JSON_Specification spec[] = {
515 3 : GNUNET_JSON_spec_uint8 ("noreveal_index",
516 : &response.details.created.noreveal_index),
517 3 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
518 : &exchange_sig),
519 3 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
520 : &response.details.created.exchange_pub),
521 3 : GNUNET_JSON_spec_end ()
522 : };
523 :
524 3 : if (GNUNET_OK!=
525 3 : GNUNET_JSON_parse (j_response,
526 : spec,
527 : NULL, NULL))
528 : {
529 0 : GNUNET_break_op (0);
530 0 : return GNUNET_SYSERR;
531 : }
532 :
533 3 : if (GNUNET_OK !=
534 3 : TALER_exchange_online_withdraw_age_confirmation_verify (
535 3 : &wbh->planchets_h,
536 3 : response.details.created.noreveal_index,
537 : &response.details.created.exchange_pub,
538 : &exchange_sig))
539 : {
540 0 : GNUNET_break_op (0);
541 0 : return GNUNET_SYSERR;
542 :
543 : }
544 :
545 3 : wbh->callback (wbh->callback_cls,
546 : &response);
547 : /* make sure the callback isn't called again */
548 3 : wbh->callback = NULL;
549 :
550 3 : return GNUNET_OK;
551 : }
552 :
553 :
554 : /**
555 : * Function called when we're done processing the
556 : * HTTP /withdraw request.
557 : *
558 : * @param cls the `struct TALER_EXCHANGE_WithdrawBlindedHandle`
559 : * @param response_code The HTTP response code
560 : * @param response response data
561 : */
562 : static void
563 75 : handle_withdraw_blinded_finished (
564 : void *cls,
565 : long response_code,
566 : const void *response)
567 : {
568 75 : struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = cls;
569 75 : const json_t *j_response = response;
570 75 : struct TALER_EXCHANGE_WithdrawBlindedResponse wbr = {
571 : .hr.reply = j_response,
572 75 : .hr.http_status = (unsigned int) response_code
573 : };
574 :
575 75 : wbh->job = NULL;
576 75 : switch (response_code)
577 : {
578 0 : case 0:
579 0 : wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
580 0 : break;
581 61 : case MHD_HTTP_OK:
582 : {
583 61 : if (GNUNET_OK !=
584 61 : withdraw_blinded_ok (
585 : wbh,
586 : j_response))
587 : {
588 0 : GNUNET_break_op (0);
589 0 : wbr.hr.http_status = 0;
590 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
591 0 : break;
592 : }
593 61 : GNUNET_assert (NULL == wbh->callback);
594 61 : TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
595 64 : return;
596 : }
597 3 : case MHD_HTTP_CREATED:
598 3 : if (GNUNET_OK !=
599 3 : withdraw_blinded_created (
600 : wbh,
601 : j_response))
602 : {
603 0 : GNUNET_break_op (0);
604 0 : wbr.hr.http_status = 0;
605 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
606 0 : break;
607 : }
608 3 : GNUNET_assert (NULL == wbh->callback);
609 3 : TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
610 3 : return;
611 0 : case MHD_HTTP_BAD_REQUEST:
612 : /* This should never happen, either us or the exchange is buggy
613 : (or API version conflict); just pass JSON reply to the application */
614 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
615 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
616 0 : break;
617 0 : case MHD_HTTP_FORBIDDEN:
618 0 : GNUNET_break_op (0);
619 : /* Nothing really to verify, exchange says one of the signatures is
620 : invalid; as we checked them, this should never happen, we
621 : should pass the JSON reply to the application */
622 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
623 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
624 0 : break;
625 0 : case MHD_HTTP_NOT_FOUND:
626 : /* Nothing really to verify, the exchange basically just says
627 : that it doesn't know this reserve. Can happen if we
628 : query before the wire transfer went through.
629 : We should simply pass the JSON reply to the application. */
630 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
631 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
632 0 : break;
633 7 : case MHD_HTTP_CONFLICT:
634 : /* The age requirements might not have been met */
635 7 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
636 7 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
637 7 : break;
638 0 : case MHD_HTTP_GONE:
639 : /* could happen if denomination was revoked */
640 : /* Note: one might want to check /keys for revocation
641 : signature here, alas tricky in case our /keys
642 : is outdated => left to clients */
643 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
644 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
645 0 : break;
646 4 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
647 : /* only validate reply is well-formed */
648 : {
649 : struct GNUNET_JSON_Specification spec[] = {
650 4 : GNUNET_JSON_spec_fixed_auto (
651 : "h_payto",
652 : &wbr.details.unavailable_for_legal_reasons.h_payto),
653 4 : GNUNET_JSON_spec_uint64 (
654 : "requirement_row",
655 : &wbr.details.unavailable_for_legal_reasons.requirement_row),
656 4 : GNUNET_JSON_spec_end ()
657 : };
658 :
659 4 : if (GNUNET_OK !=
660 4 : GNUNET_JSON_parse (j_response,
661 : spec,
662 : NULL, NULL))
663 : {
664 0 : GNUNET_break_op (0);
665 0 : wbr.hr.http_status = 0;
666 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
667 0 : break;
668 : }
669 4 : break;
670 : }
671 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
672 : /* Server had an internal issue; we should retry, but this API
673 : leaves this to the application */
674 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
675 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
676 0 : break;
677 0 : default:
678 : /* unexpected response code */
679 0 : GNUNET_break_op (0);
680 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
681 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
682 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
683 : "Unexpected response code %u/%d for exchange withdraw\n",
684 : (unsigned int) response_code,
685 : (int) wbr.hr.ec);
686 0 : break;
687 : }
688 11 : wbh->callback (wbh->callback_cls,
689 : &wbr);
690 11 : TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
691 : }
692 :
693 :
694 : /**
695 : * Runs the actual withdraw operation with the blinded planchets.
696 : *
697 : * @param[in,out] wbh withdraw blinded handle
698 : */
699 : static void
700 75 : perform_withdraw_protocol (
701 : struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh)
702 : {
703 : #define FAIL_IF(cond) \
704 : do { \
705 : if ((cond)) \
706 : { \
707 : GNUNET_break (! (cond)); \
708 : goto ERROR; \
709 : } \
710 : } while (0)
711 :
712 75 : json_t *j_denoms = NULL;
713 75 : json_t *j_planchets = NULL;
714 75 : json_t *j_request_body = NULL;
715 75 : CURL *curlh = NULL;
716 75 : struct GNUNET_HashContext *coins_hctx = NULL;
717 : struct TALER_BlindedCoinHashP bch;
718 :
719 75 : GNUNET_assert (0 < wbh->num_input);
720 :
721 75 : FAIL_IF (GNUNET_OK !=
722 : TALER_amount_set_zero (wbh->keys->currency,
723 : &wbh->amount));
724 75 : FAIL_IF (GNUNET_OK !=
725 : TALER_amount_set_zero (wbh->keys->currency,
726 : &wbh->fee));
727 :
728 : /* Accumulate total value with fees */
729 156 : for (size_t i = 0; i < wbh->num_input; i++)
730 : {
731 81 : const struct TALER_EXCHANGE_DenomPublicKey *dpub =
732 81 : wbh->with_age_proof ?
733 81 : wbh->blinded.with_age_proof_input[i].denom_pub :
734 72 : wbh->blinded.input[i].denom_pub;
735 :
736 81 : FAIL_IF (0 >
737 : TALER_amount_add (&wbh->amount,
738 : &wbh->amount,
739 : &dpub->value));
740 81 : FAIL_IF (0 >
741 : TALER_amount_add (&wbh->fee,
742 : &wbh->fee,
743 : &dpub->fees.withdraw));
744 :
745 81 : if (GNUNET_CRYPTO_BSA_CS ==
746 81 : dpub->key.bsign_pub_key->cipher)
747 38 : GNUNET_assert (NULL != wbh->blinding_seed);
748 :
749 : }
750 :
751 75 : if (wbh->with_age_proof || wbh->max_age > 0)
752 : {
753 5 : wbh->age_mask =
754 5 : wbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
755 :
756 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
757 : "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
758 : TALER_B2S (&wbh->reserve_pub),
759 : wbh->max_age);
760 : }
761 : else
762 : {
763 70 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
764 : "Attempting to withdraw from reserve %s\n",
765 : TALER_B2S (&wbh->reserve_pub));
766 : }
767 :
768 75 : coins_hctx = GNUNET_CRYPTO_hash_context_start ();
769 75 : FAIL_IF (NULL == coins_hctx);
770 :
771 75 : j_denoms = json_array ();
772 75 : j_planchets = json_array ();
773 75 : FAIL_IF ((NULL == j_denoms) ||
774 : (NULL == j_planchets));
775 :
776 156 : for (size_t i = 0; i< wbh->num_input; i++)
777 : {
778 : /* Build the denomination array */
779 81 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
780 81 : wbh->with_age_proof ?
781 81 : wbh->blinded.with_age_proof_input[i].denom_pub :
782 72 : wbh->blinded.input[i].denom_pub;
783 81 : const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
784 : json_t *jdenom;
785 :
786 : /* The mask must be the same for all coins */
787 81 : FAIL_IF (wbh->with_age_proof &&
788 : (wbh->age_mask.bits != denom_pub->key.age_mask.bits));
789 :
790 81 : jdenom = GNUNET_JSON_from_data_auto (denom_h);
791 81 : FAIL_IF (NULL == jdenom);
792 81 : FAIL_IF (0 > json_array_append_new (j_denoms,
793 : jdenom));
794 : }
795 :
796 :
797 : /* Build the planchet array and calculate the hash over all planchets. */
798 75 : if (! wbh->with_age_proof)
799 : {
800 142 : for (size_t i = 0; i< wbh->num_input; i++)
801 : {
802 72 : const struct TALER_PlanchetDetail *planchet =
803 72 : &wbh->blinded.input[i].planchet_details;
804 72 : json_t *jc = GNUNET_JSON_PACK (
805 : TALER_JSON_pack_blinded_planchet (
806 : NULL,
807 : &planchet->blinded_planchet));
808 72 : FAIL_IF (NULL == jc);
809 72 : FAIL_IF (0 > json_array_append_new (j_planchets,
810 : jc));
811 :
812 72 : TALER_coin_ev_hash (&planchet->blinded_planchet,
813 : &planchet->denom_pub_hash,
814 : &bch);
815 :
816 72 : GNUNET_CRYPTO_hash_context_read (coins_hctx,
817 : &bch,
818 : sizeof(bch));
819 : }
820 : }
821 : else
822 : { /* Age restricted case with required age-proof. */
823 :
824 : /**
825 : * We collect the run of all coin candidates for the same γ index
826 : * first, then γ+1 etc.
827 : */
828 20 : for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
829 : {
830 : struct GNUNET_HashContext *batch_ctx;
831 : struct TALER_BlindedCoinHashP batch_h;
832 :
833 15 : batch_ctx = GNUNET_CRYPTO_hash_context_start ();
834 15 : FAIL_IF (NULL == batch_ctx);
835 :
836 42 : for (size_t i = 0; i< wbh->num_input; i++)
837 : {
838 27 : const struct TALER_PlanchetDetail *planchet =
839 27 : &wbh->blinded.with_age_proof_input[i].planchet_details[k];
840 27 : json_t *jc = GNUNET_JSON_PACK (
841 : TALER_JSON_pack_blinded_planchet (
842 : NULL,
843 : &planchet->blinded_planchet));
844 :
845 27 : FAIL_IF (NULL == jc);
846 27 : FAIL_IF (0 > json_array_append_new (
847 : j_planchets,
848 : jc));
849 :
850 27 : TALER_coin_ev_hash (
851 : &planchet->blinded_planchet,
852 : &planchet->denom_pub_hash,
853 : &bch);
854 :
855 27 : GNUNET_CRYPTO_hash_context_read (
856 : batch_ctx,
857 : &bch,
858 : sizeof(bch));
859 : }
860 :
861 15 : GNUNET_CRYPTO_hash_context_finish (
862 : batch_ctx,
863 : &batch_h.hash);
864 15 : GNUNET_CRYPTO_hash_context_read (
865 : coins_hctx,
866 : &batch_h,
867 : sizeof(batch_h));
868 : }
869 : }
870 :
871 : /* Build the hash of the planchets */
872 75 : GNUNET_CRYPTO_hash_context_finish (
873 : coins_hctx,
874 : &wbh->planchets_h.hash);
875 75 : coins_hctx = NULL;
876 :
877 : /* Sign the request */
878 145 : TALER_wallet_withdraw_sign (
879 75 : &wbh->amount,
880 75 : &wbh->fee,
881 75 : &wbh->planchets_h,
882 : wbh->blinding_seed,
883 75 : wbh->with_age_proof ? &wbh->age_mask: NULL,
884 75 : wbh->with_age_proof ? wbh->max_age : 0,
885 : wbh->reserve_priv,
886 : &wbh->reserve_sig);
887 :
888 : /* Initiate the POST-request */
889 75 : j_request_body = GNUNET_JSON_PACK (
890 : GNUNET_JSON_pack_string ("cipher",
891 : "ED25519"),
892 : GNUNET_JSON_pack_data_auto ("reserve_pub",
893 : &wbh->reserve_pub),
894 : GNUNET_JSON_pack_array_steal ("denoms_h",
895 : j_denoms),
896 : GNUNET_JSON_pack_array_steal ("coin_evs",
897 : j_planchets),
898 : GNUNET_JSON_pack_allow_null (
899 : wbh->with_age_proof
900 : ? GNUNET_JSON_pack_int64 ("max_age",
901 : wbh->max_age)
902 : : GNUNET_JSON_pack_string ("max_age",
903 : NULL) ),
904 : GNUNET_JSON_pack_data_auto ("reserve_sig",
905 : &wbh->reserve_sig));
906 75 : FAIL_IF (NULL == j_request_body);
907 :
908 75 : if (NULL != wbh->blinding_seed)
909 : {
910 35 : json_t *j_seed = GNUNET_JSON_PACK (
911 : GNUNET_JSON_pack_data_auto ("blinding_seed",
912 : wbh->blinding_seed));
913 35 : GNUNET_assert (NULL != j_seed);
914 35 : GNUNET_assert (0 ==
915 : json_object_update_new (
916 : j_request_body,
917 : j_seed));
918 : }
919 :
920 75 : curlh = TALER_EXCHANGE_curl_easy_get_ (wbh->request_url);
921 75 : FAIL_IF (NULL == curlh);
922 75 : FAIL_IF (GNUNET_OK !=
923 : TALER_curl_easy_post (
924 : &wbh->post_ctx,
925 : curlh,
926 : j_request_body));
927 75 : json_decref (j_request_body);
928 75 : j_request_body = NULL;
929 :
930 150 : wbh->job = GNUNET_CURL_job_add2 (
931 : wbh->curl_ctx,
932 : curlh,
933 75 : wbh->post_ctx.headers,
934 : &handle_withdraw_blinded_finished,
935 : wbh);
936 75 : FAIL_IF (NULL == wbh->job);
937 :
938 : /* No errors, return */
939 75 : return;
940 :
941 0 : ERROR:
942 0 : if (NULL != coins_hctx)
943 0 : GNUNET_CRYPTO_hash_context_abort (coins_hctx);
944 0 : if (NULL != j_denoms)
945 0 : json_decref (j_denoms);
946 0 : if (NULL != j_planchets)
947 0 : json_decref (j_planchets);
948 0 : if (NULL != j_request_body)
949 0 : json_decref (j_request_body);
950 0 : if (NULL != curlh)
951 0 : curl_easy_cleanup (curlh);
952 0 : TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
953 0 : return;
954 : #undef FAIL_IF
955 : }
956 :
957 :
958 : /**
959 : * @brief Callback to copy the results from the call to TALER_withdraw_blinded
960 : * in the non-age-restricted case to the result for the originating call from TALER_withdraw.
961 : *
962 : * @param cls struct TALER_WithdrawHandle
963 : * @param wbr The response
964 : */
965 : static void
966 70 : copy_results (
967 : void *cls,
968 : const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr)
969 : {
970 : /* The original handle from the top-level call to withdraw */
971 70 : struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
972 70 : struct TALER_EXCHANGE_WithdrawResponse resp = {
973 : .hr = wbr->hr,
974 : };
975 :
976 70 : wh->withdraw_blinded_handle = NULL;
977 :
978 : /**
979 : * The withdraw protocol has been performed with blinded data.
980 : * Now the response can be copied as is, except for the MHD_HTTP_OK case,
981 : * in which we now need to perform the unblinding.
982 : */
983 70 : switch (wbr->hr.http_status)
984 : {
985 61 : case MHD_HTTP_OK:
986 61 : {
987 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails
988 61 : details[GNUNET_NZL (wh->num_coins)];
989 61 : bool ok = true;
990 :
991 61 : GNUNET_assert (wh->num_coins == wbr->details.ok.num_sigs);
992 61 : memset (details,
993 : 0,
994 : sizeof(details));
995 61 : resp.details.ok.num_sigs = wbr->details.ok.num_sigs;
996 61 : resp.details.ok.coin_details = details;
997 61 : resp.details.ok.planchets_h = wbr->details.ok.planchets_h;
998 124 : for (size_t n = 0; n<wh->num_coins; n++)
999 : {
1000 63 : const struct TALER_BlindedDenominationSignature *bsig =
1001 63 : &wbr->details.ok.blinded_denom_sigs[n];
1002 63 : struct CoinData *cd = &wh->coin_data[n];
1003 63 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
1004 : struct TALER_FreshCoin fresh_coin;
1005 :
1006 63 : *coin = wh->coin_data[n].candidates[0].details;
1007 63 : coin->planchet = wh->coin_data[n].planchet_details[0];
1008 63 : GNUNET_CRYPTO_eddsa_key_get_public (
1009 63 : &coin->coin_priv.eddsa_priv,
1010 : &coin->coin_pub.eddsa_pub);
1011 :
1012 63 : if (GNUNET_OK !=
1013 63 : TALER_planchet_to_coin (&cd->denom_pub.key,
1014 : bsig,
1015 63 : &coin->blinding_key,
1016 63 : &coin->coin_priv,
1017 63 : &coin->h_age_commitment,
1018 63 : &coin->h_coin_pub,
1019 63 : &coin->blinding_values,
1020 : &fresh_coin))
1021 : {
1022 0 : resp.hr.http_status = 0;
1023 0 : resp.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
1024 0 : GNUNET_break_op (0);
1025 0 : ok = false;
1026 0 : break;
1027 : }
1028 63 : coin->denom_sig = fresh_coin.sig;
1029 : }
1030 61 : if (ok)
1031 : {
1032 61 : wh->callback (
1033 : wh->callback_cls,
1034 : &resp);
1035 61 : wh->callback = NULL;
1036 : }
1037 124 : for (size_t n = 0; n<wh->num_coins; n++)
1038 : {
1039 63 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
1040 :
1041 63 : TALER_denom_sig_free (&coin->denom_sig);
1042 : }
1043 61 : break;
1044 : }
1045 0 : case MHD_HTTP_CREATED:
1046 0 : resp.details.created = wbr->details.created;
1047 0 : break;
1048 :
1049 4 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
1050 4 : resp.details.unavailable_for_legal_reasons =
1051 : wbr->details.unavailable_for_legal_reasons;
1052 4 : break;
1053 :
1054 5 : default:
1055 : /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */
1056 5 : break;
1057 : }
1058 70 : if (NULL != wh->callback)
1059 : {
1060 9 : wh->callback (
1061 : wh->callback_cls,
1062 : &resp);
1063 9 : wh->callback = NULL;
1064 : }
1065 70 : TALER_EXCHANGE_withdraw_cancel (wh);
1066 70 : }
1067 :
1068 :
1069 : /**
1070 : * @brief Callback to copy the results from the call to TALER_withdraw_blinded
1071 : * in the age-restricted case to the result for the originating call from TALER_withdraw.
1072 : *
1073 : * @param cls struct TALER_WithdrawHandle
1074 : * @param wbr The response
1075 : */
1076 : static void
1077 5 : copy_results_with_age_proof (
1078 : void *cls,
1079 : const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr)
1080 5 : {
1081 : /* The original handle from the top-level call to withdraw */
1082 5 : struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
1083 5 : uint8_t k = wbr->details.created.noreveal_index;
1084 5 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins];
1085 5 : struct TALER_EXCHANGE_WithdrawResponse resp = {
1086 : .hr = wbr->hr,
1087 : };
1088 :
1089 5 : wh->withdraw_blinded_handle = NULL;
1090 :
1091 5 : switch (wbr->hr.http_status)
1092 : {
1093 0 : case MHD_HTTP_OK:
1094 : /* in the age-restricted case, this should not happen */
1095 0 : GNUNET_break_op (0);
1096 0 : break;
1097 :
1098 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
1099 0 : resp.details.unavailable_for_legal_reasons =
1100 : wbr->details.unavailable_for_legal_reasons;
1101 0 : break;
1102 :
1103 3 : case MHD_HTTP_CREATED:
1104 : {
1105 3 : GNUNET_assert (wh->num_coins == wbr->details.created.num_coins);
1106 3 : resp.details.created = wbr->details.created;
1107 3 : resp.details.created.coin_details = details;
1108 3 : resp.details.created.kappa_seed = wh->kappa_seed;
1109 3 : memset (details,
1110 : 0,
1111 : sizeof(details));
1112 10 : for (size_t n = 0; n< wh->num_coins; n++)
1113 : {
1114 7 : details[n] = wh->coin_data[n].candidates[k].details;
1115 7 : details[n].planchet = wh->coin_data[n].planchet_details[k];
1116 : }
1117 3 : break;
1118 : }
1119 :
1120 2 : default:
1121 2 : break;
1122 : }
1123 :
1124 5 : wh->callback (
1125 : wh->callback_cls,
1126 : &resp);
1127 5 : wh->callback = NULL;
1128 5 : TALER_EXCHANGE_withdraw_cancel (wh);
1129 5 : }
1130 :
1131 :
1132 : /**
1133 : * @brief Prepares and executes TALER_EXCHANGE_withdraw_blinded.
1134 : * If there were CS-denominations involved, started once the all calls
1135 : * to /blinding-prepare are done.
1136 : */
1137 : static void
1138 75 : call_withdraw_blinded (
1139 : struct TALER_EXCHANGE_WithdrawHandle *wh)
1140 : {
1141 :
1142 75 : GNUNET_assert (NULL == wh->blinding_prepare_handle);
1143 :
1144 75 : if (! wh->with_age_proof)
1145 70 : {
1146 70 : struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins];
1147 :
1148 70 : memset (input,
1149 : 0,
1150 : sizeof(input));
1151 :
1152 : /* Prepare the blinded planchets as input */
1153 142 : for (size_t n = 0; n < wh->num_coins; n++)
1154 : {
1155 72 : input[n].denom_pub =
1156 72 : &wh->coin_data[n].denom_pub;
1157 72 : input[n].planchet_details =
1158 72 : *wh->coin_data[n].planchet_details;
1159 : }
1160 :
1161 70 : wh->withdraw_blinded_handle =
1162 70 : TALER_EXCHANGE_withdraw_blinded (
1163 : wh->curl_ctx,
1164 : wh->keys,
1165 : wh->exchange_url,
1166 : wh->reserve_priv,
1167 70 : wh->has_blinding_seed ? &wh->blinding_seed : NULL,
1168 : wh->num_coins,
1169 : input,
1170 : ©_results,
1171 : wh);
1172 : }
1173 : else
1174 5 : { /* age restricted case */
1175 : struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
1176 5 : ari[wh->num_coins];
1177 :
1178 5 : memset (ari,
1179 : 0,
1180 : sizeof(ari));
1181 :
1182 : /* Prepare the blinded planchets as input */
1183 14 : for (size_t n = 0; n < wh->num_coins; n++)
1184 : {
1185 9 : ari[n].denom_pub = &wh->coin_data[n].denom_pub;
1186 36 : for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
1187 27 : ari[n].planchet_details[k] =
1188 27 : wh->coin_data[n].planchet_details[k];
1189 : }
1190 :
1191 5 : wh->withdraw_blinded_handle =
1192 5 : TALER_EXCHANGE_withdraw_blinded_with_age_proof (
1193 : wh->curl_ctx,
1194 : wh->keys,
1195 : wh->exchange_url,
1196 : wh->reserve_priv,
1197 5 : wh->has_blinding_seed ? &wh->blinding_seed : NULL,
1198 5 : wh->max_age,
1199 5 : wh->num_coins,
1200 : ari,
1201 : ©_results_with_age_proof,
1202 : wh);
1203 : }
1204 75 : }
1205 :
1206 :
1207 : /**
1208 : * @brief Function called when /blinding-prepare is finished
1209 : *
1210 : * @param cls the `struct BlindingPrepareClosure *`
1211 : * @param bpr replies from the /blinding-prepare request
1212 : */
1213 : static void
1214 35 : blinding_prepare_done (
1215 : void *cls,
1216 : const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
1217 : {
1218 35 : struct BlindingPrepareClosure *bpcls = cls;
1219 35 : struct TALER_EXCHANGE_WithdrawHandle *wh = bpcls->withdraw_handle;
1220 :
1221 35 : wh->blinding_prepare_handle = NULL;
1222 35 : switch (bpr->hr.http_status)
1223 : {
1224 35 : case MHD_HTTP_OK:
1225 : {
1226 35 : bool success = false;
1227 35 : size_t num = bpr->details.ok.num_blinding_values;
1228 :
1229 35 : GNUNET_assert (0 != num);
1230 35 : GNUNET_assert (num == bpcls->num_nonces);
1231 81 : for (size_t i = 0; i < bpcls->num_prepare_coins; i++)
1232 : {
1233 46 : struct TALER_PlanchetDetail *planchet = bpcls->coins[i].planchet;
1234 46 : struct CoinCandidate *can = bpcls->coins[i].candidate;
1235 46 : size_t cs_idx = bpcls->coins[i].cs_idx;
1236 :
1237 46 : GNUNET_assert (NULL != can);
1238 46 : GNUNET_assert (NULL != planchet);
1239 46 : success = false;
1240 :
1241 : /* Complete the initialization of the coin with CS denomination */
1242 46 : TALER_denom_ewv_copy (
1243 : &can->details.blinding_values,
1244 46 : &bpr->details.ok.blinding_values[cs_idx]);
1245 :
1246 46 : GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
1247 : can->details.blinding_values.blinding_inputs->cipher);
1248 :
1249 46 : TALER_planchet_setup_coin_priv (
1250 46 : &can->details.secret,
1251 46 : &can->details.blinding_values,
1252 : &can->details.coin_priv);
1253 :
1254 46 : TALER_planchet_blinding_secret_create (
1255 46 : &can->details.secret,
1256 46 : &can->details.blinding_values,
1257 : &can->details.blinding_key);
1258 :
1259 : /* This initializes the 2nd half of the
1260 : can->planchet_detail.blinded_planchet */
1261 46 : if (GNUNET_OK !=
1262 46 : TALER_planchet_prepare (
1263 46 : bpcls->coins[i].denom_pub,
1264 46 : &can->details.blinding_values,
1265 46 : &can->details.blinding_key,
1266 46 : &bpcls->nonces[cs_idx],
1267 46 : &can->details.coin_priv,
1268 46 : &can->details.h_age_commitment,
1269 : &can->details.h_coin_pub,
1270 : planchet))
1271 : {
1272 0 : GNUNET_break (0);
1273 0 : break;
1274 : }
1275 :
1276 46 : TALER_coin_ev_hash (&planchet->blinded_planchet,
1277 46 : &planchet->denom_pub_hash,
1278 : &can->blinded_coin_h);
1279 46 : success = true;
1280 : }
1281 :
1282 : /* /blinding-prepare is done, we can now perform the
1283 : * actual withdraw operation */
1284 35 : if (success)
1285 35 : call_withdraw_blinded (wh);
1286 35 : goto cleanup;
1287 : }
1288 0 : default:
1289 : {
1290 : /* We got an error condition during blinding prepare that we need to report */
1291 0 : struct TALER_EXCHANGE_WithdrawResponse resp = {
1292 : .hr = bpr->hr
1293 : };
1294 :
1295 0 : wh->callback (
1296 : wh->callback_cls,
1297 : &resp);
1298 :
1299 0 : wh->callback = NULL;
1300 0 : break;
1301 : }
1302 : }
1303 0 : TALER_EXCHANGE_withdraw_cancel (wh);
1304 35 : cleanup:
1305 35 : GNUNET_free (bpcls->coins);
1306 35 : GNUNET_free (bpcls->nonces);
1307 35 : GNUNET_free (bpcls);
1308 35 : }
1309 :
1310 :
1311 : /**
1312 : * @brief Prepares non age-restricted coins for the call to withdraw and
1313 : * calculates the total amount with fees.
1314 : * For denomination with CS as cipher, initiates the preflight to retrieve the
1315 : * bpcls-parameter via /blinding-prepare.
1316 : * Note that only one of the three parameters seed, tuples or secrets must not be NULL
1317 : *
1318 : * @param wh The handler to the withdraw
1319 : * @param num_coins Number of coins to withdraw
1320 : * @param max_age The maximum age to commit to
1321 : * @param denoms_pub Array @e num_coins of denominations
1322 : * @param seed master seed from which to derive @e num_coins secrets and blinding, if @e blinding_seed is NULL
1323 : * @param blinding_seed master seed for the blinding. Might be NULL, in which case the blinding_seed is derived from @e seed
1324 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
1325 : */
1326 : static enum GNUNET_GenericReturnValue
1327 75 : prepare_coins (
1328 : struct TALER_EXCHANGE_WithdrawHandle *wh,
1329 : size_t num_coins,
1330 : uint8_t max_age,
1331 : const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub,
1332 : const struct TALER_WithdrawMasterSeedP *seed,
1333 : const struct TALER_BlindingMasterSeedP *blinding_seed)
1334 : {
1335 75 : size_t cs_num = 0;
1336 : struct BlindingPrepareClosure *cs_closure;
1337 : uint8_t kappa;
1338 :
1339 : #define FAIL_IF(cond) \
1340 : do \
1341 : { \
1342 : if ((cond)) \
1343 : { \
1344 : GNUNET_break (! (cond)); \
1345 : goto ERROR; \
1346 : } \
1347 : } while (0)
1348 :
1349 75 : GNUNET_assert (0 < num_coins);
1350 :
1351 75 : wh->num_coins = num_coins;
1352 75 : wh->max_age = max_age;
1353 75 : wh->age_mask = denoms_pub[0].key.age_mask;
1354 75 : wh->coin_data = GNUNET_new_array (
1355 : wh->num_coins,
1356 : struct CoinData);
1357 :
1358 : /* First, figure out how many Clause-Schnorr denominations we have */
1359 156 : for (size_t i =0; i< wh->num_coins; i++)
1360 : {
1361 81 : if (GNUNET_CRYPTO_BSA_CS ==
1362 81 : denoms_pub[i].key.bsign_pub_key->cipher)
1363 38 : cs_num++;
1364 : }
1365 :
1366 75 : if (wh->with_age_proof)
1367 : {
1368 5 : kappa = TALER_CNC_KAPPA;
1369 : }
1370 : else
1371 : {
1372 70 : kappa = 1;
1373 : }
1374 :
1375 75 : {
1376 75 : struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins];
1377 75 : struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)];
1378 75 : uint32_t cs_indices[GNUNET_NZL (cs_num)];
1379 :
1380 75 : size_t cs_denom_idx = 0;
1381 75 : size_t cs_coin_idx = 0;
1382 :
1383 75 : if (wh->with_age_proof)
1384 : {
1385 5 : TALER_withdraw_expand_kappa_seed (seed,
1386 : &wh->kappa_seed);
1387 20 : for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
1388 : {
1389 15 : TALER_withdraw_expand_secrets (
1390 : num_coins,
1391 15 : &wh->kappa_seed.tuple[k],
1392 15 : secrets[k]);
1393 : }
1394 : }
1395 : else
1396 : {
1397 70 : TALER_withdraw_expand_secrets (
1398 : num_coins,
1399 : seed,
1400 70 : secrets[0]);
1401 : }
1402 :
1403 75 : if (0 < cs_num)
1404 : {
1405 35 : memset (cs_nonce_keys,
1406 : 0,
1407 : sizeof(cs_nonce_keys));
1408 35 : cs_closure = GNUNET_new (struct BlindingPrepareClosure);
1409 35 : cs_closure->withdraw_handle = wh;
1410 35 : cs_closure->num_prepare_coins = cs_num * kappa;
1411 35 : GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num));
1412 35 : cs_closure->coins =
1413 35 : GNUNET_new_array (cs_closure->num_prepare_coins,
1414 : struct BlindingPrepareCoinData);
1415 35 : cs_closure->num_nonces = cs_num;
1416 35 : cs_closure->nonces =
1417 35 : GNUNET_new_array (cs_closure->num_nonces,
1418 : union GNUNET_CRYPTO_BlindSessionNonce);
1419 : }
1420 : else
1421 : {
1422 40 : cs_closure = NULL;
1423 : }
1424 :
1425 156 : for (uint32_t i = 0; i < wh->num_coins; i++)
1426 : {
1427 81 : struct CoinData *cd = &wh->coin_data[i];
1428 81 : bool age_denom = (0 != denoms_pub[i].key.age_mask.bits);
1429 :
1430 81 : cd->denom_pub = denoms_pub[i];
1431 : /* The age mask must be the same for all coins */
1432 81 : FAIL_IF (wh->with_age_proof &&
1433 : (0 == denoms_pub[i].key.age_mask.bits));
1434 81 : FAIL_IF (wh->age_mask.bits !=
1435 : denoms_pub[i].key.age_mask.bits);
1436 81 : TALER_denom_pub_copy (&cd->denom_pub.key,
1437 81 : &denoms_pub[i].key);
1438 :
1439 : /* Mark the indices of the coins which are of type Clause-Schnorr
1440 : * and add their denomination public key hash to the list.
1441 : */
1442 81 : if (GNUNET_CRYPTO_BSA_CS ==
1443 81 : cd->denom_pub.key.bsign_pub_key->cipher)
1444 : {
1445 38 : GNUNET_assert (cs_denom_idx<cs_num);
1446 38 : cs_indices[cs_denom_idx] = i;
1447 38 : cs_nonce_keys[cs_denom_idx].cnc_num = i;
1448 38 : cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
1449 38 : cs_denom_idx++;
1450 : }
1451 :
1452 : /*
1453 : * Note that we "loop" here either only once (if with_age_proof is false),
1454 : * or TALER_CNC_KAPPA times.
1455 : */
1456 180 : for (uint8_t k = 0; k < kappa; k++)
1457 : {
1458 99 : struct CoinCandidate *can = &cd->candidates[k];
1459 99 : struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
1460 :
1461 99 : can->details.secret = secrets[k][i];
1462 : /*
1463 : * The age restriction needs to be set on a coin if the denomination
1464 : * support age restriction. Note that his is regardless of weither
1465 : * with_age_proof is set or not.
1466 : */
1467 99 : if (age_denom)
1468 : {
1469 : /* Derive the age restriction from the given secret and
1470 : * the maximum age */
1471 37 : TALER_age_restriction_from_secret (
1472 37 : &can->details.secret,
1473 37 : &wh->age_mask,
1474 37 : wh->max_age,
1475 : &can->details.age_commitment_proof);
1476 :
1477 37 : TALER_age_commitment_hash (
1478 37 : &can->details.age_commitment_proof.commitment,
1479 : &can->details.h_age_commitment);
1480 : }
1481 :
1482 99 : switch (cd->denom_pub.key.bsign_pub_key->cipher)
1483 : {
1484 53 : case GNUNET_CRYPTO_BSA_RSA:
1485 53 : TALER_denom_ewv_copy (&can->details.blinding_values,
1486 : TALER_denom_ewv_rsa_singleton ());
1487 53 : TALER_planchet_setup_coin_priv (&can->details.secret,
1488 53 : &can->details.blinding_values,
1489 : &can->details.coin_priv);
1490 53 : TALER_planchet_blinding_secret_create (&can->details.secret,
1491 53 : &can->details.blinding_values,
1492 : &can->details.blinding_key);
1493 53 : FAIL_IF (GNUNET_OK !=
1494 : TALER_planchet_prepare (&cd->denom_pub.key,
1495 : &can->details.blinding_values,
1496 : &can->details.blinding_key,
1497 : NULL,
1498 : &can->details.coin_priv,
1499 : (age_denom)
1500 : ? &can->details.h_age_commitment
1501 : : NULL,
1502 : &can->details.h_coin_pub,
1503 : planchet));
1504 53 : TALER_coin_ev_hash (&planchet->blinded_planchet,
1505 53 : &planchet->denom_pub_hash,
1506 : &can->blinded_coin_h);
1507 :
1508 53 : break;
1509 :
1510 46 : case GNUNET_CRYPTO_BSA_CS:
1511 : {
1512 : /**
1513 : * Prepare the nonce and save the index and the denomination for the callback
1514 : * after the call to blinding-prepare
1515 : */
1516 46 : cs_closure->coins[cs_coin_idx].candidate = can;
1517 46 : cs_closure->coins[cs_coin_idx].planchet = planchet;
1518 46 : cs_closure->coins[cs_coin_idx].denom_pub = &cd->denom_pub.key;
1519 46 : cs_closure->coins[cs_coin_idx].cs_idx = i;
1520 46 : cs_closure->coins[cs_coin_idx].age_denom = age_denom;
1521 46 : cs_coin_idx++;
1522 46 : break;
1523 : }
1524 0 : default:
1525 0 : FAIL_IF (1);
1526 : }
1527 : }
1528 : }
1529 :
1530 75 : if (0 < cs_num)
1531 : {
1532 35 : if (NULL != blinding_seed)
1533 : {
1534 32 : wh->blinding_seed = *blinding_seed;
1535 : }
1536 : else
1537 : {
1538 3 : TALER_cs_withdraw_seed_to_blinding_seed (
1539 : seed,
1540 : &wh->blinding_seed);
1541 : }
1542 35 : wh->has_blinding_seed = true;
1543 :
1544 35 : TALER_cs_derive_only_cs_blind_nonces_from_seed (
1545 35 : &wh->blinding_seed,
1546 : false, /* not for melt */
1547 : cs_num,
1548 : cs_indices,
1549 : cs_closure->nonces);
1550 :
1551 35 : wh->blinding_prepare_handle =
1552 35 : TALER_EXCHANGE_blinding_prepare_for_withdraw (
1553 : wh->curl_ctx,
1554 : wh->exchange_url,
1555 : &wh->blinding_seed,
1556 : cs_num,
1557 : cs_nonce_keys,
1558 : &blinding_prepare_done,
1559 : cs_closure);
1560 35 : FAIL_IF (NULL == wh->blinding_prepare_handle);
1561 : }
1562 : }
1563 75 : return GNUNET_OK;
1564 :
1565 0 : ERROR:
1566 0 : if (0<cs_num)
1567 : {
1568 0 : GNUNET_free (cs_closure->nonces);
1569 0 : GNUNET_free (cs_closure);
1570 : }
1571 0 : TALER_EXCHANGE_withdraw_cancel (wh);
1572 0 : return GNUNET_SYSERR;
1573 : #undef FAIL_IF
1574 :
1575 : }
1576 :
1577 :
1578 : /**
1579 : * Prepare a withdraw handle for both, the non-restricted
1580 : * and age-restricted case.
1581 : *
1582 : * @param curl_ctx The curl context to use
1583 : * @param keys The keys from the exchange
1584 : * @param exchange_url The base url to the exchange
1585 : * @param reserve_priv The private key of the exchange
1586 : * @param res_cb The callback to call on response
1587 : * @param res_cb_cls The closure to pass to the callback
1588 : */
1589 : static struct TALER_EXCHANGE_WithdrawHandle *
1590 75 : setup_withdraw_handle (
1591 : struct GNUNET_CURL_Context *curl_ctx,
1592 : struct TALER_EXCHANGE_Keys *keys,
1593 : const char *exchange_url,
1594 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1595 : TALER_EXCHANGE_WithdrawCallback res_cb,
1596 : void *res_cb_cls)
1597 : {
1598 : struct TALER_EXCHANGE_WithdrawHandle *wh;
1599 :
1600 75 : wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
1601 75 : wh->exchange_url = exchange_url;
1602 75 : wh->keys = TALER_EXCHANGE_keys_incref (keys);
1603 75 : wh->curl_ctx = curl_ctx;
1604 75 : wh->reserve_priv = reserve_priv;
1605 75 : wh->callback = res_cb;
1606 75 : wh->callback_cls = res_cb_cls;
1607 :
1608 75 : return wh;
1609 : }
1610 :
1611 :
1612 : struct TALER_EXCHANGE_WithdrawHandle *
1613 70 : TALER_EXCHANGE_withdraw_extra_blinding_seed (
1614 : struct GNUNET_CURL_Context *curl_ctx,
1615 : struct TALER_EXCHANGE_Keys *keys,
1616 : const char *exchange_url,
1617 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1618 : size_t num_coins,
1619 : const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
1620 : const struct TALER_WithdrawMasterSeedP *seed,
1621 : const struct TALER_BlindingMasterSeedP *blinding_seed,
1622 : uint8_t opaque_max_age,
1623 : TALER_EXCHANGE_WithdrawCallback res_cb,
1624 : void *res_cb_cls)
1625 70 : {
1626 : struct TALER_EXCHANGE_WithdrawHandle *wh;
1627 :
1628 70 : wh = setup_withdraw_handle (curl_ctx,
1629 : keys,
1630 : exchange_url,
1631 : reserve_priv,
1632 : res_cb,
1633 : res_cb_cls);
1634 70 : GNUNET_assert (NULL != wh);
1635 70 : wh->with_age_proof = false;
1636 :
1637 70 : if (GNUNET_OK !=
1638 70 : prepare_coins (wh,
1639 : num_coins,
1640 : opaque_max_age,
1641 : denoms_pub,
1642 : seed,
1643 : blinding_seed))
1644 : {
1645 0 : GNUNET_free (wh);
1646 0 : return NULL;
1647 : }
1648 :
1649 : /* If there were no CS denominations, we can now perform the actual
1650 : * withdraw protocol. Otherwise, there are calls to /blinding-prepare
1651 : * in flight and once they finish, the withdraw-protocol will be
1652 : * called from within the blinding_prepare_done-function.
1653 : */
1654 70 : if (NULL == wh->blinding_prepare_handle)
1655 37 : call_withdraw_blinded (wh);
1656 :
1657 70 : return wh;
1658 : }
1659 :
1660 :
1661 : struct TALER_EXCHANGE_WithdrawHandle *
1662 2 : TALER_EXCHANGE_withdraw (
1663 : struct GNUNET_CURL_Context *curl_ctx,
1664 : struct TALER_EXCHANGE_Keys *keys,
1665 : const char *exchange_url,
1666 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1667 : size_t num_coins,
1668 : const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
1669 : const struct TALER_WithdrawMasterSeedP *seed,
1670 : uint8_t opaque_max_age,
1671 : TALER_EXCHANGE_WithdrawCallback res_cb,
1672 : void *res_cb_cls)
1673 2 : {
1674 2 : return TALER_EXCHANGE_withdraw_extra_blinding_seed (
1675 : curl_ctx,
1676 : keys,
1677 : exchange_url,
1678 : reserve_priv,
1679 : num_coins,
1680 : denoms_pub,
1681 : seed,
1682 : NULL,
1683 : opaque_max_age,
1684 : res_cb,
1685 : res_cb_cls
1686 : );
1687 : }
1688 :
1689 :
1690 : struct TALER_EXCHANGE_WithdrawHandle *
1691 5 : TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed (
1692 : struct GNUNET_CURL_Context *curl_ctx,
1693 : struct TALER_EXCHANGE_Keys *keys,
1694 : const char *exchange_url,
1695 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1696 : size_t num_coins,
1697 : const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
1698 : const struct TALER_WithdrawMasterSeedP *seed,
1699 : const struct TALER_BlindingMasterSeedP *blinding_seed,
1700 : uint8_t max_age,
1701 : TALER_EXCHANGE_WithdrawCallback res_cb,
1702 : void *res_cb_cls)
1703 5 : {
1704 : struct TALER_EXCHANGE_WithdrawHandle *wh;
1705 :
1706 5 : wh = setup_withdraw_handle (curl_ctx,
1707 : keys,
1708 : exchange_url,
1709 : reserve_priv,
1710 : res_cb,
1711 : res_cb_cls);
1712 5 : GNUNET_assert (NULL != wh);
1713 :
1714 5 : wh->with_age_proof = true;
1715 :
1716 5 : if (GNUNET_OK !=
1717 5 : prepare_coins (wh,
1718 : num_coins,
1719 : max_age,
1720 : denoms_pub,
1721 : seed,
1722 : blinding_seed))
1723 : {
1724 0 : GNUNET_free (wh);
1725 0 : return NULL;
1726 : }
1727 :
1728 : /* If there were no CS denominations, we can now perform the actual
1729 : * withdraw protocol. Otherwise, there are calls to /blinding-prepare
1730 : * in flight and once they finish, the withdraw-protocol will be
1731 : * called from within the blinding_prepare_done-function.
1732 : */
1733 5 : if (NULL == wh->blinding_prepare_handle)
1734 3 : call_withdraw_blinded (wh);
1735 :
1736 5 : return wh;
1737 : }
1738 :
1739 :
1740 : struct TALER_EXCHANGE_WithdrawHandle *
1741 5 : TALER_EXCHANGE_withdraw_with_age_proof (
1742 : struct GNUNET_CURL_Context *curl_ctx,
1743 : struct TALER_EXCHANGE_Keys *keys,
1744 : const char *exchange_url,
1745 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1746 : size_t num_coins,
1747 : const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
1748 : const struct TALER_WithdrawMasterSeedP *seed,
1749 : uint8_t max_age,
1750 : TALER_EXCHANGE_WithdrawCallback res_cb,
1751 : void *res_cb_cls)
1752 5 : {
1753 5 : return TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed (
1754 : curl_ctx,
1755 : keys,
1756 : exchange_url,
1757 : reserve_priv,
1758 : num_coins,
1759 : denoms_pub,
1760 : seed,
1761 : NULL,
1762 : max_age,
1763 : res_cb,
1764 : res_cb_cls);
1765 : }
1766 :
1767 :
1768 : void
1769 75 : TALER_EXCHANGE_withdraw_cancel (
1770 : struct TALER_EXCHANGE_WithdrawHandle *wh)
1771 : {
1772 75 : uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1;
1773 :
1774 : /* Cleanup coin data */
1775 156 : for (unsigned int i = 0; i<wh->num_coins; i++)
1776 : {
1777 81 : struct CoinData *cd = &wh->coin_data[i];
1778 :
1779 180 : for (uint8_t k = 0; k < kappa; k++)
1780 : {
1781 99 : struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
1782 99 : struct CoinCandidate *can = &cd->candidates[k];
1783 :
1784 99 : TALER_blinded_planchet_free (&planchet->blinded_planchet);
1785 99 : TALER_denom_ewv_free (&can->details.blinding_values);
1786 99 : TALER_age_commitment_proof_free (&can->details.age_commitment_proof);
1787 : }
1788 81 : TALER_denom_pub_free (&cd->denom_pub.key);
1789 : }
1790 :
1791 75 : TALER_EXCHANGE_blinding_prepare_cancel (wh->blinding_prepare_handle);
1792 75 : TALER_EXCHANGE_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
1793 75 : wh->blinding_prepare_handle = NULL;
1794 75 : wh->withdraw_blinded_handle = NULL;
1795 :
1796 75 : GNUNET_free (wh->coin_data);
1797 75 : TALER_EXCHANGE_keys_decref (wh->keys);
1798 75 : GNUNET_free (wh);
1799 75 : }
1800 :
1801 :
1802 : /**
1803 : * @brief Prepare the handler for blinded withdraw
1804 : *
1805 : * Allocates the handler struct and prepares all fields of the handler
1806 : * except the blinded planchets,
1807 : * which depend on them being age-restricted or not.
1808 : *
1809 : * @param curl_ctx the context for curl
1810 : * @param keys the exchange keys
1811 : * @param exchange_url the url to the exchange
1812 : * @param reserve_priv the reserve's private key
1813 : * @param res_cb the callback on result
1814 : * @param res_cb_cls the closure to pass on to the callback
1815 : * @return the handler
1816 : */
1817 : static struct TALER_EXCHANGE_WithdrawBlindedHandle *
1818 75 : setup_handler_common (
1819 : struct GNUNET_CURL_Context *curl_ctx,
1820 : struct TALER_EXCHANGE_Keys *keys,
1821 : const char *exchange_url,
1822 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1823 : TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
1824 : void *res_cb_cls)
1825 : {
1826 :
1827 : struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
1828 75 : GNUNET_new (struct TALER_EXCHANGE_WithdrawBlindedHandle);
1829 :
1830 75 : wbh->keys = TALER_EXCHANGE_keys_incref (keys);
1831 75 : wbh->curl_ctx = curl_ctx;
1832 75 : wbh->reserve_priv = reserve_priv;
1833 75 : wbh->callback = res_cb;
1834 75 : wbh->callback_cls = res_cb_cls;
1835 75 : wbh->request_url = TALER_url_join (exchange_url,
1836 : "withdraw",
1837 : NULL);
1838 75 : GNUNET_CRYPTO_eddsa_key_get_public (
1839 75 : &wbh->reserve_priv->eddsa_priv,
1840 : &wbh->reserve_pub.eddsa_pub);
1841 :
1842 75 : return wbh;
1843 : }
1844 :
1845 :
1846 : struct TALER_EXCHANGE_WithdrawBlindedHandle *
1847 70 : TALER_EXCHANGE_withdraw_blinded (
1848 : struct GNUNET_CURL_Context *curl_ctx,
1849 : struct TALER_EXCHANGE_Keys *keys,
1850 : const char *exchange_url,
1851 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1852 : const struct TALER_BlindingMasterSeedP *blinding_seed,
1853 : size_t num_input,
1854 : const struct TALER_EXCHANGE_WithdrawBlindedCoinInput
1855 : blinded_input[static num_input],
1856 : TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
1857 : void *res_cb_cls)
1858 70 : {
1859 : struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
1860 70 : setup_handler_common (curl_ctx,
1861 : keys,
1862 : exchange_url,
1863 : reserve_priv,
1864 : res_cb,
1865 : res_cb_cls);
1866 :
1867 70 : wbh->with_age_proof = false;
1868 70 : wbh->num_input = num_input;
1869 70 : wbh->blinded.input = blinded_input;
1870 70 : wbh->blinding_seed = blinding_seed;
1871 :
1872 70 : perform_withdraw_protocol (wbh);
1873 70 : return wbh;
1874 : }
1875 :
1876 :
1877 : struct TALER_EXCHANGE_WithdrawBlindedHandle *
1878 5 : TALER_EXCHANGE_withdraw_blinded_with_age_proof (
1879 : struct GNUNET_CURL_Context *curl_ctx,
1880 : struct TALER_EXCHANGE_Keys *keys,
1881 : const char *exchange_url,
1882 : const struct TALER_ReservePrivateKeyP *reserve_priv,
1883 : const struct TALER_BlindingMasterSeedP *blinding_seed,
1884 : uint8_t max_age,
1885 : unsigned int num_input,
1886 : const struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
1887 : blinded_input[static num_input],
1888 : TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
1889 : void *res_cb_cls)
1890 5 : {
1891 : struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
1892 5 : setup_handler_common (curl_ctx,
1893 : keys,
1894 : exchange_url,
1895 : reserve_priv,
1896 : res_cb,
1897 : res_cb_cls);
1898 :
1899 5 : wbh->with_age_proof = true;
1900 5 : wbh->max_age = max_age;
1901 5 : wbh->num_input = num_input;
1902 5 : wbh->blinded.with_age_proof_input = blinded_input;
1903 5 : wbh->blinding_seed = blinding_seed;
1904 :
1905 5 : perform_withdraw_protocol (wbh);
1906 5 : return wbh;
1907 : }
1908 :
1909 :
1910 : void
1911 150 : TALER_EXCHANGE_withdraw_blinded_cancel (
1912 : struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh)
1913 : {
1914 150 : if (NULL == wbh)
1915 75 : return;
1916 75 : if (NULL != wbh->job)
1917 : {
1918 0 : GNUNET_CURL_job_cancel (wbh->job);
1919 0 : wbh->job = NULL;
1920 : }
1921 75 : GNUNET_free (wbh->request_url);
1922 75 : TALER_EXCHANGE_keys_decref (wbh->keys);
1923 75 : TALER_curl_easy_post_finished (&wbh->post_ctx);
1924 75 : GNUNET_free (wbh);
1925 : }
1926 :
1927 :
1928 : /* exchange_api_withdraw.c */
|