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 : for (size_t k = 0; k < i; k++)
236 0 : TALER_blinded_denom_sig_free (&denoms_sig[k]);
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 3 : if (response.details.created.noreveal_index >= TALER_CNC_KAPPA)
298 : {
299 0 : GNUNET_break_op (0);
300 0 : return GNUNET_SYSERR;
301 : }
302 3 : if (GNUNET_OK !=
303 3 : TALER_EXCHANGE_test_signing_key (
304 3 : wbh->keys,
305 : &response.details.created.exchange_pub))
306 : {
307 0 : GNUNET_break_op (0);
308 0 : return GNUNET_SYSERR;
309 : }
310 3 : if (GNUNET_OK !=
311 3 : TALER_exchange_online_withdraw_age_confirmation_verify (
312 3 : &wbh->planchets_h,
313 3 : response.details.created.noreveal_index,
314 : &response.details.created.exchange_pub,
315 : &exchange_sig))
316 : {
317 0 : GNUNET_break_op (0);
318 0 : return GNUNET_SYSERR;
319 : }
320 3 : wbh->callback (wbh->callback_cls,
321 : &response);
322 : /* make sure the callback isn't called again */
323 3 : wbh->callback = NULL;
324 :
325 3 : return GNUNET_OK;
326 : }
327 :
328 :
329 : /**
330 : * Function called when we're done processing the
331 : * HTTP /withdraw request.
332 : *
333 : * @param cls the `struct TALER_EXCHANGE_PostWithdrawBlindedHandle`
334 : * @param response_code The HTTP response code
335 : * @param response response data
336 : */
337 : static void
338 75 : handle_withdraw_blinded_finished (
339 : void *cls,
340 : long response_code,
341 : const void *response)
342 : {
343 75 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh = cls;
344 75 : const json_t *j_response = response;
345 75 : struct TALER_EXCHANGE_PostWithdrawBlindedResponse wbr = {
346 : .hr.reply = j_response,
347 75 : .hr.http_status = (unsigned int) response_code
348 : };
349 :
350 75 : wbh->job = NULL;
351 75 : switch (response_code)
352 : {
353 0 : case 0:
354 0 : wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
355 0 : break;
356 61 : case MHD_HTTP_OK:
357 : {
358 61 : if (GNUNET_OK !=
359 61 : withdraw_blinded_ok (
360 : wbh,
361 : j_response))
362 : {
363 0 : GNUNET_break_op (0);
364 0 : wbr.hr.http_status = 0;
365 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
366 0 : break;
367 : }
368 61 : GNUNET_assert (NULL == wbh->callback);
369 61 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
370 64 : return;
371 : }
372 3 : case MHD_HTTP_CREATED:
373 3 : if (GNUNET_OK !=
374 3 : withdraw_blinded_created (
375 : wbh,
376 : j_response))
377 : {
378 0 : GNUNET_break_op (0);
379 0 : wbr.hr.http_status = 0;
380 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
381 0 : break;
382 : }
383 3 : GNUNET_assert (NULL == wbh->callback);
384 3 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
385 3 : return;
386 0 : case MHD_HTTP_BAD_REQUEST:
387 : /* This should never happen, either us or the exchange is buggy
388 : (or API version conflict); just pass JSON reply to the application */
389 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
390 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
391 0 : break;
392 0 : case MHD_HTTP_FORBIDDEN:
393 0 : GNUNET_break_op (0);
394 : /* Nothing really to verify, exchange says one of the signatures is
395 : invalid; as we checked them, this should never happen, we
396 : should pass the JSON reply to the application */
397 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
398 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
399 0 : break;
400 0 : case MHD_HTTP_NOT_FOUND:
401 : /* Nothing really to verify, the exchange basically just says
402 : that it doesn't know this reserve. Can happen if we
403 : query before the wire transfer went through.
404 : We should simply pass the JSON reply to the application. */
405 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
406 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
407 0 : break;
408 7 : case MHD_HTTP_CONFLICT:
409 7 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
410 7 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
411 7 : if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS ==
412 7 : wbr.hr.ec)
413 : {
414 : struct GNUNET_JSON_Specification spec[] = {
415 0 : TALER_JSON_spec_amount_any (
416 : "balance",
417 : &wbr.details.conflict.details.generic_insufficient_funds.balance),
418 0 : TALER_JSON_spec_amount_any (
419 : "requested_amount",
420 : &wbr.details.conflict.details.generic_insufficient_funds.
421 : requested_amount),
422 0 : GNUNET_JSON_spec_end ()
423 : };
424 :
425 0 : if (GNUNET_OK !=
426 0 : GNUNET_JSON_parse (j_response,
427 : spec,
428 : NULL, NULL))
429 : {
430 0 : GNUNET_break_op (0);
431 0 : wbr.hr.http_status = 0;
432 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
433 0 : break;
434 : }
435 : }
436 7 : break;
437 0 : case MHD_HTTP_GONE:
438 : /* could happen if denomination was revoked */
439 : /* Note: one might want to check /keys for revocation
440 : signature here, alas tricky in case our /keys
441 : is outdated => left to clients */
442 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
443 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
444 0 : break;
445 0 : case MHD_HTTP_PRECONDITION_FAILED:
446 : /* could happen if we were too early and the denomination
447 : is not yet available */
448 : /* Note: one might want to check the "Date" header to
449 : see if our clock is very far off */
450 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
451 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
452 0 : break;
453 4 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
454 4 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
455 4 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
456 4 : if (GNUNET_OK !=
457 4 : TALER_EXCHANGE_parse_451 (&wbr.details.unavailable_for_legal_reasons,
458 : j_response))
459 : {
460 0 : GNUNET_break_op (0);
461 0 : wbr.hr.http_status = 0;
462 0 : wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
463 0 : break;
464 : }
465 4 : break;
466 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
467 : /* Server had an internal issue; we should retry, but this API
468 : leaves this to the application */
469 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
470 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
471 0 : break;
472 0 : case MHD_HTTP_NOT_IMPLEMENTED:
473 : /* Server does not implement a feature (usually the cipher) */
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 : case MHD_HTTP_BAD_GATEWAY:
478 : /* Server could not talk to another component, usually this
479 : indicates a problem with the secmod helper */
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 : break;
483 0 : case MHD_HTTP_SERVICE_UNAVAILABLE:
484 : /* Server had an internal issue; we should retry, but this API
485 : leaves this to the application */
486 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
487 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
488 0 : break;
489 0 : default:
490 : /* unexpected response code */
491 0 : GNUNET_break_op (0);
492 0 : wbr.hr.ec = TALER_JSON_get_error_code (j_response);
493 0 : wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
494 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
495 : "Unexpected response code %u/%d for exchange withdraw\n",
496 : (unsigned int) response_code,
497 : (int) wbr.hr.ec);
498 0 : break;
499 : }
500 11 : wbh->callback (wbh->callback_cls,
501 : &wbr);
502 11 : TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
503 : }
504 :
505 :
506 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
507 75 : TALER_EXCHANGE_post_withdraw_blinded_create (
508 : struct GNUNET_CURL_Context *curl_ctx,
509 : struct TALER_EXCHANGE_Keys *keys,
510 : const char *exchange_url,
511 : const struct TALER_ReservePrivateKeyP *reserve_priv,
512 : const struct TALER_BlindingMasterSeedP *blinding_seed,
513 : size_t num_input,
514 : const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *blinded_input)
515 : {
516 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
517 75 : GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle);
518 :
519 75 : wbh->keys = TALER_EXCHANGE_keys_incref (keys);
520 75 : wbh->curl_ctx = curl_ctx;
521 75 : wbh->reserve_priv = reserve_priv;
522 75 : wbh->request_url = TALER_url_join (exchange_url,
523 : "withdraw",
524 : NULL);
525 75 : GNUNET_CRYPTO_eddsa_key_get_public (
526 75 : &wbh->reserve_priv->eddsa_priv,
527 : &wbh->reserve_pub.eddsa_pub);
528 75 : wbh->num_input = num_input;
529 75 : wbh->blinded.input = blinded_input;
530 75 : wbh->blinding_seed = blinding_seed;
531 :
532 75 : return wbh;
533 : }
534 :
535 :
536 : enum GNUNET_GenericReturnValue
537 5 : TALER_EXCHANGE_post_withdraw_blinded_set_options_ (
538 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
539 : unsigned int num_options,
540 : const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue options[])
541 : {
542 10 : for (unsigned int i = 0; i < num_options; i++)
543 : {
544 10 : const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt =
545 10 : &options[i];
546 10 : switch (opt->option)
547 : {
548 5 : case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END:
549 5 : return GNUNET_OK;
550 5 : case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF:
551 5 : pwbh->with_age_proof = true;
552 5 : pwbh->max_age = opt->details.with_age_proof.max_age;
553 5 : pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input;
554 5 : break;
555 : }
556 : }
557 0 : return GNUNET_OK;
558 : }
559 :
560 :
561 : enum TALER_ErrorCode
562 75 : TALER_EXCHANGE_post_withdraw_blinded_start (
563 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
564 : TALER_EXCHANGE_PostWithdrawBlindedCallback cb,
565 : TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cb_cls)
566 : {
567 75 : json_t *j_denoms = NULL;
568 75 : json_t *j_planchets = NULL;
569 75 : json_t *j_request_body = NULL;
570 75 : CURL *curlh = NULL;
571 75 : struct GNUNET_HashContext *coins_hctx = NULL;
572 : struct TALER_BlindedCoinHashP bch;
573 :
574 75 : pwbh->callback = cb;
575 75 : pwbh->callback_cls = cb_cls;
576 : #define FAIL_IF(cond) \
577 : do { \
578 : if ((cond)) \
579 : { \
580 : GNUNET_break (! (cond)); \
581 : goto ERROR; \
582 : } \
583 : } while (0)
584 :
585 75 : GNUNET_assert (0 < pwbh->num_input);
586 :
587 75 : FAIL_IF (GNUNET_OK !=
588 : TALER_amount_set_zero (pwbh->keys->currency,
589 : &pwbh->amount));
590 75 : FAIL_IF (GNUNET_OK !=
591 : TALER_amount_set_zero (pwbh->keys->currency,
592 : &pwbh->fee));
593 :
594 : /* Accumulate total value with fees */
595 156 : for (size_t i = 0; i < pwbh->num_input; i++)
596 : {
597 81 : const struct TALER_EXCHANGE_DenomPublicKey *dpub =
598 81 : pwbh->with_age_proof ?
599 81 : pwbh->blinded.with_age_proof_input[i].denom_pub :
600 72 : pwbh->blinded.input[i].denom_pub;
601 :
602 81 : FAIL_IF (0 >
603 : TALER_amount_add (&pwbh->amount,
604 : &pwbh->amount,
605 : &dpub->value));
606 81 : FAIL_IF (0 >
607 : TALER_amount_add (&pwbh->fee,
608 : &pwbh->fee,
609 : &dpub->fees.withdraw));
610 :
611 81 : if (GNUNET_CRYPTO_BSA_CS ==
612 81 : dpub->key.bsign_pub_key->cipher)
613 38 : GNUNET_assert (NULL != pwbh->blinding_seed);
614 :
615 : }
616 :
617 75 : if (pwbh->with_age_proof || pwbh->max_age > 0)
618 : {
619 5 : pwbh->age_mask =
620 5 : pwbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
621 :
622 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
623 : "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
624 : TALER_B2S (&pwbh->reserve_pub),
625 : pwbh->max_age);
626 : }
627 : else
628 : {
629 70 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
630 : "Attempting to withdraw from reserve %s\n",
631 : TALER_B2S (&pwbh->reserve_pub));
632 : }
633 :
634 75 : coins_hctx = GNUNET_CRYPTO_hash_context_start ();
635 75 : FAIL_IF (NULL == coins_hctx);
636 :
637 75 : j_denoms = json_array ();
638 75 : j_planchets = json_array ();
639 75 : FAIL_IF ((NULL == j_denoms) ||
640 : (NULL == j_planchets));
641 :
642 156 : for (size_t i = 0; i< pwbh->num_input; i++)
643 : {
644 : /* Build the denomination array */
645 81 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
646 81 : pwbh->with_age_proof ?
647 81 : pwbh->blinded.with_age_proof_input[i].denom_pub :
648 72 : pwbh->blinded.input[i].denom_pub;
649 81 : const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
650 : json_t *jdenom;
651 :
652 : /* The mask must be the same for all coins */
653 81 : FAIL_IF (pwbh->with_age_proof &&
654 : (pwbh->age_mask.bits != denom_pub->key.age_mask.bits));
655 :
656 81 : jdenom = GNUNET_JSON_from_data_auto (denom_h);
657 81 : FAIL_IF (NULL == jdenom);
658 81 : FAIL_IF (0 > json_array_append_new (j_denoms,
659 : jdenom));
660 : }
661 :
662 :
663 : /* Build the planchet array and calculate the hash over all planchets. */
664 75 : if (! pwbh->with_age_proof)
665 : {
666 142 : for (size_t i = 0; i< pwbh->num_input; i++)
667 : {
668 72 : const struct TALER_PlanchetDetail *planchet =
669 72 : &pwbh->blinded.input[i].planchet_details;
670 72 : json_t *jc = GNUNET_JSON_PACK (
671 : TALER_JSON_pack_blinded_planchet (
672 : NULL,
673 : &planchet->blinded_planchet));
674 72 : FAIL_IF (NULL == jc);
675 72 : FAIL_IF (0 > json_array_append_new (j_planchets,
676 : jc));
677 :
678 72 : TALER_coin_ev_hash (&planchet->blinded_planchet,
679 : &planchet->denom_pub_hash,
680 : &bch);
681 :
682 72 : GNUNET_CRYPTO_hash_context_read (coins_hctx,
683 : &bch,
684 : sizeof(bch));
685 : }
686 : }
687 : else
688 : { /* Age restricted case with required age-proof. */
689 :
690 : /**
691 : * We collect the run of all coin candidates for the same γ index
692 : * first, then γ+1 etc.
693 : */
694 20 : for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
695 : {
696 : struct GNUNET_HashContext *batch_ctx;
697 : struct TALER_BlindedCoinHashP batch_h;
698 :
699 15 : batch_ctx = GNUNET_CRYPTO_hash_context_start ();
700 15 : FAIL_IF (NULL == batch_ctx);
701 :
702 42 : for (size_t i = 0; i< pwbh->num_input; i++)
703 : {
704 27 : const struct TALER_PlanchetDetail *planchet =
705 27 : &pwbh->blinded.with_age_proof_input[i].planchet_details[k];
706 27 : json_t *jc = GNUNET_JSON_PACK (
707 : TALER_JSON_pack_blinded_planchet (
708 : NULL,
709 : &planchet->blinded_planchet));
710 :
711 27 : FAIL_IF (NULL == jc);
712 27 : FAIL_IF (0 > json_array_append_new (
713 : j_planchets,
714 : jc));
715 :
716 27 : TALER_coin_ev_hash (
717 : &planchet->blinded_planchet,
718 : &planchet->denom_pub_hash,
719 : &bch);
720 :
721 27 : GNUNET_CRYPTO_hash_context_read (
722 : batch_ctx,
723 : &bch,
724 : sizeof(bch));
725 : }
726 :
727 15 : GNUNET_CRYPTO_hash_context_finish (
728 : batch_ctx,
729 : &batch_h.hash);
730 15 : GNUNET_CRYPTO_hash_context_read (
731 : coins_hctx,
732 : &batch_h,
733 : sizeof(batch_h));
734 : }
735 : }
736 :
737 75 : GNUNET_CRYPTO_hash_context_finish (
738 : coins_hctx,
739 : &pwbh->planchets_h.hash);
740 75 : coins_hctx = NULL;
741 :
742 145 : TALER_wallet_withdraw_sign (
743 75 : &pwbh->amount,
744 75 : &pwbh->fee,
745 75 : &pwbh->planchets_h,
746 : pwbh->blinding_seed,
747 75 : pwbh->with_age_proof ? &pwbh->age_mask: NULL,
748 75 : pwbh->with_age_proof ? pwbh->max_age : 0,
749 : pwbh->reserve_priv,
750 : &pwbh->reserve_sig);
751 :
752 : /* Initiate the POST-request */
753 75 : j_request_body = GNUNET_JSON_PACK (
754 : GNUNET_JSON_pack_string ("cipher",
755 : "ED25519"),
756 : GNUNET_JSON_pack_data_auto ("reserve_pub",
757 : &pwbh->reserve_pub),
758 : GNUNET_JSON_pack_array_steal ("denoms_h",
759 : j_denoms),
760 : GNUNET_JSON_pack_array_steal ("coin_evs",
761 : j_planchets),
762 : GNUNET_JSON_pack_allow_null (
763 : pwbh->with_age_proof
764 : ? GNUNET_JSON_pack_uint64 ("max_age",
765 : pwbh->max_age)
766 : : GNUNET_JSON_pack_string ("max_age",
767 : NULL) ),
768 : GNUNET_JSON_pack_data_auto ("reserve_sig",
769 : &pwbh->reserve_sig));
770 : /* both arrays are now owned by j_request_body */
771 75 : j_denoms = NULL;
772 75 : j_planchets = NULL;
773 75 : FAIL_IF (NULL == j_request_body);
774 :
775 75 : if (NULL != pwbh->blinding_seed)
776 : {
777 35 : json_t *j_seed = GNUNET_JSON_PACK (
778 : GNUNET_JSON_pack_data_auto ("blinding_seed",
779 : pwbh->blinding_seed));
780 35 : GNUNET_assert (NULL != j_seed);
781 35 : GNUNET_assert (0 ==
782 : json_object_update_new (
783 : j_request_body,
784 : j_seed));
785 : }
786 :
787 75 : curlh = TALER_EXCHANGE_curl_easy_get_ (pwbh->request_url);
788 75 : FAIL_IF (NULL == curlh);
789 75 : FAIL_IF (GNUNET_OK !=
790 : TALER_curl_easy_post (
791 : &pwbh->post_ctx,
792 : curlh,
793 : j_request_body));
794 75 : json_decref (j_request_body);
795 75 : j_request_body = NULL;
796 :
797 150 : pwbh->job = GNUNET_CURL_job_add2 (
798 : pwbh->curl_ctx,
799 : curlh,
800 75 : pwbh->post_ctx.headers,
801 : &handle_withdraw_blinded_finished,
802 : pwbh);
803 75 : FAIL_IF (NULL == pwbh->job);
804 :
805 75 : return TALER_EC_NONE;
806 :
807 0 : ERROR:
808 0 : if (NULL != coins_hctx)
809 0 : GNUNET_CRYPTO_hash_context_abort (coins_hctx);
810 0 : if (NULL != j_denoms)
811 0 : json_decref (j_denoms);
812 0 : if (NULL != j_planchets)
813 0 : json_decref (j_planchets);
814 0 : if (NULL != j_request_body)
815 0 : json_decref (j_request_body);
816 0 : if (NULL != curlh)
817 0 : curl_easy_cleanup (curlh);
818 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
819 : #undef FAIL_IF
820 : }
821 :
822 :
823 : void
824 150 : TALER_EXCHANGE_post_withdraw_blinded_cancel (
825 : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh)
826 : {
827 150 : if (NULL == pwbh)
828 75 : return;
829 75 : if (NULL != pwbh->job)
830 : {
831 0 : GNUNET_CURL_job_cancel (pwbh->job);
832 0 : pwbh->job = NULL;
833 : }
834 75 : GNUNET_free (pwbh->request_url);
835 75 : TALER_EXCHANGE_keys_decref (pwbh->keys);
836 75 : TALER_curl_easy_post_finished (&pwbh->post_ctx);
837 75 : GNUNET_free (pwbh);
838 : }
839 :
840 :
841 : /* exchange_api_post-withdraw_blinded.c */
|