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