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 7 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
400 7 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
401 7 : if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS ==
402 7 : wbr.hr.ec)
403 : {
404 : struct GNUNET_JSON_Specification spec[] = {
405 0 : TALER_JSON_spec_amount_any (
406 : "balance",
407 : &wbr.details.conflict.details.generic_insufficient_funds.balance),
408 0 : TALER_JSON_spec_amount_any (
409 : "requested_amount",
410 : &wbr.details.conflict.details.generic_insufficient_funds.
411 : requested_amount),
412 0 : GNUNET_JSON_spec_end ()
413 : };
414 :
415 0 : if (GNUNET_OK !=
416 0 : GNUNET_JSON_parse (j_response,
417 : spec,
418 : NULL, NULL))
419 : {
420 0 : GNUNET_break_op (0);
421 0 : wbr.hr.http_status = 0;
422 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
423 0 : break;
424 : }
425 : }
426 7 : break;
427 0 : case MHD_HTTP_GONE:
428 : /* could happen if denomination was revoked */
429 : /* Note: one might want to check /keys for revocation
430 : signature here, alas tricky in case our /keys
431 : is outdated => left to clients */
432 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
433 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
434 0 : break;
435 0 : case MHD_HTTP_PRECONDITION_FAILED:
436 : /* could happen if we were too early and the denomination
437 : is not yet available */
438 : /* Note: one might want to check the "Date" header to
439 : see if our clock is very far off */
440 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
441 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
442 0 : break;
443 4 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
444 4 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
445 4 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
446 4 : if (GNUNET_OK !=
447 4 : TALER_EXCHANGE_parse_451 (&wbr.details.unavailable_for_legal_reasons,
448 : j_response))
449 : {
450 0 : GNUNET_break_op (0);
451 0 : wbr.hr.http_status = 0;
452 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
453 0 : break;
454 : }
455 4 : break;
456 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
457 : /* Server had an internal issue; we should retry, but this API
458 : leaves this to the application */
459 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
460 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
461 0 : break;
462 0 : case MHD_HTTP_NOT_IMPLEMENTED:
463 : /* Server does not implement a feature (usually the cipher) */
464 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
465 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
466 0 : break;
467 0 : case MHD_HTTP_BAD_GATEWAY:
468 : /* Server could not talk to another component, usually this
469 : indicates a problem with the secmod helper */
470 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
471 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
472 0 : break;
473 0 : case MHD_HTTP_SERVICE_UNAVAILABLE:
474 : /* Server had an internal issue; we should retry, but this API
475 : leaves this to the application */
476 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
477 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
478 0 : break;
479 0 : default:
480 : /* unexpected response code */
481 0 : GNUNET_break_op (0);
482 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
483 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
484 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
485 : "Unexpected response code %u/%d for exchange withdraw\n",
486 : (unsigned int) response_code,
487 : (int) wbr.hr.ec);
488 0 : break;
489 : }
490 11 : wbh->callback (wbh->callback_cls,
491 : &wbr);
492 11 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
493 : }
494 :
495 :
496 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
497 75 : TALER_EXCHANGE_post_withdraw_blinded_create (
498 : struct GNUNET_CURL_Context *curl_ctx,
499 : struct TALER_EXCHANGE_Keys *keys,
500 : const char *exchange_url,
501 : const struct TALER_ReservePrivateKeyP *reserve_priv,
502 : const struct TALER_BlindingMasterSeedP *blinding_seed,
503 : size_t num_input,
504 : const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *blinded_input)
505 : {
506 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
507 75 : GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle);
508 :
509 75 : wbh->keys = TALER_EXCHANGE_keys_incref (keys);
510 75 : wbh->curl_ctx = curl_ctx;
511 75 : wbh->reserve_priv = reserve_priv;
512 75 : wbh->request_url = TALER_url_join (exchange_url,
513 : "withdraw",
514 : NULL);
515 75 : GNUNET_CRYPTO_eddsa_key_get_public (
516 75 : &wbh->reserve_priv->eddsa_priv,
517 : &wbh->reserve_pub.eddsa_pub);
518 75 : wbh->num_input = num_input;
519 75 : wbh->blinded.input = blinded_input;
520 75 : wbh->blinding_seed = blinding_seed;
521 :
522 75 : return wbh;
523 : }
524 :
525 :
526 : enum GNUNET_GenericReturnValue
527 5 : TALER_EXCHANGE_post_withdraw_blinded_set_options_ (
528 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
529 : unsigned int num_options,
530 : const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue options[])
531 : {
532 10 : for (unsigned int i = 0; i < num_options; i++)
533 : {
534 10 : const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt =
535 10 : &options[i];
536 10 : switch (opt->option)
537 : {
538 5 : case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END:
539 5 : return GNUNET_OK;
540 5 : case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF:
541 5 : pwbh->with_age_proof = true;
542 5 : pwbh->max_age = opt->details.with_age_proof.max_age;
543 5 : pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input;
544 5 : break;
545 : }
546 : }
547 0 : return GNUNET_OK;
548 : }
549 :
550 :
551 : enum TALER_ErrorCode
552 75 : TALER_EXCHANGE_post_withdraw_blinded_start (
553 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
554 : TALER_EXCHANGE_PostWithdrawBlindedCallback cb,
555 : TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cb_cls)
556 : {
557 75 : json_t *j_denoms = NULL;
558 75 : json_t *j_planchets = NULL;
559 75 : json_t *j_request_body = NULL;
560 75 : CURL *curlh = NULL;
561 75 : struct GNUNET_HashContext *coins_hctx = NULL;
562 : struct TALER_BlindedCoinHashP bch;
563 :
564 75 : pwbh->callback = cb;
565 75 : pwbh->callback_cls = cb_cls;
566 : #define FAIL_IF(cond) \
567 : do { \
568 : if ((cond)) \
569 : { \
570 : GNUNET_break (! (cond)); \
571 : goto ERROR; \
572 : } \
573 : } while (0)
574 :
575 75 : GNUNET_assert (0 < pwbh->num_input);
576 :
577 75 : FAIL_IF (GNUNET_OK !=
578 : TALER_amount_set_zero (pwbh->keys->currency,
579 : &pwbh->amount));
580 75 : FAIL_IF (GNUNET_OK !=
581 : TALER_amount_set_zero (pwbh->keys->currency,
582 : &pwbh->fee));
583 :
584 : /* Accumulate total value with fees */
585 156 : for (size_t i = 0; i < pwbh->num_input; i++)
586 : {
587 81 : const struct TALER_EXCHANGE_DenomPublicKey *dpub =
588 81 : pwbh->with_age_proof ?
589 81 : pwbh->blinded.with_age_proof_input[i].denom_pub :
590 72 : pwbh->blinded.input[i].denom_pub;
591 :
592 81 : FAIL_IF (0 >
593 : TALER_amount_add (&pwbh->amount,
594 : &pwbh->amount,
595 : &dpub->value));
596 81 : FAIL_IF (0 >
597 : TALER_amount_add (&pwbh->fee,
598 : &pwbh->fee,
599 : &dpub->fees.withdraw));
600 :
601 81 : if (GNUNET_CRYPTO_BSA_CS ==
602 81 : dpub->key.bsign_pub_key->cipher)
603 38 : GNUNET_assert (NULL != pwbh->blinding_seed);
604 :
605 : }
606 :
607 75 : if (pwbh->with_age_proof || pwbh->max_age > 0)
608 : {
609 5 : pwbh->age_mask =
610 5 : pwbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
611 :
612 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
613 : "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
614 : TALER_B2S (&pwbh->reserve_pub),
615 : pwbh->max_age);
616 : }
617 : else
618 : {
619 70 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
620 : "Attempting to withdraw from reserve %s\n",
621 : TALER_B2S (&pwbh->reserve_pub));
622 : }
623 :
624 75 : coins_hctx = GNUNET_CRYPTO_hash_context_start ();
625 75 : FAIL_IF (NULL == coins_hctx);
626 :
627 75 : j_denoms = json_array ();
628 75 : j_planchets = json_array ();
629 75 : FAIL_IF ((NULL == j_denoms) ||
630 : (NULL == j_planchets));
631 :
632 156 : for (size_t i = 0; i< pwbh->num_input; i++)
633 : {
634 : /* Build the denomination array */
635 81 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
636 81 : pwbh->with_age_proof ?
637 81 : pwbh->blinded.with_age_proof_input[i].denom_pub :
638 72 : pwbh->blinded.input[i].denom_pub;
639 81 : const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
640 : json_t *jdenom;
641 :
642 : /* The mask must be the same for all coins */
643 81 : FAIL_IF (pwbh->with_age_proof &&
644 : (pwbh->age_mask.bits != denom_pub->key.age_mask.bits));
645 :
646 81 : jdenom = GNUNET_JSON_from_data_auto (denom_h);
647 81 : FAIL_IF (NULL == jdenom);
648 81 : FAIL_IF (0 > json_array_append_new (j_denoms,
649 : jdenom));
650 : }
651 :
652 :
653 : /* Build the planchet array and calculate the hash over all planchets. */
654 75 : if (! pwbh->with_age_proof)
655 : {
656 142 : for (size_t i = 0; i< pwbh->num_input; i++)
657 : {
658 72 : const struct TALER_PlanchetDetail *planchet =
659 72 : &pwbh->blinded.input[i].planchet_details;
660 72 : json_t *jc = GNUNET_JSON_PACK (
661 : TALER_JSON_pack_blinded_planchet (
662 : NULL,
663 : &planchet->blinded_planchet));
664 72 : FAIL_IF (NULL == jc);
665 72 : FAIL_IF (0 > json_array_append_new (j_planchets,
666 : jc));
667 :
668 72 : TALER_coin_ev_hash (&planchet->blinded_planchet,
669 : &planchet->denom_pub_hash,
670 : &bch);
671 :
672 72 : GNUNET_CRYPTO_hash_context_read (coins_hctx,
673 : &bch,
674 : sizeof(bch));
675 : }
676 : }
677 : else
678 : { /* Age restricted case with required age-proof. */
679 :
680 : /**
681 : * We collect the run of all coin candidates for the same γ index
682 : * first, then γ+1 etc.
683 : */
684 20 : for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
685 : {
686 : struct GNUNET_HashContext *batch_ctx;
687 : struct TALER_BlindedCoinHashP batch_h;
688 :
689 15 : batch_ctx = GNUNET_CRYPTO_hash_context_start ();
690 15 : FAIL_IF (NULL == batch_ctx);
691 :
692 42 : for (size_t i = 0; i< pwbh->num_input; i++)
693 : {
694 27 : const struct TALER_PlanchetDetail *planchet =
695 27 : &pwbh->blinded.with_age_proof_input[i].planchet_details[k];
696 27 : json_t *jc = GNUNET_JSON_PACK (
697 : TALER_JSON_pack_blinded_planchet (
698 : NULL,
699 : &planchet->blinded_planchet));
700 :
701 27 : FAIL_IF (NULL == jc);
702 27 : FAIL_IF (0 > json_array_append_new (
703 : j_planchets,
704 : jc));
705 :
706 27 : TALER_coin_ev_hash (
707 : &planchet->blinded_planchet,
708 : &planchet->denom_pub_hash,
709 : &bch);
710 :
711 27 : GNUNET_CRYPTO_hash_context_read (
712 : batch_ctx,
713 : &bch,
714 : sizeof(bch));
715 : }
716 :
717 15 : GNUNET_CRYPTO_hash_context_finish (
718 : batch_ctx,
719 : &batch_h.hash);
720 15 : GNUNET_CRYPTO_hash_context_read (
721 : coins_hctx,
722 : &batch_h,
723 : sizeof(batch_h));
724 : }
725 : }
726 :
727 75 : GNUNET_CRYPTO_hash_context_finish (
728 : coins_hctx,
729 : &pwbh->planchets_h.hash);
730 75 : coins_hctx = NULL;
731 :
732 145 : TALER_wallet_withdraw_sign (
733 75 : &pwbh->amount,
734 75 : &pwbh->fee,
735 75 : &pwbh->planchets_h,
736 : pwbh->blinding_seed,
737 75 : pwbh->with_age_proof ? &pwbh->age_mask: NULL,
738 75 : pwbh->with_age_proof ? pwbh->max_age : 0,
739 : pwbh->reserve_priv,
740 : &pwbh->reserve_sig);
741 :
742 : /* Initiate the POST-request */
743 75 : j_request_body = GNUNET_JSON_PACK (
744 : GNUNET_JSON_pack_string ("cipher",
745 : "ED25519"),
746 : GNUNET_JSON_pack_data_auto ("reserve_pub",
747 : &pwbh->reserve_pub),
748 : GNUNET_JSON_pack_array_steal ("denoms_h",
749 : j_denoms),
750 : GNUNET_JSON_pack_array_steal ("coin_evs",
751 : j_planchets),
752 : GNUNET_JSON_pack_allow_null (
753 : pwbh->with_age_proof
754 : ? GNUNET_JSON_pack_uint64 ("max_age",
755 : pwbh->max_age)
756 : : GNUNET_JSON_pack_string ("max_age",
757 : NULL) ),
758 : GNUNET_JSON_pack_data_auto ("reserve_sig",
759 : &pwbh->reserve_sig));
760 75 : FAIL_IF (NULL == j_request_body);
761 :
762 75 : if (NULL != pwbh->blinding_seed)
763 : {
764 35 : json_t *j_seed = GNUNET_JSON_PACK (
765 : GNUNET_JSON_pack_data_auto ("blinding_seed",
766 : pwbh->blinding_seed));
767 35 : GNUNET_assert (NULL != j_seed);
768 35 : GNUNET_assert (0 ==
769 : json_object_update_new (
770 : j_request_body,
771 : j_seed));
772 : }
773 :
774 75 : curlh = TALER_EXCHANGE_curl_easy_get_ (pwbh->request_url);
775 75 : FAIL_IF (NULL == curlh);
776 75 : FAIL_IF (GNUNET_OK !=
777 : TALER_curl_easy_post (
778 : &pwbh->post_ctx,
779 : curlh,
780 : j_request_body));
781 75 : json_decref (j_request_body);
782 75 : j_request_body = NULL;
783 :
784 150 : pwbh->job = GNUNET_CURL_job_add2 (
785 : pwbh->curl_ctx,
786 : curlh,
787 75 : pwbh->post_ctx.headers,
788 : &handle_withdraw_blinded_finished,
789 : pwbh);
790 75 : FAIL_IF (NULL == pwbh->job);
791 :
792 75 : return TALER_EC_NONE;
793 :
794 0 : ERROR:
795 0 : if (NULL != coins_hctx)
796 0 : GNUNET_CRYPTO_hash_context_abort (coins_hctx);
797 0 : if (NULL != j_denoms)
798 0 : json_decref (j_denoms);
799 0 : if (NULL != j_planchets)
800 0 : json_decref (j_planchets);
801 0 : if (NULL != j_request_body)
802 0 : json_decref (j_request_body);
803 0 : if (NULL != curlh)
804 0 : curl_easy_cleanup (curlh);
805 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
806 : #undef FAIL_IF
807 : }
808 :
809 :
810 : void
811 150 : TALER_EXCHANGE_post_withdraw_blinded_cancel (
812 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh)
813 : {
814 150 : if (NULL == pwbh)
815 75 : return;
816 75 : if (NULL != pwbh->job)
817 : {
818 0 : GNUNET_CURL_job_cancel (pwbh->job);
819 0 : pwbh->job = NULL;
820 : }
821 75 : GNUNET_free (pwbh->request_url);
822 75 : TALER_EXCHANGE_keys_decref (pwbh->keys);
823 75 : TALER_curl_easy_post_finished (&pwbh->post_ctx);
824 75 : GNUNET_free (pwbh);
825 : }
826 :
827 :
828 : /* exchange_api_post-withdraw_blinded.c */
|