exchange_api_post-withdraw_blinded.c (24387B)
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 withdraw_blinded_ok ( 181 struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh, 182 const json_t *j_response) 183 { 184 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 GNUNET_JSON_spec_array_const ("ev_sigs", 191 &j_sigs), 192 GNUNET_JSON_spec_end () 193 }; 194 195 if (GNUNET_OK != 196 GNUNET_JSON_parse (j_response, 197 spec, 198 NULL, NULL)) 199 { 200 GNUNET_break_op (0); 201 return GNUNET_SYSERR; 202 } 203 204 if (wbh->num_input != json_array_size (j_sigs)) 205 { 206 /* Number of coins generated does not match our expectation */ 207 GNUNET_break_op (0); 208 return GNUNET_SYSERR; 209 } 210 211 { 212 struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input]; 213 214 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 json_array_foreach (j_sigs, i, j_sig) 224 { 225 struct GNUNET_JSON_Specification ispec[] = { 226 TALER_JSON_spec_blinded_denom_sig (NULL, 227 &denoms_sig[i]), 228 GNUNET_JSON_spec_end () 229 }; 230 231 if (GNUNET_OK != 232 GNUNET_JSON_parse (j_sig, 233 ispec, 234 NULL, NULL)) 235 { 236 GNUNET_break_op (0); 237 return GNUNET_SYSERR; 238 } 239 } 240 } 241 242 response.details.ok.num_sigs = wbh->num_input; 243 response.details.ok.blinded_denom_sigs = denoms_sig; 244 response.details.ok.planchets_h = wbh->planchets_h; 245 wbh->callback ( 246 wbh->callback_cls, 247 &response); 248 /* Make sure the callback isn't called again */ 249 wbh->callback = NULL; 250 /* Free resources */ 251 for (size_t i = 0; i < wbh->num_input; i++) 252 TALER_blinded_denom_sig_free (&denoms_sig[i]); 253 } 254 255 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 withdraw_blinded_created ( 269 struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh, 270 const json_t *j_response) 271 { 272 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 .details.created.num_coins = wbh->num_input, 277 }; 278 struct TALER_ExchangeSignatureP exchange_sig; 279 struct GNUNET_JSON_Specification spec[] = { 280 GNUNET_JSON_spec_uint8 ("noreveal_index", 281 &response.details.created.noreveal_index), 282 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 283 &exchange_sig), 284 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 285 &response.details.created.exchange_pub), 286 GNUNET_JSON_spec_end () 287 }; 288 289 if (GNUNET_OK!= 290 GNUNET_JSON_parse (j_response, 291 spec, 292 NULL, NULL)) 293 { 294 GNUNET_break_op (0); 295 return GNUNET_SYSERR; 296 } 297 298 if (GNUNET_OK != 299 TALER_exchange_online_withdraw_age_confirmation_verify ( 300 &wbh->planchets_h, 301 response.details.created.noreveal_index, 302 &response.details.created.exchange_pub, 303 &exchange_sig)) 304 { 305 GNUNET_break_op (0); 306 return GNUNET_SYSERR; 307 308 } 309 310 wbh->callback (wbh->callback_cls, 311 &response); 312 /* make sure the callback isn't called again */ 313 wbh->callback = NULL; 314 315 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 handle_withdraw_blinded_finished ( 329 void *cls, 330 long response_code, 331 const void *response) 332 { 333 struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh = cls; 334 const json_t *j_response = response; 335 struct TALER_EXCHANGE_PostWithdrawBlindedResponse wbr = { 336 .hr.reply = j_response, 337 .hr.http_status = (unsigned int) response_code 338 }; 339 340 wbh->job = NULL; 341 switch (response_code) 342 { 343 case 0: 344 wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 345 break; 346 case MHD_HTTP_OK: 347 { 348 if (GNUNET_OK != 349 withdraw_blinded_ok ( 350 wbh, 351 j_response)) 352 { 353 GNUNET_break_op (0); 354 wbr.hr.http_status = 0; 355 wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 356 break; 357 } 358 GNUNET_assert (NULL == wbh->callback); 359 TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh); 360 return; 361 } 362 case MHD_HTTP_CREATED: 363 if (GNUNET_OK != 364 withdraw_blinded_created ( 365 wbh, 366 j_response)) 367 { 368 GNUNET_break_op (0); 369 wbr.hr.http_status = 0; 370 wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 371 break; 372 } 373 GNUNET_assert (NULL == wbh->callback); 374 TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh); 375 return; 376 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 380 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 381 break; 382 case MHD_HTTP_FORBIDDEN: 383 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 388 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 389 break; 390 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 396 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 397 break; 398 case MHD_HTTP_CONFLICT: 399 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 400 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 401 if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS == 402 wbr.hr.ec) 403 { 404 struct GNUNET_JSON_Specification spec[] = { 405 TALER_JSON_spec_amount_any ( 406 "balance", 407 &wbr.details.conflict.details.generic_insufficient_funds.balance), 408 TALER_JSON_spec_amount_any ( 409 "requested_amount", 410 &wbr.details.conflict.details.generic_insufficient_funds. 411 requested_amount), 412 GNUNET_JSON_spec_end () 413 }; 414 415 if (GNUNET_OK != 416 GNUNET_JSON_parse (j_response, 417 spec, 418 NULL, NULL)) 419 { 420 GNUNET_break_op (0); 421 wbr.hr.http_status = 0; 422 wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 423 break; 424 } 425 } 426 break; 427 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 433 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 434 break; 435 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 441 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 442 break; 443 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 444 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 445 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 446 if (GNUNET_OK != 447 TALER_EXCHANGE_parse_451 (&wbr.details.unavailable_for_legal_reasons, 448 j_response)) 449 { 450 GNUNET_break_op (0); 451 wbr.hr.http_status = 0; 452 wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 453 break; 454 } 455 break; 456 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 460 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 461 break; 462 case MHD_HTTP_NOT_IMPLEMENTED: 463 /* Server does not implement a feature (usually the cipher) */ 464 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 465 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 466 break; 467 case MHD_HTTP_BAD_GATEWAY: 468 /* Server could not talk to another component, usually this 469 indicates a problem with the secmod helper */ 470 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 471 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 472 break; 473 case MHD_HTTP_SERVICE_UNAVAILABLE: 474 /* Server had an internal issue; we should retry, but this API 475 leaves this to the application */ 476 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 477 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 478 break; 479 default: 480 /* unexpected response code */ 481 GNUNET_break_op (0); 482 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 483 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 484 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 break; 489 } 490 wbh->callback (wbh->callback_cls, 491 &wbr); 492 TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh); 493 } 494 495 496 struct TALER_EXCHANGE_PostWithdrawBlindedHandle * 497 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 GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle); 508 509 wbh->keys = TALER_EXCHANGE_keys_incref (keys); 510 wbh->curl_ctx = curl_ctx; 511 wbh->reserve_priv = reserve_priv; 512 wbh->request_url = TALER_url_join (exchange_url, 513 "withdraw", 514 NULL); 515 GNUNET_CRYPTO_eddsa_key_get_public ( 516 &wbh->reserve_priv->eddsa_priv, 517 &wbh->reserve_pub.eddsa_pub); 518 wbh->num_input = num_input; 519 wbh->blinded.input = blinded_input; 520 wbh->blinding_seed = blinding_seed; 521 522 return wbh; 523 } 524 525 526 enum GNUNET_GenericReturnValue 527 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 for (unsigned int i = 0; i < num_options; i++) 533 { 534 const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt = 535 &options[i]; 536 switch (opt->option) 537 { 538 case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END: 539 return GNUNET_OK; 540 case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF: 541 pwbh->with_age_proof = true; 542 pwbh->max_age = opt->details.with_age_proof.max_age; 543 pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input; 544 break; 545 } 546 } 547 return GNUNET_OK; 548 } 549 550 551 enum TALER_ErrorCode 552 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 json_t *j_denoms = NULL; 558 json_t *j_planchets = NULL; 559 json_t *j_request_body = NULL; 560 CURL *curlh = NULL; 561 struct GNUNET_HashContext *coins_hctx = NULL; 562 struct TALER_BlindedCoinHashP bch; 563 564 pwbh->callback = cb; 565 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 GNUNET_assert (0 < pwbh->num_input); 576 577 FAIL_IF (GNUNET_OK != 578 TALER_amount_set_zero (pwbh->keys->currency, 579 &pwbh->amount)); 580 FAIL_IF (GNUNET_OK != 581 TALER_amount_set_zero (pwbh->keys->currency, 582 &pwbh->fee)); 583 584 /* Accumulate total value with fees */ 585 for (size_t i = 0; i < pwbh->num_input; i++) 586 { 587 const struct TALER_EXCHANGE_DenomPublicKey *dpub = 588 pwbh->with_age_proof ? 589 pwbh->blinded.with_age_proof_input[i].denom_pub : 590 pwbh->blinded.input[i].denom_pub; 591 592 FAIL_IF (0 > 593 TALER_amount_add (&pwbh->amount, 594 &pwbh->amount, 595 &dpub->value)); 596 FAIL_IF (0 > 597 TALER_amount_add (&pwbh->fee, 598 &pwbh->fee, 599 &dpub->fees.withdraw)); 600 601 if (GNUNET_CRYPTO_BSA_CS == 602 dpub->key.bsign_pub_key->cipher) 603 GNUNET_assert (NULL != pwbh->blinding_seed); 604 605 } 606 607 if (pwbh->with_age_proof || pwbh->max_age > 0) 608 { 609 pwbh->age_mask = 610 pwbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask; 611 612 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 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 620 "Attempting to withdraw from reserve %s\n", 621 TALER_B2S (&pwbh->reserve_pub)); 622 } 623 624 coins_hctx = GNUNET_CRYPTO_hash_context_start (); 625 FAIL_IF (NULL == coins_hctx); 626 627 j_denoms = json_array (); 628 j_planchets = json_array (); 629 FAIL_IF ((NULL == j_denoms) || 630 (NULL == j_planchets)); 631 632 for (size_t i = 0; i< pwbh->num_input; i++) 633 { 634 /* Build the denomination array */ 635 const struct TALER_EXCHANGE_DenomPublicKey *denom_pub = 636 pwbh->with_age_proof ? 637 pwbh->blinded.with_age_proof_input[i].denom_pub : 638 pwbh->blinded.input[i].denom_pub; 639 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 FAIL_IF (pwbh->with_age_proof && 644 (pwbh->age_mask.bits != denom_pub->key.age_mask.bits)); 645 646 jdenom = GNUNET_JSON_from_data_auto (denom_h); 647 FAIL_IF (NULL == jdenom); 648 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 if (! pwbh->with_age_proof) 655 { 656 for (size_t i = 0; i< pwbh->num_input; i++) 657 { 658 const struct TALER_PlanchetDetail *planchet = 659 &pwbh->blinded.input[i].planchet_details; 660 json_t *jc = GNUNET_JSON_PACK ( 661 TALER_JSON_pack_blinded_planchet ( 662 NULL, 663 &planchet->blinded_planchet)); 664 FAIL_IF (NULL == jc); 665 FAIL_IF (0 > json_array_append_new (j_planchets, 666 jc)); 667 668 TALER_coin_ev_hash (&planchet->blinded_planchet, 669 &planchet->denom_pub_hash, 670 &bch); 671 672 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 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 batch_ctx = GNUNET_CRYPTO_hash_context_start (); 690 FAIL_IF (NULL == batch_ctx); 691 692 for (size_t i = 0; i< pwbh->num_input; i++) 693 { 694 const struct TALER_PlanchetDetail *planchet = 695 &pwbh->blinded.with_age_proof_input[i].planchet_details[k]; 696 json_t *jc = GNUNET_JSON_PACK ( 697 TALER_JSON_pack_blinded_planchet ( 698 NULL, 699 &planchet->blinded_planchet)); 700 701 FAIL_IF (NULL == jc); 702 FAIL_IF (0 > json_array_append_new ( 703 j_planchets, 704 jc)); 705 706 TALER_coin_ev_hash ( 707 &planchet->blinded_planchet, 708 &planchet->denom_pub_hash, 709 &bch); 710 711 GNUNET_CRYPTO_hash_context_read ( 712 batch_ctx, 713 &bch, 714 sizeof(bch)); 715 } 716 717 GNUNET_CRYPTO_hash_context_finish ( 718 batch_ctx, 719 &batch_h.hash); 720 GNUNET_CRYPTO_hash_context_read ( 721 coins_hctx, 722 &batch_h, 723 sizeof(batch_h)); 724 } 725 } 726 727 GNUNET_CRYPTO_hash_context_finish ( 728 coins_hctx, 729 &pwbh->planchets_h.hash); 730 coins_hctx = NULL; 731 732 TALER_wallet_withdraw_sign ( 733 &pwbh->amount, 734 &pwbh->fee, 735 &pwbh->planchets_h, 736 pwbh->blinding_seed, 737 pwbh->with_age_proof ? &pwbh->age_mask: NULL, 738 pwbh->with_age_proof ? pwbh->max_age : 0, 739 pwbh->reserve_priv, 740 &pwbh->reserve_sig); 741 742 /* Initiate the POST-request */ 743 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 FAIL_IF (NULL == j_request_body); 761 762 if (NULL != pwbh->blinding_seed) 763 { 764 json_t *j_seed = GNUNET_JSON_PACK ( 765 GNUNET_JSON_pack_data_auto ("blinding_seed", 766 pwbh->blinding_seed)); 767 GNUNET_assert (NULL != j_seed); 768 GNUNET_assert (0 == 769 json_object_update_new ( 770 j_request_body, 771 j_seed)); 772 } 773 774 curlh = TALER_EXCHANGE_curl_easy_get_ (pwbh->request_url); 775 FAIL_IF (NULL == curlh); 776 FAIL_IF (GNUNET_OK != 777 TALER_curl_easy_post ( 778 &pwbh->post_ctx, 779 curlh, 780 j_request_body)); 781 json_decref (j_request_body); 782 j_request_body = NULL; 783 784 pwbh->job = GNUNET_CURL_job_add2 ( 785 pwbh->curl_ctx, 786 curlh, 787 pwbh->post_ctx.headers, 788 &handle_withdraw_blinded_finished, 789 pwbh); 790 FAIL_IF (NULL == pwbh->job); 791 792 return TALER_EC_NONE; 793 794 ERROR: 795 if (NULL != coins_hctx) 796 GNUNET_CRYPTO_hash_context_abort (coins_hctx); 797 if (NULL != j_denoms) 798 json_decref (j_denoms); 799 if (NULL != j_planchets) 800 json_decref (j_planchets); 801 if (NULL != j_request_body) 802 json_decref (j_request_body); 803 if (NULL != curlh) 804 curl_easy_cleanup (curlh); 805 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 806 #undef FAIL_IF 807 } 808 809 810 void 811 TALER_EXCHANGE_post_withdraw_blinded_cancel ( 812 struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh) 813 { 814 if (NULL == pwbh) 815 return; 816 if (NULL != pwbh->job) 817 { 818 GNUNET_CURL_job_cancel (pwbh->job); 819 pwbh->job = NULL; 820 } 821 GNUNET_free (pwbh->request_url); 822 TALER_EXCHANGE_keys_decref (pwbh->keys); 823 TALER_curl_easy_post_finished (&pwbh->post_ctx); 824 GNUNET_free (pwbh); 825 } 826 827 828 /* exchange_api_post-withdraw_blinded.c */