Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023-2026 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_post-withdraw.c
19 : * @brief Implementation of /withdraw requests
20 : * @author Özgür Kesim
21 : */
22 : #include "taler/platform.h"
23 : #include <gnunet/gnunet_common.h>
24 : #include <jansson.h>
25 : #include <microhttpd.h> /* just for HTTP status codes */
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_json_lib.h>
28 : #include <gnunet/gnunet_curl_lib.h>
29 : #include <sys/wait.h>
30 : #include "taler/taler_curl_lib.h"
31 : #include "taler/taler_error_codes.h"
32 : #include "taler/taler_json_lib.h"
33 : #include "taler/taler_exchange_service.h"
34 : #include "exchange_api_common.h"
35 : #include "exchange_api_handle.h"
36 : #include "taler/taler_signatures.h"
37 : #include "exchange_api_curl_defaults.h"
38 : #include "taler/taler_util.h"
39 :
40 : /**
41 : * A CoinCandidate is populated from a master secret.
42 : * The data is copied from and generated out of the client's input.
43 : */
44 : struct CoinCandidate
45 : {
46 : /**
47 : * The details derived form the master secrets
48 : */
49 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;
50 :
51 : /**
52 : * Blinded hash of the coin
53 : **/
54 : struct TALER_BlindedCoinHashP blinded_coin_h;
55 :
56 : };
57 :
58 :
59 : /**
60 : * Data we keep per coin in the batch.
61 : * This is copied from and generated out of the input provided
62 : * by the client.
63 : */
64 : struct CoinData
65 : {
66 : /**
67 : * The denomination of the coin.
68 : */
69 : struct TALER_EXCHANGE_DenomPublicKey denom_pub;
70 :
71 : /**
72 : * The Candidates for the coin. If the batch is not age-restricted,
73 : * only index 0 is used.
74 : */
75 : struct CoinCandidate candidates[TALER_CNC_KAPPA];
76 :
77 : /**
78 : * Details of the planchet(s). If the batch is not age-restricted,
79 : * only index 0 is used.
80 : */
81 : struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
82 : };
83 :
84 :
85 : /**
86 : * Per-CS-coin data needed to complete the coin after /blinding-prepare.
87 : */
88 : struct BlindingPrepareCoinData
89 : {
90 : /**
91 : * Pointer to the candidate in CoinData.candidates,
92 : * to continue to build its contents based on the results from /blinding-prepare
93 : */
94 : struct CoinCandidate *candidate;
95 :
96 : /**
97 : * Planchet to finally generate in the corresponding candidate
98 : * in CoinData.planchet_details
99 : */
100 : struct TALER_PlanchetDetail *planchet;
101 :
102 : /**
103 : * Denomination information, needed for the
104 : * step after /blinding-prepare
105 : */
106 : const struct TALER_DenominationPublicKey *denom_pub;
107 :
108 : /**
109 : * True, if denomination supports age restriction
110 : */
111 : bool age_denom;
112 :
113 : /**
114 : * The index into the array of returned values from the call to
115 : * /blinding-prepare that are to be used for this coin.
116 : */
117 : size_t cs_idx;
118 :
119 : };
120 :
121 :
122 : /**
123 : * A /withdraw request-handle for calls from
124 : * a wallet, i. e. when blinding data is available.
125 : */
126 : struct TALER_EXCHANGE_PostWithdrawHandle
127 : {
128 :
129 : /**
130 : * The base-URL of the exchange.
131 : */
132 : const char *exchange_url;
133 :
134 : /**
135 : * Seed to derive of all seeds for the coins.
136 : */
137 : struct TALER_WithdrawMasterSeedP seed;
138 :
139 : /**
140 : * If @e with_age_proof is true, the derived TALER_CNC_KAPPA many
141 : * seeds for candidate batches.
142 : */
143 : struct TALER_KappaWithdrawMasterSeedP kappa_seed;
144 :
145 : /**
146 : * True if @e blinding_seed is filled, that is, if
147 : * any of the denominations is of cipher type CS
148 : */
149 : bool has_blinding_seed;
150 :
151 : /**
152 : * Seed used for the derivation of blinding factors for denominations
153 : * with Clause-Schnorr cipher. We derive this from the master seed
154 : * for the withdraw, but independent from the other planchet seeds.
155 : * Only valid when @e has_blinding_seed is true;
156 : */
157 : struct TALER_BlindingMasterSeedP blinding_seed;
158 :
159 : /**
160 : * Reserve private key.
161 : */
162 : const struct TALER_ReservePrivateKeyP *reserve_priv;
163 :
164 : /**
165 : * Reserve public key, calculated
166 : */
167 : struct TALER_ReservePublicKeyP reserve_pub;
168 :
169 : /**
170 : * Signature of the reserve for the request, calculated after all
171 : * parameters for the coins are collected.
172 : */
173 : struct TALER_ReserveSignatureP reserve_sig;
174 :
175 : /*
176 : * The denomination keys of the exchange
177 : */
178 : struct TALER_EXCHANGE_Keys *keys;
179 :
180 : /**
181 : * True, if the withdraw is for age-restricted coins, with age-proof.
182 : * The denominations MUST support age restriction.
183 : */
184 : bool with_age_proof;
185 :
186 : /**
187 : * If @e with_age_proof is true, the age mask, extracted
188 : * from the denominations.
189 : * MUST be the same for all denominations.
190 : */
191 : struct TALER_AgeMask age_mask;
192 :
193 : /**
194 : * The maximum age to commit to. If @e with_age_proof
195 : * is true, the client will need to proof the correct setting
196 : * of age-restriction on the coins via an additional call
197 : * to /reveal-withdraw.
198 : */
199 : uint8_t max_age;
200 :
201 : /**
202 : * Length of the @e coin_data Array
203 : */
204 : size_t num_coins;
205 :
206 : /**
207 : * Array of per-coin data
208 : */
209 : struct CoinData *coin_data;
210 :
211 : /**
212 : * Context for curl.
213 : */
214 : struct GNUNET_CURL_Context *curl_ctx;
215 :
216 : /**
217 : * Function to call with withdraw response results.
218 : */
219 : TALER_EXCHANGE_PostWithdrawCallback callback;
220 :
221 : /**
222 : * Closure for @e callback
223 : */
224 : void *callback_cls;
225 :
226 : /**
227 : * The handler for the call to /blinding-prepare, needed for CS denominations.
228 : * NULL until _start is called for CS denominations, or when no CS denoms.
229 : */
230 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *blinding_prepare_handle;
231 :
232 : /**
233 : * The Handler for the actual call to the exchange
234 : */
235 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *withdraw_blinded_handle;
236 :
237 : /**
238 : * Number of CS denomination coin entries in @e bp_coins.
239 : * Zero if no CS denominations.
240 : */
241 : size_t num_bp_coins;
242 :
243 : /**
244 : * Array of @e num_bp_coins coin data for the blinding-prepare step.
245 : */
246 : struct BlindingPrepareCoinData *bp_coins;
247 :
248 : /**
249 : * Number of nonces in @e bp_nonces.
250 : */
251 : size_t num_bp_nonces;
252 :
253 : /**
254 : * Array of @e num_bp_nonces nonces for CS denominations.
255 : */
256 : union GNUNET_CRYPTO_BlindSessionNonce *bp_nonces;
257 :
258 : /**
259 : * Nonce keys for the blinding-prepare call.
260 : */
261 : struct TALER_EXCHANGE_NonceKey *bp_nonce_keys;
262 :
263 : /**
264 : * Number of nonce keys in @e bp_nonce_keys.
265 : */
266 : size_t num_bp_nonce_keys;
267 :
268 : /**
269 : * Array of @e init_num_coins denomination public keys.
270 : * NULL after _start is called.
271 : */
272 : struct TALER_EXCHANGE_DenomPublicKey *init_denoms_pub;
273 :
274 : /**
275 : * Number of coins provided in @e init_denoms_pub.
276 : */
277 : size_t init_num_coins;
278 :
279 : struct
280 : {
281 :
282 : /**
283 : * True if @e blinding_seed is filled, that is, if
284 : * any of the denominations is of cipher type CS
285 : */
286 : bool has_blinding_seed;
287 :
288 : /**
289 : * Seed used for the derivation of blinding factors for denominations
290 : * with Clause-Schnorr cipher. We derive this from the master seed
291 : * for the withdraw, but independent from the other planchet seeds.
292 : * Only valid when @e has_blinding_seed is true;
293 : */
294 : struct TALER_BlindingMasterSeedP blinding_seed;
295 :
296 : } options;
297 : };
298 :
299 :
300 : /**
301 : * @brief Callback to copy the results from the call to post_withdraw_blinded
302 : * in the non-age-restricted case to the result for the originating call.
303 : *
304 : * @param cls struct TALER_EXCHANGE_PostWithdrawHandle
305 : * @param wbr The response
306 : */
307 : static void
308 70 : copy_results (
309 : void *cls,
310 : const struct TALER_EXCHANGE_PostWithdrawBlindedResponse *wbr)
311 : {
312 : /* The original handle from the top-level call to withdraw */
313 70 : struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
314 70 : struct TALER_EXCHANGE_PostWithdrawResponse resp = {
315 : .hr = wbr->hr,
316 : };
317 :
318 70 : wh->withdraw_blinded_handle = NULL;
319 :
320 : /**
321 : * The withdraw protocol has been performed with blinded data.
322 : * Now the response can be copied as is, except for the MHD_HTTP_OK case,
323 : * in which we now need to perform the unblinding.
324 : */
325 70 : switch (wbr->hr.http_status)
326 : {
327 61 : case MHD_HTTP_OK:
328 61 : {
329 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails
330 61 : details[GNUNET_NZL (wh->num_coins)];
331 61 : bool ok = true;
332 :
333 61 : GNUNET_assert (wh->num_coins == wbr->details.ok.num_sigs);
334 61 : memset (details,
335 : 0,
336 : sizeof(details));
337 61 : resp.details.ok.num_sigs = wbr->details.ok.num_sigs;
338 61 : resp.details.ok.coin_details = details;
339 61 : resp.details.ok.planchets_h = wbr->details.ok.planchets_h;
340 124 : for (size_t n = 0; n<wh->num_coins; n++)
341 : {
342 63 : const struct TALER_BlindedDenominationSignature *bsig =
343 63 : &wbr->details.ok.blinded_denom_sigs[n];
344 63 : struct CoinData *cd = &wh->coin_data[n];
345 63 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
346 : struct TALER_FreshCoin fresh_coin;
347 :
348 63 : *coin = wh->coin_data[n].candidates[0].details;
349 63 : coin->planchet = wh->coin_data[n].planchet_details[0];
350 63 : GNUNET_CRYPTO_eddsa_key_get_public (
351 63 : &coin->coin_priv.eddsa_priv,
352 : &coin->coin_pub.eddsa_pub);
353 :
354 63 : if (GNUNET_OK !=
355 63 : TALER_planchet_to_coin (&cd->denom_pub.key,
356 : bsig,
357 63 : &coin->blinding_key,
358 63 : &coin->coin_priv,
359 63 : &coin->h_age_commitment,
360 63 : &coin->h_coin_pub,
361 63 : &coin->blinding_values,
362 : &fresh_coin))
363 : {
364 0 : resp.hr.http_status = 0;
365 0 : resp.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
366 0 : GNUNET_break_op (0);
367 0 : ok = false;
368 0 : break;
369 : }
370 63 : coin->denom_sig = fresh_coin.sig;
371 : }
372 61 : if (ok)
373 : {
374 61 : wh->callback (
375 : wh->callback_cls,
376 : &resp);
377 61 : wh->callback = NULL;
378 : }
379 124 : for (size_t n = 0; n<wh->num_coins; n++)
380 : {
381 63 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
382 :
383 63 : TALER_denom_sig_free (&coin->denom_sig);
384 : }
385 61 : break;
386 : }
387 0 : case MHD_HTTP_CREATED:
388 0 : resp.details.created = wbr->details.created;
389 0 : break;
390 :
391 4 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
392 4 : resp.details.unavailable_for_legal_reasons =
393 : wbr->details.unavailable_for_legal_reasons;
394 4 : break;
395 :
396 5 : default:
397 : /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */
398 5 : break;
399 : }
400 70 : if (NULL != wh->callback)
401 : {
402 9 : wh->callback (
403 : wh->callback_cls,
404 : &resp);
405 9 : wh->callback = NULL;
406 : }
407 70 : TALER_EXCHANGE_post_withdraw_cancel (wh);
408 70 : }
409 :
410 :
411 : /**
412 : * @brief Callback to copy the results from the call to post_withdraw_blinded
413 : * in the age-restricted case.
414 : *
415 : * @param cls struct TALER_EXCHANGE_PostWithdrawHandle
416 : * @param wbr The response
417 : */
418 : static void
419 5 : copy_results_with_age_proof (
420 : void *cls,
421 : const struct TALER_EXCHANGE_PostWithdrawBlindedResponse *wbr)
422 5 : {
423 : /* The original handle from the top-level call to withdraw */
424 5 : struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
425 5 : uint8_t k = wbr->details.created.noreveal_index;
426 5 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins];
427 5 : struct TALER_EXCHANGE_PostWithdrawResponse resp = {
428 : .hr = wbr->hr,
429 : };
430 :
431 5 : wh->withdraw_blinded_handle = NULL;
432 5 : switch (wbr->hr.http_status)
433 : {
434 0 : case MHD_HTTP_OK:
435 : /* in the age-restricted case, this should not happen */
436 0 : GNUNET_break_op (0);
437 0 : break;
438 :
439 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
440 0 : resp.details.unavailable_for_legal_reasons =
441 : wbr->details.unavailable_for_legal_reasons;
442 0 : break;
443 :
444 3 : case MHD_HTTP_CREATED:
445 : {
446 3 : GNUNET_assert (wh->num_coins == wbr->details.created.num_coins);
447 3 : resp.details.created = wbr->details.created;
448 3 : resp.details.created.coin_details = details;
449 3 : resp.details.created.kappa_seed = wh->kappa_seed;
450 3 : memset (details,
451 : 0,
452 : sizeof(details));
453 10 : for (size_t n = 0; n< wh->num_coins; n++)
454 : {
455 7 : details[n] = wh->coin_data[n].candidates[k].details;
456 7 : details[n].planchet = wh->coin_data[n].planchet_details[k];
457 : }
458 3 : break;
459 : }
460 :
461 2 : default:
462 2 : break;
463 : }
464 :
465 5 : wh->callback (
466 : wh->callback_cls,
467 : &resp);
468 5 : wh->callback = NULL;
469 5 : TALER_EXCHANGE_post_withdraw_cancel (wh);
470 5 : }
471 :
472 :
473 : /**
474 : * @brief Prepares and starts the actual TALER_EXCHANGE_post_withdraw_blinded
475 : * operation once all blinding-prepare steps are done (or immediately if
476 : * there are no CS denominations).
477 : *
478 : * @param wh The withdraw handle
479 : * @return #TALER_EC_NONE on success, error code on failure
480 : */
481 : static enum TALER_ErrorCode
482 75 : call_withdraw_blinded (
483 : struct TALER_EXCHANGE_PostWithdrawHandle *wh)
484 : {
485 : enum TALER_ErrorCode ec;
486 :
487 75 : GNUNET_assert (NULL == wh->blinding_prepare_handle);
488 :
489 75 : if (! wh->with_age_proof)
490 70 : {
491 70 : struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins];
492 :
493 70 : memset (input,
494 : 0,
495 : sizeof(input));
496 :
497 : /* Prepare the blinded planchets as input */
498 142 : for (size_t n = 0; n < wh->num_coins; n++)
499 : {
500 72 : input[n].denom_pub =
501 72 : &wh->coin_data[n].denom_pub;
502 72 : input[n].planchet_details =
503 72 : *wh->coin_data[n].planchet_details;
504 : }
505 :
506 70 : wh->withdraw_blinded_handle =
507 70 : TALER_EXCHANGE_post_withdraw_blinded_create (
508 : wh->curl_ctx,
509 : wh->keys,
510 : wh->exchange_url,
511 : wh->reserve_priv,
512 70 : wh->has_blinding_seed ? &wh->blinding_seed : NULL,
513 : wh->num_coins,
514 : input);
515 70 : if (NULL == wh->withdraw_blinded_handle)
516 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
517 70 : ec = TALER_EXCHANGE_post_withdraw_blinded_start (
518 : wh->withdraw_blinded_handle,
519 : ©_results,
520 : wh);
521 70 : if (TALER_EC_NONE != ec)
522 : {
523 0 : wh->withdraw_blinded_handle = NULL;
524 0 : return ec;
525 : }
526 : }
527 : else
528 5 : { /* age restricted case */
529 : struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
530 5 : ari[wh->num_coins];
531 :
532 5 : memset (ari,
533 : 0,
534 : sizeof(ari));
535 :
536 : /* Prepare the blinded planchets as input */
537 14 : for (size_t n = 0; n < wh->num_coins; n++)
538 : {
539 9 : ari[n].denom_pub = &wh->coin_data[n].denom_pub;
540 36 : for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
541 27 : ari[n].planchet_details[k] =
542 27 : wh->coin_data[n].planchet_details[k];
543 : }
544 :
545 5 : wh->withdraw_blinded_handle =
546 5 : TALER_EXCHANGE_post_withdraw_blinded_create (
547 : wh->curl_ctx,
548 : wh->keys,
549 : wh->exchange_url,
550 : wh->reserve_priv,
551 5 : wh->has_blinding_seed ? &wh->blinding_seed : NULL,
552 : wh->num_coins,
553 : NULL);
554 5 : if (NULL == wh->withdraw_blinded_handle)
555 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
556 5 : TALER_EXCHANGE_post_withdraw_blinded_set_options (
557 : wh->withdraw_blinded_handle,
558 : TALER_EXCHANGE_post_withdraw_blinded_option_with_age_proof (
559 : wh->max_age,
560 : ari));
561 5 : ec = TALER_EXCHANGE_post_withdraw_blinded_start (
562 : wh->withdraw_blinded_handle,
563 : ©_results_with_age_proof,
564 : wh);
565 5 : if (TALER_EC_NONE != ec)
566 : {
567 0 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
568 0 : wh->withdraw_blinded_handle = NULL;
569 0 : return ec;
570 : }
571 : }
572 75 : return TALER_EC_NONE;
573 : }
574 :
575 :
576 : /**
577 : * @brief Function called when /blinding-prepare is finished.
578 : *
579 : * @param cls the `struct TALER_EXCHANGE_PostWithdrawHandle *`
580 : * @param bpr replies from the /blinding-prepare request
581 : */
582 : static void
583 35 : blinding_prepare_done (
584 : void *cls,
585 : const struct TALER_EXCHANGE_PostBlindingPrepareResponse *bpr)
586 : {
587 35 : struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
588 :
589 35 : wh->blinding_prepare_handle = NULL;
590 35 : switch (bpr->hr.http_status)
591 : {
592 35 : case MHD_HTTP_OK:
593 : {
594 35 : bool success = false;
595 35 : size_t num = bpr->details.ok.num_blinding_values;
596 :
597 35 : GNUNET_assert (0 != num);
598 35 : GNUNET_assert (num == wh->num_bp_nonces);
599 81 : for (size_t i = 0; i < wh->num_bp_coins; i++)
600 : {
601 46 : struct TALER_PlanchetDetail *planchet = wh->bp_coins[i].planchet;
602 46 : struct CoinCandidate *can = wh->bp_coins[i].candidate;
603 46 : size_t cs_idx = wh->bp_coins[i].cs_idx;
604 :
605 46 : GNUNET_assert (NULL != can);
606 46 : GNUNET_assert (NULL != planchet);
607 46 : success = false;
608 :
609 : /* Complete the initialization of the coin with CS denomination */
610 46 : TALER_denom_ewv_copy (
611 : &can->details.blinding_values,
612 46 : &bpr->details.ok.blinding_values[cs_idx]);
613 :
614 46 : GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
615 : can->details.blinding_values.blinding_inputs->cipher);
616 :
617 46 : TALER_planchet_setup_coin_priv (
618 46 : &can->details.secret,
619 46 : &can->details.blinding_values,
620 : &can->details.coin_priv);
621 :
622 46 : TALER_planchet_blinding_secret_create (
623 46 : &can->details.secret,
624 46 : &can->details.blinding_values,
625 : &can->details.blinding_key);
626 :
627 : /* This initializes the 2nd half of the
628 : can->planchet_detail.blinded_planchet */
629 46 : if (GNUNET_OK !=
630 46 : TALER_planchet_prepare (
631 46 : wh->bp_coins[i].denom_pub,
632 46 : &can->details.blinding_values,
633 46 : &can->details.blinding_key,
634 46 : &wh->bp_nonces[cs_idx],
635 46 : &can->details.coin_priv,
636 46 : &can->details.h_age_commitment,
637 : &can->details.h_coin_pub,
638 : planchet))
639 : {
640 0 : GNUNET_break (0);
641 0 : break;
642 : }
643 :
644 46 : TALER_coin_ev_hash (&planchet->blinded_planchet,
645 46 : &planchet->denom_pub_hash,
646 : &can->blinded_coin_h);
647 46 : success = true;
648 : }
649 :
650 : /* /blinding-prepare is done, we can now perform the
651 : * actual withdraw operation */
652 35 : if (success)
653 : {
654 35 : enum TALER_ErrorCode ec = call_withdraw_blinded (wh);
655 :
656 35 : if (TALER_EC_NONE != ec)
657 : {
658 0 : struct TALER_EXCHANGE_PostWithdrawResponse resp = {
659 : .hr.ec = ec,
660 : .hr.http_status = 0,
661 : };
662 :
663 0 : wh->callback (
664 : wh->callback_cls,
665 : &resp);
666 0 : wh->callback = NULL;
667 0 : TALER_EXCHANGE_post_withdraw_cancel (wh);
668 : }
669 35 : return;
670 : }
671 : else
672 : {
673 : /* prepare completed but coin setup failed */
674 0 : struct TALER_EXCHANGE_PostWithdrawResponse resp = {
675 : .hr.ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
676 : .hr.http_status = 0,
677 : };
678 :
679 0 : wh->callback (
680 : wh->callback_cls,
681 : &resp);
682 0 : wh->callback = NULL;
683 0 : TALER_EXCHANGE_post_withdraw_cancel (wh);
684 0 : return;
685 : }
686 : }
687 0 : default:
688 : {
689 : /* We got an error condition during blinding prepare that we need to report */
690 0 : struct TALER_EXCHANGE_PostWithdrawResponse resp = {
691 : .hr = bpr->hr
692 : };
693 :
694 0 : wh->callback (
695 : wh->callback_cls,
696 : &resp);
697 0 : wh->callback = NULL;
698 0 : break;
699 : }
700 : }
701 0 : TALER_EXCHANGE_post_withdraw_cancel (wh);
702 : }
703 :
704 :
705 : /**
706 : * @brief Prepares coins for the call to withdraw:
707 : * Performs synchronous crypto for RSA denominations, and stores
708 : * the data needed for the async /blinding-prepare step for CS denominations.
709 : * Does NOT start any async operations.
710 : *
711 : * @param wh The handler to the withdraw
712 : * @param num_coins Number of coins to withdraw
713 : * @param max_age The maximum age to commit to
714 : * @param denoms_pub Array @e num_coins of denominations
715 : * @param seed master seed from which to derive @e num_coins secrets
716 : * @param blinding_seed master seed for the blinding. Might be NULL, in which
717 : * case the blinding_seed is derived from @e seed
718 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
719 : */
720 : static enum GNUNET_GenericReturnValue
721 75 : prepare_coins (
722 : struct TALER_EXCHANGE_PostWithdrawHandle *wh,
723 : size_t num_coins,
724 : uint8_t max_age,
725 : const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub,
726 : const struct TALER_WithdrawMasterSeedP *seed,
727 : const struct TALER_BlindingMasterSeedP *blinding_seed)
728 : {
729 75 : size_t cs_num = 0;
730 : uint8_t kappa;
731 :
732 : #define FAIL_IF(cond) \
733 : do \
734 : { \
735 : if ((cond)) \
736 : { \
737 : GNUNET_break (! (cond)); \
738 : goto ERROR; \
739 : } \
740 : } while (0)
741 :
742 75 : GNUNET_assert (0 < num_coins);
743 :
744 75 : wh->num_coins = num_coins;
745 75 : wh->max_age = max_age;
746 75 : wh->age_mask = denoms_pub[0].key.age_mask;
747 75 : wh->coin_data = GNUNET_new_array (
748 : wh->num_coins,
749 : struct CoinData);
750 :
751 : /* First, figure out how many Clause-Schnorr denominations we have */
752 156 : for (size_t i =0; i< wh->num_coins; i++)
753 : {
754 81 : if (GNUNET_CRYPTO_BSA_CS ==
755 81 : denoms_pub[i].key.bsign_pub_key->cipher)
756 38 : cs_num++;
757 : }
758 :
759 75 : if (wh->with_age_proof)
760 5 : kappa = TALER_CNC_KAPPA;
761 : else
762 70 : kappa = 1;
763 :
764 75 : {
765 75 : struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins];
766 75 : struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)];
767 75 : uint32_t cs_indices[GNUNET_NZL (cs_num)];
768 :
769 75 : size_t cs_denom_idx = 0;
770 75 : size_t cs_coin_idx = 0;
771 :
772 75 : if (wh->with_age_proof)
773 : {
774 5 : TALER_withdraw_expand_kappa_seed (seed,
775 : &wh->kappa_seed);
776 20 : for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
777 : {
778 15 : TALER_withdraw_expand_secrets (
779 : num_coins,
780 15 : &wh->kappa_seed.tuple[k],
781 15 : secrets[k]);
782 : }
783 : }
784 : else
785 : {
786 70 : TALER_withdraw_expand_secrets (
787 : num_coins,
788 : seed,
789 70 : secrets[0]);
790 : }
791 :
792 75 : if (0 < cs_num)
793 : {
794 35 : memset (cs_nonce_keys,
795 : 0,
796 : sizeof(cs_nonce_keys));
797 35 : wh->num_bp_coins = cs_num * kappa;
798 35 : GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num));
799 35 : wh->bp_coins =
800 35 : GNUNET_new_array (wh->num_bp_coins,
801 : struct BlindingPrepareCoinData);
802 35 : wh->num_bp_nonces = cs_num;
803 35 : wh->bp_nonces =
804 35 : GNUNET_new_array (wh->num_bp_nonces,
805 : union GNUNET_CRYPTO_BlindSessionNonce);
806 35 : wh->num_bp_nonce_keys = cs_num;
807 35 : wh->bp_nonce_keys =
808 35 : GNUNET_new_array (wh->num_bp_nonce_keys,
809 : struct TALER_EXCHANGE_NonceKey);
810 : }
811 :
812 156 : for (uint32_t i = 0; i < wh->num_coins; i++)
813 : {
814 81 : struct CoinData *cd = &wh->coin_data[i];
815 81 : bool age_denom = (0 != denoms_pub[i].key.age_mask.bits);
816 :
817 81 : cd->denom_pub = denoms_pub[i];
818 : /* The age mask must be the same for all coins */
819 81 : FAIL_IF (wh->with_age_proof &&
820 : (0 == denoms_pub[i].key.age_mask.bits));
821 81 : FAIL_IF (wh->age_mask.bits !=
822 : denoms_pub[i].key.age_mask.bits);
823 81 : TALER_denom_pub_copy (&cd->denom_pub.key,
824 81 : &denoms_pub[i].key);
825 :
826 : /* Mark the indices of the coins which are of type Clause-Schnorr
827 : * and add their denomination public key hash to the list.
828 : */
829 81 : if (GNUNET_CRYPTO_BSA_CS ==
830 81 : cd->denom_pub.key.bsign_pub_key->cipher)
831 : {
832 38 : GNUNET_assert (cs_denom_idx < cs_num);
833 38 : cs_indices[cs_denom_idx] = i;
834 38 : cs_nonce_keys[cs_denom_idx].cnc_num = i;
835 38 : cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
836 38 : wh->bp_nonce_keys[cs_denom_idx].cnc_num = i;
837 38 : wh->bp_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
838 38 : cs_denom_idx++;
839 : }
840 :
841 : /*
842 : * Note that we "loop" here either only once (if with_age_proof is false),
843 : * or TALER_CNC_KAPPA times.
844 : */
845 180 : for (uint8_t k = 0; k < kappa; k++)
846 : {
847 99 : struct CoinCandidate *can = &cd->candidates[k];
848 99 : struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
849 :
850 99 : can->details.secret = secrets[k][i];
851 : /*
852 : * The age restriction needs to be set on a coin if the denomination
853 : * support age restriction. Note that this is regardless of whether
854 : * with_age_proof is set or not.
855 : */
856 99 : if (age_denom)
857 : {
858 : /* Derive the age restriction from the given secret and
859 : * the maximum age */
860 37 : TALER_age_restriction_from_secret (
861 37 : &can->details.secret,
862 37 : &wh->age_mask,
863 37 : wh->max_age,
864 : &can->details.age_commitment_proof);
865 :
866 37 : TALER_age_commitment_hash (
867 37 : &can->details.age_commitment_proof.commitment,
868 : &can->details.h_age_commitment);
869 : }
870 :
871 99 : switch (cd->denom_pub.key.bsign_pub_key->cipher)
872 : {
873 53 : case GNUNET_CRYPTO_BSA_RSA:
874 53 : TALER_denom_ewv_copy (&can->details.blinding_values,
875 : TALER_denom_ewv_rsa_singleton ());
876 53 : TALER_planchet_setup_coin_priv (&can->details.secret,
877 53 : &can->details.blinding_values,
878 : &can->details.coin_priv);
879 53 : TALER_planchet_blinding_secret_create (&can->details.secret,
880 53 : &can->details.blinding_values,
881 : &can->details.blinding_key);
882 53 : FAIL_IF (GNUNET_OK !=
883 : TALER_planchet_prepare (&cd->denom_pub.key,
884 : &can->details.blinding_values,
885 : &can->details.blinding_key,
886 : NULL,
887 : &can->details.coin_priv,
888 : (age_denom)
889 : ? &can->details.h_age_commitment
890 : : NULL,
891 : &can->details.h_coin_pub,
892 : planchet));
893 53 : TALER_coin_ev_hash (&planchet->blinded_planchet,
894 53 : &planchet->denom_pub_hash,
895 : &can->blinded_coin_h);
896 53 : break;
897 :
898 46 : case GNUNET_CRYPTO_BSA_CS:
899 : {
900 : /* Prepare the nonce and save the index and the denomination for
901 : * the callback after the call to blinding-prepare */
902 46 : wh->bp_coins[cs_coin_idx].candidate = can;
903 46 : wh->bp_coins[cs_coin_idx].planchet = planchet;
904 46 : wh->bp_coins[cs_coin_idx].denom_pub = &cd->denom_pub.key;
905 46 : wh->bp_coins[cs_coin_idx].cs_idx = i;
906 46 : wh->bp_coins[cs_coin_idx].age_denom = age_denom;
907 46 : cs_coin_idx++;
908 46 : break;
909 : }
910 0 : default:
911 0 : FAIL_IF (1);
912 : }
913 : }
914 : }
915 :
916 75 : if (0 < cs_num)
917 : {
918 35 : if (wh->options.has_blinding_seed)
919 : {
920 32 : wh->blinding_seed = wh->options.blinding_seed;
921 : }
922 : else
923 : {
924 3 : TALER_cs_withdraw_seed_to_blinding_seed (
925 : seed,
926 : &wh->blinding_seed);
927 : }
928 35 : wh->has_blinding_seed = true;
929 :
930 35 : TALER_cs_derive_only_cs_blind_nonces_from_seed (
931 35 : &wh->blinding_seed,
932 : false, /* not for melt */
933 : cs_num,
934 : cs_indices,
935 : wh->bp_nonces);
936 : }
937 : }
938 75 : return GNUNET_OK;
939 :
940 0 : ERROR:
941 0 : if (0 < cs_num)
942 : {
943 0 : GNUNET_free (wh->bp_nonces);
944 0 : GNUNET_free (wh->bp_coins);
945 0 : GNUNET_free (wh->bp_nonce_keys);
946 0 : wh->num_bp_coins = 0;
947 0 : wh->num_bp_nonces = 0;
948 0 : wh->num_bp_nonce_keys = 0;
949 : }
950 0 : return GNUNET_SYSERR;
951 : #undef FAIL_IF
952 : }
953 :
954 :
955 : struct TALER_EXCHANGE_PostWithdrawHandle *
956 75 : TALER_EXCHANGE_post_withdraw_create (
957 : struct GNUNET_CURL_Context *curl_ctx,
958 : const char *exchange_url,
959 : struct TALER_EXCHANGE_Keys *keys,
960 : const struct TALER_ReservePrivateKeyP *reserve_priv,
961 : size_t num_coins,
962 : const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
963 : const struct TALER_WithdrawMasterSeedP *seed,
964 : uint8_t opaque_max_age)
965 75 : {
966 : struct TALER_EXCHANGE_PostWithdrawHandle *wh;
967 :
968 75 : wh = GNUNET_new (struct TALER_EXCHANGE_PostWithdrawHandle);
969 75 : wh->exchange_url = exchange_url;
970 75 : wh->keys = TALER_EXCHANGE_keys_incref (keys);
971 75 : wh->curl_ctx = curl_ctx;
972 75 : wh->reserve_priv = reserve_priv;
973 75 : wh->seed = *seed;
974 75 : wh->max_age = opaque_max_age;
975 75 : wh->init_num_coins = num_coins;
976 75 : wh->init_denoms_pub = GNUNET_new_array (num_coins,
977 : struct TALER_EXCHANGE_DenomPublicKey);
978 156 : for (size_t i = 0; i < num_coins; i++)
979 : {
980 81 : wh->init_denoms_pub[i] = denoms_pub[i];
981 81 : TALER_denom_pub_copy (&wh->init_denoms_pub[i].key,
982 81 : &denoms_pub[i].key);
983 : }
984 :
985 75 : return wh;
986 : }
987 :
988 :
989 : enum GNUNET_GenericReturnValue
990 73 : TALER_EXCHANGE_post_withdraw_set_options_ (
991 : struct TALER_EXCHANGE_PostWithdrawHandle *pwh,
992 : unsigned int num_options,
993 : const struct TALER_EXCHANGE_PostWithdrawOptionValue options[])
994 : {
995 146 : for (unsigned int i = 0; i < num_options; i++)
996 : {
997 146 : const struct TALER_EXCHANGE_PostWithdrawOptionValue *opt = &options[i];
998 146 : switch (opt->option)
999 : {
1000 73 : case TALER_EXCHANGE_POST_WITHDRAW_OPTION_END:
1001 73 : return GNUNET_OK;
1002 5 : case TALER_EXCHANGE_POST_WITHDRAW_OPTION_WITH_AGE_PROOF:
1003 5 : pwh->with_age_proof = true;
1004 5 : pwh->max_age = opt->details.max_age;
1005 5 : break;
1006 68 : case TALER_EXCHANGE_POST_WITHDRAW_OPTION_BLINDING_SEED:
1007 68 : pwh->options.has_blinding_seed = true;
1008 68 : pwh->options.blinding_seed = opt->details.blinding_seed;
1009 68 : break;
1010 : }
1011 : }
1012 0 : return GNUNET_OK;
1013 : }
1014 :
1015 :
1016 : enum TALER_ErrorCode
1017 75 : TALER_EXCHANGE_post_withdraw_start (
1018 : struct TALER_EXCHANGE_PostWithdrawHandle *pwh,
1019 : TALER_EXCHANGE_PostWithdrawCallback cb,
1020 : TALER_EXCHANGE_POST_WITHDRAW_RESULT_CLOSURE *cb_cls)
1021 : {
1022 75 : pwh->callback = cb;
1023 75 : pwh->callback_cls = cb_cls;
1024 :
1025 : /* Run prepare_coins now that options have been applied */
1026 75 : if (GNUNET_OK !=
1027 75 : prepare_coins (pwh,
1028 : pwh->init_num_coins,
1029 75 : pwh->max_age,
1030 75 : pwh->init_denoms_pub,
1031 75 : &pwh->seed,
1032 75 : pwh->has_blinding_seed
1033 : ? &pwh->blinding_seed
1034 : : NULL))
1035 : {
1036 0 : GNUNET_free (pwh->coin_data);
1037 0 : for (size_t i = 0; i < pwh->init_num_coins; i++)
1038 0 : TALER_denom_pub_free (&pwh->init_denoms_pub[i].key);
1039 0 : GNUNET_free (pwh->init_denoms_pub);
1040 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
1041 : }
1042 : /* Free init data - no longer needed after prepare_coins */
1043 156 : for (size_t i = 0; i < pwh->init_num_coins; i++)
1044 81 : TALER_denom_pub_free (&pwh->init_denoms_pub[i].key);
1045 75 : GNUNET_free (pwh->init_denoms_pub);
1046 :
1047 75 : if (0 < pwh->num_bp_coins)
1048 : {
1049 : /* There are CS denominations; start the blinding-prepare request */
1050 35 : pwh->blinding_prepare_handle =
1051 35 : TALER_EXCHANGE_post_blinding_prepare_for_withdraw_create (
1052 : pwh->curl_ctx,
1053 : pwh->exchange_url,
1054 : &pwh->blinding_seed,
1055 : pwh->num_bp_nonce_keys,
1056 : pwh->bp_nonce_keys);
1057 35 : if (NULL == pwh->blinding_prepare_handle)
1058 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
1059 : {
1060 : enum TALER_ErrorCode ec =
1061 35 : TALER_EXCHANGE_post_blinding_prepare_start (
1062 : pwh->blinding_prepare_handle,
1063 : &blinding_prepare_done,
1064 : pwh);
1065 35 : if (TALER_EC_NONE != ec)
1066 : {
1067 0 : pwh->blinding_prepare_handle = NULL;
1068 0 : return ec;
1069 : }
1070 : }
1071 35 : return TALER_EC_NONE;
1072 : }
1073 :
1074 : /* No CS denominations; proceed directly to the withdraw protocol */
1075 40 : return call_withdraw_blinded (pwh);
1076 : }
1077 :
1078 :
1079 : void
1080 75 : TALER_EXCHANGE_post_withdraw_cancel (
1081 : struct TALER_EXCHANGE_PostWithdrawHandle *wh)
1082 : {
1083 75 : uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1;
1084 :
1085 : /* Cleanup init data if _start was never called (or failed) */
1086 75 : if (NULL != wh->init_denoms_pub)
1087 : {
1088 0 : for (size_t i = 0; i < wh->init_num_coins; i++)
1089 0 : TALER_denom_pub_free (&wh->init_denoms_pub[i].key);
1090 0 : GNUNET_free (wh->init_denoms_pub);
1091 : }
1092 : /* Cleanup coin data */
1093 75 : if (NULL != wh->coin_data)
1094 : {
1095 156 : for (unsigned int i = 0; i < wh->num_coins; i++)
1096 : {
1097 81 : struct CoinData *cd = &wh->coin_data[i];
1098 :
1099 180 : for (uint8_t k = 0; k < kappa; k++)
1100 : {
1101 99 : struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
1102 99 : struct CoinCandidate *can = &cd->candidates[k];
1103 :
1104 99 : TALER_blinded_planchet_free (&planchet->blinded_planchet);
1105 99 : TALER_denom_ewv_free (&can->details.blinding_values);
1106 99 : TALER_age_commitment_proof_free (&can->details.age_commitment_proof);
1107 : }
1108 81 : TALER_denom_pub_free (&cd->denom_pub.key);
1109 : }
1110 : }
1111 :
1112 75 : TALER_EXCHANGE_post_blinding_prepare_cancel (wh->blinding_prepare_handle);
1113 75 : wh->blinding_prepare_handle = NULL;
1114 75 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
1115 75 : wh->withdraw_blinded_handle = NULL;
1116 :
1117 75 : GNUNET_free (wh->bp_coins);
1118 75 : GNUNET_free (wh->bp_nonces);
1119 75 : GNUNET_free (wh->bp_nonce_keys);
1120 75 : GNUNET_free (wh->coin_data);
1121 75 : TALER_EXCHANGE_keys_decref (wh->keys);
1122 75 : GNUNET_free (wh);
1123 75 : }
1124 :
1125 :
1126 : /* exchange_api_post-withdraw.c */
|