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 : /**
42 : * A /withdraw request-handle for calls with pre-blinded planchets.
43 : * Returned by TALER_EXCHANGE_post_withdraw_blinded_create.
44 : */
45 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle
46 : {
47 :
48 : /**
49 : * Reserve private key.
50 : */
51 : const struct TALER_ReservePrivateKeyP *reserve_priv;
52 :
53 : /**
54 : * Reserve public key, calculated
55 : */
56 : struct TALER_ReservePublicKeyP reserve_pub;
57 :
58 : /**
59 : * Signature of the reserve for the request, calculated after all
60 : * parameters for the coins are collected.
61 : */
62 : struct TALER_ReserveSignatureP reserve_sig;
63 :
64 : /*
65 : * The denomination keys of the exchange
66 : */
67 : struct TALER_EXCHANGE_Keys *keys;
68 :
69 : /**
70 : * The hash of all the planchets
71 : */
72 : struct TALER_HashBlindedPlanchetsP planchets_h;
73 :
74 : /**
75 : * Seed used for the derival of blinding factors for denominations
76 : * with Clause-Schnorr cipher.
77 : */
78 : const struct TALER_BlindingMasterSeedP *blinding_seed;
79 :
80 : /**
81 : * Total amount requested (without fee).
82 : */
83 : struct TALER_Amount amount;
84 :
85 : /**
86 : * Total withdraw fee
87 : */
88 : struct TALER_Amount fee;
89 :
90 : /**
91 : * Is this call for age-restricted coins, with age proof?
92 : */
93 : bool with_age_proof;
94 :
95 : /**
96 : * If @e with_age_proof is true or @max_age is > 0,
97 : * the age mask to use, extracted from the denominations.
98 : * MUST be the same for all denominations.
99 : */
100 : struct TALER_AgeMask age_mask;
101 :
102 : /**
103 : * The maximum age to commit to. If @e with_age_proof
104 : * is true, the client will need to proof the correct setting
105 : * of age-restriction on the coins via an additional call
106 : * to /reveal-withdraw.
107 : */
108 : uint8_t max_age;
109 :
110 : /**
111 : * If @e with_age_proof is true, the hash of all the selected planchets
112 : */
113 : struct TALER_HashBlindedPlanchetsP selected_h;
114 :
115 : /**
116 : * Length of the either the @e blinded.input or
117 : * the @e blinded.with_age_proof_input array,
118 : * depending on @e with_age_proof.
119 : */
120 : size_t num_input;
121 :
122 : union
123 : {
124 : /**
125 : * The blinded planchet input candidates for age-restricted coins
126 : * for the call to /withdraw
127 : */
128 : const struct
129 : TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input;
130 :
131 : /**
132 : * The blinded planchet input for the call to /withdraw,
133 : * for age-unrestricted coins.
134 : */
135 : const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input;
136 :
137 : } blinded;
138 :
139 : /**
140 : * The url for this request.
141 : */
142 : char *request_url;
143 :
144 : /**
145 : * Context for curl.
146 : */
147 : struct GNUNET_CURL_Context *curl_ctx;
148 :
149 : /**
150 : * CURL handle for the request job.
151 : */
152 : struct GNUNET_CURL_Job *job;
153 :
154 : /**
155 : * Post Context
156 : */
157 : struct TALER_CURL_PostContext post_ctx;
158 :
159 : /**
160 : * Function to call with withdraw response results.
161 : */
162 : TALER_EXCHANGE_PostWithdrawBlindedCallback callback;
163 :
164 : /**
165 : * Closure for @e callback
166 : */
167 : void *callback_cls;
168 : };
169 :
170 :
171 : /**
172 : * We got a 200 OK response for the /withdraw operation.
173 : * Extract the signatures and return them to the caller.
174 : *
175 : * @param wbh operation handle
176 : * @param j_response reply from the exchange
177 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
178 : */
179 : static enum GNUNET_GenericReturnValue
180 61 : withdraw_blinded_ok (
181 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
182 : const json_t *j_response)
183 : {
184 61 : struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
185 : .hr.reply = j_response,
186 : .hr.http_status = MHD_HTTP_OK,
187 : };
188 : const json_t *j_sigs;
189 : struct GNUNET_JSON_Specification spec[] = {
190 61 : GNUNET_JSON_spec_array_const ("ev_sigs",
191 : &j_sigs),
192 61 : GNUNET_JSON_spec_end ()
193 : };
194 :
195 61 : if (GNUNET_OK !=
196 61 : GNUNET_JSON_parse (j_response,
197 : spec,
198 : NULL, NULL))
199 : {
200 0 : GNUNET_break_op (0);
201 0 : return GNUNET_SYSERR;
202 : }
203 :
204 61 : if (wbh->num_input != json_array_size (j_sigs))
205 : {
206 : /* Number of coins generated does not match our expectation */
207 0 : GNUNET_break_op (0);
208 0 : return GNUNET_SYSERR;
209 : }
210 :
211 61 : {
212 61 : struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input];
213 :
214 61 : memset (denoms_sig,
215 : 0,
216 : sizeof(denoms_sig));
217 :
218 : /* Reconstruct the coins and unblind the signatures */
219 : {
220 : json_t *j_sig;
221 : size_t i;
222 :
223 124 : json_array_foreach (j_sigs, i, j_sig)
224 : {
225 : struct GNUNET_JSON_Specification ispec[] = {
226 63 : TALER_JSON_spec_blinded_denom_sig (NULL,
227 : &denoms_sig[i]),
228 63 : GNUNET_JSON_spec_end ()
229 : };
230 :
231 63 : if (GNUNET_OK !=
232 63 : GNUNET_JSON_parse (j_sig,
233 : ispec,
234 : NULL, NULL))
235 : {
236 0 : GNUNET_break_op (0);
237 0 : return GNUNET_SYSERR;
238 : }
239 : }
240 : }
241 :
242 61 : response.details.ok.num_sigs = wbh->num_input;
243 61 : response.details.ok.blinded_denom_sigs = denoms_sig;
244 61 : response.details.ok.planchets_h = wbh->planchets_h;
245 61 : wbh->callback (
246 : wbh->callback_cls,
247 : &response);
248 : /* Make sure the callback isn't called again */
249 61 : wbh->callback = NULL;
250 : /* Free resources */
251 124 : for (size_t i = 0; i < wbh->num_input; i++)
252 63 : TALER_blinded_denom_sig_free (&denoms_sig[i]);
253 : }
254 :
255 61 : return GNUNET_OK;
256 : }
257 :
258 :
259 : /**
260 : * We got a 201 CREATED response for the /withdraw operation.
261 : * Extract the noreveal_index and return it to the caller.
262 : *
263 : * @param wbh operation handle
264 : * @param j_response reply from the exchange
265 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
266 : */
267 : static enum GNUNET_GenericReturnValue
268 3 : withdraw_blinded_created (
269 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
270 : const json_t *j_response)
271 : {
272 3 : struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
273 : .hr.reply = j_response,
274 : .hr.http_status = MHD_HTTP_CREATED,
275 : .details.created.planchets_h = wbh->planchets_h,
276 3 : .details.created.num_coins = wbh->num_input,
277 : };
278 : struct TALER_ExchangeSignatureP exchange_sig;
279 : struct GNUNET_JSON_Specification spec[] = {
280 3 : GNUNET_JSON_spec_uint8 ("noreveal_index",
281 : &response.details.created.noreveal_index),
282 3 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
283 : &exchange_sig),
284 3 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
285 : &response.details.created.exchange_pub),
286 3 : GNUNET_JSON_spec_end ()
287 : };
288 :
289 3 : if (GNUNET_OK!=
290 3 : GNUNET_JSON_parse (j_response,
291 : spec,
292 : NULL, NULL))
293 : {
294 0 : GNUNET_break_op (0);
295 0 : return GNUNET_SYSERR;
296 : }
297 :
298 3 : if (GNUNET_OK !=
299 3 : TALER_exchange_online_withdraw_age_confirmation_verify (
300 3 : &wbh->planchets_h,
301 3 : response.details.created.noreveal_index,
302 : &response.details.created.exchange_pub,
303 : &exchange_sig))
304 : {
305 0 : GNUNET_break_op (0);
306 0 : return GNUNET_SYSERR;
307 :
308 : }
309 :
310 3 : wbh->callback (wbh->callback_cls,
311 : &response);
312 : /* make sure the callback isn't called again */
313 3 : wbh->callback = NULL;
314 :
315 3 : return GNUNET_OK;
316 : }
317 :
318 :
319 : /**
320 : * Function called when we're done processing the
321 : * HTTP /withdraw request.
322 : *
323 : * @param cls the `struct TALER_EXCHANGE_PostWithdrawBlindedHandle`
324 : * @param response_code The HTTP response code
325 : * @param response response data
326 : */
327 : static void
328 75 : handle_withdraw_blinded_finished (
329 : void *cls,
330 : long response_code,
331 : const void *response)
332 : {
333 75 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh = cls;
334 75 : const json_t *j_response = response;
335 75 : struct TALER_EXCHANGE_PostWithdrawBlindedResponse wbr = {
336 : .hr.reply = j_response,
337 75 : .hr.http_status = (unsigned int) response_code
338 : };
339 :
340 75 : wbh->job = NULL;
341 75 : switch (response_code)
342 : {
343 0 : case 0:
344 0 : wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
345 0 : break;
346 61 : case MHD_HTTP_OK:
347 : {
348 61 : if (GNUNET_OK !=
349 61 : withdraw_blinded_ok (
350 : wbh,
351 : j_response))
352 : {
353 0 : GNUNET_break_op (0);
354 0 : wbr.hr.http_status = 0;
355 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
356 0 : break;
357 : }
358 61 : GNUNET_assert (NULL == wbh->callback);
359 61 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
360 64 : return;
361 : }
362 3 : case MHD_HTTP_CREATED:
363 3 : if (GNUNET_OK !=
364 3 : withdraw_blinded_created (
365 : wbh,
366 : j_response))
367 : {
368 0 : GNUNET_break_op (0);
369 0 : wbr.hr.http_status = 0;
370 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
371 0 : break;
372 : }
373 3 : GNUNET_assert (NULL == wbh->callback);
374 3 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
375 3 : return;
376 0 : case MHD_HTTP_BAD_REQUEST:
377 : /* This should never happen, either us or the exchange is buggy
378 : (or API version conflict); just pass JSON reply to the application */
379 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
380 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
381 0 : break;
382 0 : case MHD_HTTP_FORBIDDEN:
383 0 : GNUNET_break_op (0);
384 : /* Nothing really to verify, exchange says one of the signatures is
385 : invalid; as we checked them, this should never happen, we
386 : should pass the JSON reply to the application */
387 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
388 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
389 0 : break;
390 0 : case MHD_HTTP_NOT_FOUND:
391 : /* Nothing really to verify, the exchange basically just says
392 : that it doesn't know this reserve. Can happen if we
393 : query before the wire transfer went through.
394 : We should simply pass the JSON reply to the application. */
395 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
396 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
397 0 : break;
398 7 : case MHD_HTTP_CONFLICT:
399 : /* The age requirements might not have been met */
400 7 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
401 7 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
402 7 : break;
403 0 : case MHD_HTTP_GONE:
404 : /* could happen if denomination was revoked */
405 : /* Note: one might want to check /keys for revocation
406 : signature here, alas tricky in case our /keys
407 : is outdated => left to clients */
408 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
409 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
410 0 : break;
411 4 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
412 : /* only validate reply is well-formed */
413 : {
414 : struct GNUNET_JSON_Specification spec[] = {
415 4 : GNUNET_JSON_spec_fixed_auto (
416 : "h_payto",
417 : &wbr.details.unavailable_for_legal_reasons.h_payto),
418 4 : GNUNET_JSON_spec_uint64 (
419 : "requirement_row",
420 : &wbr.details.unavailable_for_legal_reasons.requirement_row),
421 4 : GNUNET_JSON_spec_end ()
422 : };
423 :
424 4 : if (GNUNET_OK !=
425 4 : GNUNET_JSON_parse (j_response,
426 : spec,
427 : NULL, NULL))
428 : {
429 0 : GNUNET_break_op (0);
430 0 : wbr.hr.http_status = 0;
431 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
432 0 : break;
433 : }
434 4 : break;
435 : }
436 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
437 : /* Server had an internal issue; we should retry, but this API
438 : leaves this to the application */
439 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
440 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
441 0 : break;
442 0 : default:
443 : /* unexpected response code */
444 0 : GNUNET_break_op (0);
445 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
446 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
447 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
448 : "Unexpected response code %u/%d for exchange withdraw\n",
449 : (unsigned int) response_code,
450 : (int) wbr.hr.ec);
451 0 : break;
452 : }
453 11 : wbh->callback (wbh->callback_cls,
454 : &wbr);
455 11 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
456 : }
457 :
458 :
459 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
460 75 : TALER_EXCHANGE_post_withdraw_blinded_create (
461 : struct GNUNET_CURL_Context *curl_ctx,
462 : struct TALER_EXCHANGE_Keys *keys,
463 : const char *exchange_url,
464 : const struct TALER_ReservePrivateKeyP *reserve_priv,
465 : const struct TALER_BlindingMasterSeedP *blinding_seed,
466 : size_t num_input,
467 : const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *blinded_input)
468 : {
469 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
470 75 : GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle);
471 :
472 75 : wbh->keys = TALER_EXCHANGE_keys_incref (keys);
473 75 : wbh->curl_ctx = curl_ctx;
474 75 : wbh->reserve_priv = reserve_priv;
475 75 : wbh->request_url = TALER_url_join (exchange_url,
476 : "withdraw",
477 : NULL);
478 75 : GNUNET_CRYPTO_eddsa_key_get_public (
479 75 : &wbh->reserve_priv->eddsa_priv,
480 : &wbh->reserve_pub.eddsa_pub);
481 75 : wbh->num_input = num_input;
482 75 : wbh->blinded.input = blinded_input;
483 75 : wbh->blinding_seed = blinding_seed;
484 :
485 75 : return wbh;
486 : }
487 :
488 :
489 : enum GNUNET_GenericReturnValue
490 5 : TALER_EXCHANGE_post_withdraw_blinded_set_options_ (
491 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
492 : unsigned int num_options,
493 : const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue options[])
494 : {
495 10 : for (unsigned int i = 0; i < num_options; i++)
496 : {
497 10 : const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt =
498 10 : &options[i];
499 10 : switch (opt->option)
500 : {
501 5 : case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END:
502 5 : return GNUNET_OK;
503 5 : case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF:
504 5 : pwbh->with_age_proof = true;
505 5 : pwbh->max_age = opt->details.with_age_proof.max_age;
506 5 : pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input;
507 5 : break;
508 : }
509 : }
510 0 : return GNUNET_OK;
511 : }
512 :
513 :
514 : enum TALER_ErrorCode
515 75 : TALER_EXCHANGE_post_withdraw_blinded_start (
516 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
517 : TALER_EXCHANGE_PostWithdrawBlindedCallback cb,
518 : TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cb_cls)
519 : {
520 75 : json_t *j_denoms = NULL;
521 75 : json_t *j_planchets = NULL;
522 75 : json_t *j_request_body = NULL;
523 75 : CURL *curlh = NULL;
524 75 : struct GNUNET_HashContext *coins_hctx = NULL;
525 : struct TALER_BlindedCoinHashP bch;
526 :
527 75 : pwbh->callback = cb;
528 75 : pwbh->callback_cls = cb_cls;
529 : #define FAIL_IF(cond) \
530 : do { \
531 : if ((cond)) \
532 : { \
533 : GNUNET_break (! (cond)); \
534 : goto ERROR; \
535 : } \
536 : } while (0)
537 :
538 75 : GNUNET_assert (0 < pwbh->num_input);
539 :
540 75 : FAIL_IF (GNUNET_OK !=
541 : TALER_amount_set_zero (pwbh->keys->currency,
542 : &pwbh->amount));
543 75 : FAIL_IF (GNUNET_OK !=
544 : TALER_amount_set_zero (pwbh->keys->currency,
545 : &pwbh->fee));
546 :
547 : /* Accumulate total value with fees */
548 156 : for (size_t i = 0; i < pwbh->num_input; i++)
549 : {
550 81 : const struct TALER_EXCHANGE_DenomPublicKey *dpub =
551 81 : pwbh->with_age_proof ?
552 81 : pwbh->blinded.with_age_proof_input[i].denom_pub :
553 72 : pwbh->blinded.input[i].denom_pub;
554 :
555 81 : FAIL_IF (0 >
556 : TALER_amount_add (&pwbh->amount,
557 : &pwbh->amount,
558 : &dpub->value));
559 81 : FAIL_IF (0 >
560 : TALER_amount_add (&pwbh->fee,
561 : &pwbh->fee,
562 : &dpub->fees.withdraw));
563 :
564 81 : if (GNUNET_CRYPTO_BSA_CS ==
565 81 : dpub->key.bsign_pub_key->cipher)
566 38 : GNUNET_assert (NULL != pwbh->blinding_seed);
567 :
568 : }
569 :
570 75 : if (pwbh->with_age_proof || pwbh->max_age > 0)
571 : {
572 5 : pwbh->age_mask =
573 5 : pwbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
574 :
575 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
576 : "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
577 : TALER_B2S (&pwbh->reserve_pub),
578 : pwbh->max_age);
579 : }
580 : else
581 : {
582 70 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
583 : "Attempting to withdraw from reserve %s\n",
584 : TALER_B2S (&pwbh->reserve_pub));
585 : }
586 :
587 75 : coins_hctx = GNUNET_CRYPTO_hash_context_start ();
588 75 : FAIL_IF (NULL == coins_hctx);
589 :
590 75 : j_denoms = json_array ();
591 75 : j_planchets = json_array ();
592 75 : FAIL_IF ((NULL == j_denoms) ||
593 : (NULL == j_planchets));
594 :
595 156 : for (size_t i = 0; i< pwbh->num_input; i++)
596 : {
597 : /* Build the denomination array */
598 81 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
599 81 : pwbh->with_age_proof ?
600 81 : pwbh->blinded.with_age_proof_input[i].denom_pub :
601 72 : pwbh->blinded.input[i].denom_pub;
602 81 : const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
603 : json_t *jdenom;
604 :
605 : /* The mask must be the same for all coins */
606 81 : FAIL_IF (pwbh->with_age_proof &&
607 : (pwbh->age_mask.bits != denom_pub->key.age_mask.bits));
608 :
609 81 : jdenom = GNUNET_JSON_from_data_auto (denom_h);
610 81 : FAIL_IF (NULL == jdenom);
611 81 : FAIL_IF (0 > json_array_append_new (j_denoms,
612 : jdenom));
613 : }
614 :
615 :
616 : /* Build the planchet array and calculate the hash over all planchets. */
617 75 : if (! pwbh->with_age_proof)
618 : {
619 142 : for (size_t i = 0; i< pwbh->num_input; i++)
620 : {
621 72 : const struct TALER_PlanchetDetail *planchet =
622 72 : &pwbh->blinded.input[i].planchet_details;
623 72 : json_t *jc = GNUNET_JSON_PACK (
624 : TALER_JSON_pack_blinded_planchet (
625 : NULL,
626 : &planchet->blinded_planchet));
627 72 : FAIL_IF (NULL == jc);
628 72 : FAIL_IF (0 > json_array_append_new (j_planchets,
629 : jc));
630 :
631 72 : TALER_coin_ev_hash (&planchet->blinded_planchet,
632 : &planchet->denom_pub_hash,
633 : &bch);
634 :
635 72 : GNUNET_CRYPTO_hash_context_read (coins_hctx,
636 : &bch,
637 : sizeof(bch));
638 : }
639 : }
640 : else
641 : { /* Age restricted case with required age-proof. */
642 :
643 : /**
644 : * We collect the run of all coin candidates for the same γ index
645 : * first, then γ+1 etc.
646 : */
647 20 : for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
648 : {
649 : struct GNUNET_HashContext *batch_ctx;
650 : struct TALER_BlindedCoinHashP batch_h;
651 :
652 15 : batch_ctx = GNUNET_CRYPTO_hash_context_start ();
653 15 : FAIL_IF (NULL == batch_ctx);
654 :
655 42 : for (size_t i = 0; i< pwbh->num_input; i++)
656 : {
657 27 : const struct TALER_PlanchetDetail *planchet =
658 27 : &pwbh->blinded.with_age_proof_input[i].planchet_details[k];
659 27 : json_t *jc = GNUNET_JSON_PACK (
660 : TALER_JSON_pack_blinded_planchet (
661 : NULL,
662 : &planchet->blinded_planchet));
663 :
664 27 : FAIL_IF (NULL == jc);
665 27 : FAIL_IF (0 > json_array_append_new (
666 : j_planchets,
667 : jc));
668 :
669 27 : TALER_coin_ev_hash (
670 : &planchet->blinded_planchet,
671 : &planchet->denom_pub_hash,
672 : &bch);
673 :
674 27 : GNUNET_CRYPTO_hash_context_read (
675 : batch_ctx,
676 : &bch,
677 : sizeof(bch));
678 : }
679 :
680 15 : GNUNET_CRYPTO_hash_context_finish (
681 : batch_ctx,
682 : &batch_h.hash);
683 15 : GNUNET_CRYPTO_hash_context_read (
684 : coins_hctx,
685 : &batch_h,
686 : sizeof(batch_h));
687 : }
688 : }
689 :
690 75 : GNUNET_CRYPTO_hash_context_finish (
691 : coins_hctx,
692 : &pwbh->planchets_h.hash);
693 75 : coins_hctx = NULL;
694 :
695 145 : TALER_wallet_withdraw_sign (
696 75 : &pwbh->amount,
697 75 : &pwbh->fee,
698 75 : &pwbh->planchets_h,
699 : pwbh->blinding_seed,
700 75 : pwbh->with_age_proof ? &pwbh->age_mask: NULL,
701 75 : pwbh->with_age_proof ? pwbh->max_age : 0,
702 : pwbh->reserve_priv,
703 : &pwbh->reserve_sig);
704 :
705 : /* Initiate the POST-request */
706 75 : j_request_body = GNUNET_JSON_PACK (
707 : GNUNET_JSON_pack_string ("cipher",
708 : "ED25519"),
709 : GNUNET_JSON_pack_data_auto ("reserve_pub",
710 : &pwbh->reserve_pub),
711 : GNUNET_JSON_pack_array_steal ("denoms_h",
712 : j_denoms),
713 : GNUNET_JSON_pack_array_steal ("coin_evs",
714 : j_planchets),
715 : GNUNET_JSON_pack_allow_null (
716 : pwbh->with_age_proof
717 : ? GNUNET_JSON_pack_int64 ("max_age",
718 : pwbh->max_age)
719 : : GNUNET_JSON_pack_string ("max_age",
720 : NULL) ),
721 : GNUNET_JSON_pack_data_auto ("reserve_sig",
722 : &pwbh->reserve_sig));
723 75 : FAIL_IF (NULL == j_request_body);
724 :
725 75 : if (NULL != pwbh->blinding_seed)
726 : {
727 35 : json_t *j_seed = GNUNET_JSON_PACK (
728 : GNUNET_JSON_pack_data_auto ("blinding_seed",
729 : pwbh->blinding_seed));
730 35 : GNUNET_assert (NULL != j_seed);
731 35 : GNUNET_assert (0 ==
732 : json_object_update_new (
733 : j_request_body,
734 : j_seed));
735 : }
736 :
737 75 : curlh = TALER_EXCHANGE_curl_easy_get_ (pwbh->request_url);
738 75 : FAIL_IF (NULL == curlh);
739 75 : FAIL_IF (GNUNET_OK !=
740 : TALER_curl_easy_post (
741 : &pwbh->post_ctx,
742 : curlh,
743 : j_request_body));
744 75 : json_decref (j_request_body);
745 75 : j_request_body = NULL;
746 :
747 150 : pwbh->job = GNUNET_CURL_job_add2 (
748 : pwbh->curl_ctx,
749 : curlh,
750 75 : pwbh->post_ctx.headers,
751 : &handle_withdraw_blinded_finished,
752 : pwbh);
753 75 : FAIL_IF (NULL == pwbh->job);
754 :
755 75 : return TALER_EC_NONE;
756 :
757 0 : ERROR:
758 0 : if (NULL != coins_hctx)
759 0 : GNUNET_CRYPTO_hash_context_abort (coins_hctx);
760 0 : if (NULL != j_denoms)
761 0 : json_decref (j_denoms);
762 0 : if (NULL != j_planchets)
763 0 : json_decref (j_planchets);
764 0 : if (NULL != j_request_body)
765 0 : json_decref (j_request_body);
766 0 : if (NULL != curlh)
767 0 : curl_easy_cleanup (curlh);
768 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
769 : #undef FAIL_IF
770 : }
771 :
772 :
773 : void
774 150 : TALER_EXCHANGE_post_withdraw_blinded_cancel (
775 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh)
776 : {
777 150 : if (NULL == pwbh)
778 75 : return;
779 75 : if (NULL != pwbh->job)
780 : {
781 0 : GNUNET_CURL_job_cancel (pwbh->job);
782 0 : pwbh->job = NULL;
783 : }
784 75 : GNUNET_free (pwbh->request_url);
785 75 : TALER_EXCHANGE_keys_decref (pwbh->keys);
786 75 : TALER_curl_easy_post_finished (&pwbh->post_ctx);
787 75 : GNUNET_free (pwbh);
788 : }
789 :
790 :
791 : /* exchange_api_post-withdraw_blinded.c */
|