exchange_api_post-withdraw_blinded.c (24725B)
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 for (size_t k = 0; k < i; k++) 236 TALER_blinded_denom_sig_free (&denoms_sig[k]); 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 if (response.details.created.noreveal_index >= TALER_CNC_KAPPA) 298 { 299 GNUNET_break_op (0); 300 return GNUNET_SYSERR; 301 } 302 if (GNUNET_OK != 303 TALER_EXCHANGE_test_signing_key ( 304 wbh->keys, 305 &response.details.created.exchange_pub)) 306 { 307 GNUNET_break_op (0); 308 return GNUNET_SYSERR; 309 } 310 if (GNUNET_OK != 311 TALER_exchange_online_withdraw_age_confirmation_verify ( 312 &wbh->planchets_h, 313 response.details.created.noreveal_index, 314 &response.details.created.exchange_pub, 315 &exchange_sig)) 316 { 317 GNUNET_break_op (0); 318 return GNUNET_SYSERR; 319 } 320 wbh->callback (wbh->callback_cls, 321 &response); 322 /* make sure the callback isn't called again */ 323 wbh->callback = NULL; 324 325 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 handle_withdraw_blinded_finished ( 339 void *cls, 340 long response_code, 341 const void *response) 342 { 343 struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh = cls; 344 const json_t *j_response = response; 345 struct TALER_EXCHANGE_PostWithdrawBlindedResponse wbr = { 346 .hr.reply = j_response, 347 .hr.http_status = (unsigned int) response_code 348 }; 349 350 wbh->job = NULL; 351 switch (response_code) 352 { 353 case 0: 354 wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 355 break; 356 case MHD_HTTP_OK: 357 { 358 if (GNUNET_OK != 359 withdraw_blinded_ok ( 360 wbh, 361 j_response)) 362 { 363 GNUNET_break_op (0); 364 wbr.hr.http_status = 0; 365 wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 366 break; 367 } 368 GNUNET_assert (NULL == wbh->callback); 369 TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh); 370 return; 371 } 372 case MHD_HTTP_CREATED: 373 if (GNUNET_OK != 374 withdraw_blinded_created ( 375 wbh, 376 j_response)) 377 { 378 GNUNET_break_op (0); 379 wbr.hr.http_status = 0; 380 wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 381 break; 382 } 383 GNUNET_assert (NULL == wbh->callback); 384 TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh); 385 return; 386 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 390 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 391 break; 392 case MHD_HTTP_FORBIDDEN: 393 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 398 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 399 break; 400 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 406 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 407 break; 408 case MHD_HTTP_CONFLICT: 409 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 410 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 411 if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS == 412 wbr.hr.ec) 413 { 414 struct GNUNET_JSON_Specification spec[] = { 415 TALER_JSON_spec_amount_any ( 416 "balance", 417 &wbr.details.conflict.details.generic_insufficient_funds.balance), 418 TALER_JSON_spec_amount_any ( 419 "requested_amount", 420 &wbr.details.conflict.details.generic_insufficient_funds. 421 requested_amount), 422 GNUNET_JSON_spec_end () 423 }; 424 425 if (GNUNET_OK != 426 GNUNET_JSON_parse (j_response, 427 spec, 428 NULL, NULL)) 429 { 430 GNUNET_break_op (0); 431 wbr.hr.http_status = 0; 432 wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 433 break; 434 } 435 } 436 break; 437 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 443 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 444 break; 445 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 451 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 452 break; 453 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 454 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 455 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 456 if (GNUNET_OK != 457 TALER_EXCHANGE_parse_451 (&wbr.details.unavailable_for_legal_reasons, 458 j_response)) 459 { 460 GNUNET_break_op (0); 461 wbr.hr.http_status = 0; 462 wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 463 break; 464 } 465 break; 466 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 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 470 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 471 break; 472 case MHD_HTTP_NOT_IMPLEMENTED: 473 /* Server does not implement a feature (usually the cipher) */ 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 case MHD_HTTP_BAD_GATEWAY: 478 /* Server could not talk to another component, usually this 479 indicates a problem with the secmod helper */ 480 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 481 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 482 break; 483 case MHD_HTTP_SERVICE_UNAVAILABLE: 484 /* Server had an internal issue; we should retry, but this API 485 leaves this to the application */ 486 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 487 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 488 break; 489 default: 490 /* unexpected response code */ 491 GNUNET_break_op (0); 492 wbr.hr.ec = TALER_JSON_get_error_code (j_response); 493 wbr.hr.hint = TALER_JSON_get_error_hint (j_response); 494 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 break; 499 } 500 wbh->callback (wbh->callback_cls, 501 &wbr); 502 TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh); 503 } 504 505 506 struct TALER_EXCHANGE_PostWithdrawBlindedHandle * 507 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 GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle); 518 519 wbh->keys = TALER_EXCHANGE_keys_incref (keys); 520 wbh->curl_ctx = curl_ctx; 521 wbh->reserve_priv = reserve_priv; 522 wbh->request_url = TALER_url_join (exchange_url, 523 "withdraw", 524 NULL); 525 GNUNET_CRYPTO_eddsa_key_get_public ( 526 &wbh->reserve_priv->eddsa_priv, 527 &wbh->reserve_pub.eddsa_pub); 528 wbh->num_input = num_input; 529 wbh->blinded.input = blinded_input; 530 wbh->blinding_seed = blinding_seed; 531 532 return wbh; 533 } 534 535 536 enum GNUNET_GenericReturnValue 537 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 for (unsigned int i = 0; i < num_options; i++) 543 { 544 const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt = 545 &options[i]; 546 switch (opt->option) 547 { 548 case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END: 549 return GNUNET_OK; 550 case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF: 551 pwbh->with_age_proof = true; 552 pwbh->max_age = opt->details.with_age_proof.max_age; 553 pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input; 554 break; 555 } 556 } 557 return GNUNET_OK; 558 } 559 560 561 enum TALER_ErrorCode 562 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 json_t *j_denoms = NULL; 568 json_t *j_planchets = NULL; 569 json_t *j_request_body = NULL; 570 CURL *curlh = NULL; 571 struct GNUNET_HashContext *coins_hctx = NULL; 572 struct TALER_BlindedCoinHashP bch; 573 574 pwbh->callback = cb; 575 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 GNUNET_assert (0 < pwbh->num_input); 586 587 FAIL_IF (GNUNET_OK != 588 TALER_amount_set_zero (pwbh->keys->currency, 589 &pwbh->amount)); 590 FAIL_IF (GNUNET_OK != 591 TALER_amount_set_zero (pwbh->keys->currency, 592 &pwbh->fee)); 593 594 /* Accumulate total value with fees */ 595 for (size_t i = 0; i < pwbh->num_input; i++) 596 { 597 const struct TALER_EXCHANGE_DenomPublicKey *dpub = 598 pwbh->with_age_proof ? 599 pwbh->blinded.with_age_proof_input[i].denom_pub : 600 pwbh->blinded.input[i].denom_pub; 601 602 FAIL_IF (0 > 603 TALER_amount_add (&pwbh->amount, 604 &pwbh->amount, 605 &dpub->value)); 606 FAIL_IF (0 > 607 TALER_amount_add (&pwbh->fee, 608 &pwbh->fee, 609 &dpub->fees.withdraw)); 610 611 if (GNUNET_CRYPTO_BSA_CS == 612 dpub->key.bsign_pub_key->cipher) 613 GNUNET_assert (NULL != pwbh->blinding_seed); 614 615 } 616 617 if (pwbh->with_age_proof || pwbh->max_age > 0) 618 { 619 pwbh->age_mask = 620 pwbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask; 621 622 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 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 630 "Attempting to withdraw from reserve %s\n", 631 TALER_B2S (&pwbh->reserve_pub)); 632 } 633 634 coins_hctx = GNUNET_CRYPTO_hash_context_start (); 635 FAIL_IF (NULL == coins_hctx); 636 637 j_denoms = json_array (); 638 j_planchets = json_array (); 639 FAIL_IF ((NULL == j_denoms) || 640 (NULL == j_planchets)); 641 642 for (size_t i = 0; i< pwbh->num_input; i++) 643 { 644 /* Build the denomination array */ 645 const struct TALER_EXCHANGE_DenomPublicKey *denom_pub = 646 pwbh->with_age_proof ? 647 pwbh->blinded.with_age_proof_input[i].denom_pub : 648 pwbh->blinded.input[i].denom_pub; 649 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 FAIL_IF (pwbh->with_age_proof && 654 (pwbh->age_mask.bits != denom_pub->key.age_mask.bits)); 655 656 jdenom = GNUNET_JSON_from_data_auto (denom_h); 657 FAIL_IF (NULL == jdenom); 658 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 if (! pwbh->with_age_proof) 665 { 666 for (size_t i = 0; i< pwbh->num_input; i++) 667 { 668 const struct TALER_PlanchetDetail *planchet = 669 &pwbh->blinded.input[i].planchet_details; 670 json_t *jc = GNUNET_JSON_PACK ( 671 TALER_JSON_pack_blinded_planchet ( 672 NULL, 673 &planchet->blinded_planchet)); 674 FAIL_IF (NULL == jc); 675 FAIL_IF (0 > json_array_append_new (j_planchets, 676 jc)); 677 678 TALER_coin_ev_hash (&planchet->blinded_planchet, 679 &planchet->denom_pub_hash, 680 &bch); 681 682 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 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 batch_ctx = GNUNET_CRYPTO_hash_context_start (); 700 FAIL_IF (NULL == batch_ctx); 701 702 for (size_t i = 0; i< pwbh->num_input; i++) 703 { 704 const struct TALER_PlanchetDetail *planchet = 705 &pwbh->blinded.with_age_proof_input[i].planchet_details[k]; 706 json_t *jc = GNUNET_JSON_PACK ( 707 TALER_JSON_pack_blinded_planchet ( 708 NULL, 709 &planchet->blinded_planchet)); 710 711 FAIL_IF (NULL == jc); 712 FAIL_IF (0 > json_array_append_new ( 713 j_planchets, 714 jc)); 715 716 TALER_coin_ev_hash ( 717 &planchet->blinded_planchet, 718 &planchet->denom_pub_hash, 719 &bch); 720 721 GNUNET_CRYPTO_hash_context_read ( 722 batch_ctx, 723 &bch, 724 sizeof(bch)); 725 } 726 727 GNUNET_CRYPTO_hash_context_finish ( 728 batch_ctx, 729 &batch_h.hash); 730 GNUNET_CRYPTO_hash_context_read ( 731 coins_hctx, 732 &batch_h, 733 sizeof(batch_h)); 734 } 735 } 736 737 GNUNET_CRYPTO_hash_context_finish ( 738 coins_hctx, 739 &pwbh->planchets_h.hash); 740 coins_hctx = NULL; 741 742 TALER_wallet_withdraw_sign ( 743 &pwbh->amount, 744 &pwbh->fee, 745 &pwbh->planchets_h, 746 pwbh->blinding_seed, 747 pwbh->with_age_proof ? &pwbh->age_mask: NULL, 748 pwbh->with_age_proof ? pwbh->max_age : 0, 749 pwbh->reserve_priv, 750 &pwbh->reserve_sig); 751 752 /* Initiate the POST-request */ 753 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 FAIL_IF (NULL == j_request_body); 771 772 if (NULL != pwbh->blinding_seed) 773 { 774 json_t *j_seed = GNUNET_JSON_PACK ( 775 GNUNET_JSON_pack_data_auto ("blinding_seed", 776 pwbh->blinding_seed)); 777 GNUNET_assert (NULL != j_seed); 778 GNUNET_assert (0 == 779 json_object_update_new ( 780 j_request_body, 781 j_seed)); 782 } 783 784 curlh = TALER_EXCHANGE_curl_easy_get_ (pwbh->request_url); 785 FAIL_IF (NULL == curlh); 786 FAIL_IF (GNUNET_OK != 787 TALER_curl_easy_post ( 788 &pwbh->post_ctx, 789 curlh, 790 j_request_body)); 791 json_decref (j_request_body); 792 j_request_body = NULL; 793 794 pwbh->job = GNUNET_CURL_job_add2 ( 795 pwbh->curl_ctx, 796 curlh, 797 pwbh->post_ctx.headers, 798 &handle_withdraw_blinded_finished, 799 pwbh); 800 FAIL_IF (NULL == pwbh->job); 801 802 return TALER_EC_NONE; 803 804 ERROR: 805 if (NULL != coins_hctx) 806 GNUNET_CRYPTO_hash_context_abort (coins_hctx); 807 if (NULL != j_denoms) 808 json_decref (j_denoms); 809 if (NULL != j_planchets) 810 json_decref (j_planchets); 811 if (NULL != j_request_body) 812 json_decref (j_request_body); 813 if (NULL != curlh) 814 curl_easy_cleanup (curlh); 815 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 816 #undef FAIL_IF 817 } 818 819 820 void 821 TALER_EXCHANGE_post_withdraw_blinded_cancel ( 822 struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh) 823 { 824 if (NULL == pwbh) 825 return; 826 if (NULL != pwbh->job) 827 { 828 GNUNET_CURL_job_cancel (pwbh->job); 829 pwbh->job = NULL; 830 } 831 GNUNET_free (pwbh->request_url); 832 TALER_EXCHANGE_keys_decref (pwbh->keys); 833 TALER_curl_easy_post_finished (&pwbh->post_ctx); 834 GNUNET_free (pwbh); 835 } 836 837 838 /* exchange_api_post-withdraw_blinded.c */