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