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