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