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