exchange_api_post-purses-PURSE_PUB-create.c (24675B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022-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-purses-PURSE_PUB-create.c 19 * @brief Implementation of the client to create a purse with 20 * an initial set of deposits (and a contract) 21 * @author Christian Grothoff 22 */ 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 "taler/taler_json_lib.h" 29 #include "exchange_api_handle.h" 30 #include "exchange_api_common.h" 31 #include "taler/taler_signatures.h" 32 #include "exchange_api_curl_defaults.h" 33 34 35 /** 36 * Information we track per deposited coin. 37 */ 38 struct Deposit 39 { 40 /** 41 * Coin's public key. 42 */ 43 struct TALER_CoinSpendPublicKeyP coin_pub; 44 45 /** 46 * Signature made with the coin. 47 */ 48 struct TALER_CoinSpendSignatureP coin_sig; 49 50 /** 51 * Coin's denomination. 52 */ 53 struct TALER_DenominationHashP h_denom_pub; 54 55 /** 56 * Signature proving the validity of the coin. 57 */ 58 struct TALER_DenominationSignature denom_sig; 59 60 /** 61 * Age restriction hash for the coin. 62 */ 63 struct TALER_AgeCommitmentHashP ahac; 64 65 /** 66 * How much did we say the coin contributed. 67 */ 68 struct TALER_Amount contribution; 69 70 /** 71 * Age attestation for this coin. Valid if @e have_age. 72 */ 73 struct TALER_AgeAttestationP attest; 74 75 /** 76 * True if this coin uses age attestation. 77 */ 78 bool have_age; 79 80 }; 81 82 83 /** 84 * @brief A purse create with deposit handle 85 */ 86 struct TALER_EXCHANGE_PostPursesCreateHandle 87 { 88 89 /** 90 * The curl context for this request. 91 */ 92 struct GNUNET_CURL_Context *ctx; 93 94 /** 95 * The base URL of the exchange. 96 */ 97 char *base_url; 98 99 /** 100 * The keys of the exchange this request handle will use 101 */ 102 struct TALER_EXCHANGE_Keys *keys; 103 104 /** 105 * The url for this request, set during _start. 106 */ 107 char *url; 108 109 /** 110 * Context for #TEH_curl_easy_post(). Keeps the data that must 111 * persist for Curl to make the upload. 112 */ 113 struct TALER_CURL_PostContext post_ctx; 114 115 /** 116 * Handle for the request. 117 */ 118 struct GNUNET_CURL_Job *job; 119 120 /** 121 * Function to call with the result. 122 */ 123 TALER_EXCHANGE_PostPursesCreateCallback cb; 124 125 /** 126 * Closure for @a cb. 127 */ 128 TALER_EXCHANGE_POST_PURSES_CREATE_RESULT_CLOSURE *cb_cls; 129 130 /** 131 * Expected value in the purse after fees. 132 */ 133 struct TALER_Amount purse_value_after_fees; 134 135 /** 136 * Our encrypted contract (if we had any). 137 */ 138 struct TALER_EncryptedContract econtract; 139 140 /** 141 * Public key of the merge capability. 142 */ 143 struct TALER_PurseMergePublicKeyP merge_pub; 144 145 /** 146 * Private key of the purse which we are creating. 147 */ 148 struct TALER_PurseContractPrivateKeyP purse_priv; 149 150 /** 151 * Private key for the merge operation. 152 */ 153 struct TALER_PurseMergePrivateKeyP merge_priv; 154 155 /** 156 * Private key to decrypt the contract. 157 */ 158 struct TALER_ContractDiffiePrivateP contract_priv; 159 160 /** 161 * Contract terms for the payment. 162 */ 163 json_t *contract_terms; 164 165 /** 166 * Minimum age, as per @e contract_terms. 167 */ 168 uint32_t min_age; 169 170 /** 171 * Public key of the purse. 172 */ 173 struct TALER_PurseContractPublicKeyP purse_pub; 174 175 /** 176 * Signature for purse creation. 177 */ 178 struct TALER_PurseContractSignatureP purse_sig; 179 180 /** 181 * Hash over the purse's contract terms. 182 */ 183 struct TALER_PrivateContractHashP h_contract_terms; 184 185 /** 186 * When does the purse expire. 187 */ 188 struct GNUNET_TIME_Timestamp purse_expiration; 189 190 /** 191 * Array of @e num_deposit deposits. 192 */ 193 struct Deposit *deposits; 194 195 /** 196 * How many deposits did we make? 197 */ 198 unsigned int num_deposits; 199 200 struct 201 { 202 203 /** 204 * Whether we are uploading a contract. 205 */ 206 bool upload_contract; 207 208 } options; 209 210 }; 211 212 213 /** 214 * Find the deposited coin @a coin_pub and how we used it. 215 * 216 * @param cls a `struct TALER_EXCHANGE_PostPursesCreateHandle *` 217 * @param coin_pub public key of the coin named in the conflict reply 218 * @param[out] h_denom_pub set to the hash of the denomination we used 219 * @param[out] h_age_commitment set to the age commitment hash we used, or NULL 220 * @return #GNUNET_OK if found, #GNUNET_NO if the coin is not ours 221 */ 222 static enum GNUNET_GenericReturnValue 223 purse_create_coin_lookup (void *cls, 224 const struct TALER_CoinSpendPublicKeyP *coin_pub, 225 const struct TALER_DenominationHashP **h_denom_pub, 226 const struct TALER_AgeCommitmentHashP ** 227 h_age_commitment) 228 { 229 struct TALER_EXCHANGE_PostPursesCreateHandle *pch = cls; 230 231 for (unsigned int i = 0; i<pch->num_deposits; i++) 232 { 233 const struct Deposit *deposit = &pch->deposits[i]; 234 235 if (0 != GNUNET_memcmp (coin_pub, 236 &deposit->coin_pub)) 237 continue; 238 *h_denom_pub = &deposit->h_denom_pub; 239 *h_age_commitment = deposit->have_age 240 ? &deposit->ahac 241 : NULL; 242 return GNUNET_OK; 243 } 244 return GNUNET_NO; 245 } 246 247 248 /** 249 * Function called when we're done processing the 250 * HTTP /purses/$PID/create request. 251 * 252 * @param cls the `struct TALER_EXCHANGE_PostPursesCreateHandle` 253 * @param response_code HTTP response code, 0 on error 254 * @param response parsed JSON result, NULL on error 255 */ 256 static void 257 handle_purse_create_deposit_finished (void *cls, 258 long response_code, 259 const void *response) 260 { 261 struct TALER_EXCHANGE_PostPursesCreateHandle *pch = cls; 262 const json_t *j = response; 263 struct TALER_EXCHANGE_PostPursesCreateResponse dr = { 264 .hr.reply = j, 265 .hr.http_status = (unsigned int) response_code 266 }; 267 const struct TALER_EXCHANGE_Keys *keys = pch->keys; 268 269 pch->job = NULL; 270 switch (response_code) 271 { 272 case 0: 273 dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 274 break; 275 case MHD_HTTP_OK: 276 { 277 struct GNUNET_JSON_Specification spec[] = { 278 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 279 &dr.details.ok.exchange_sig), 280 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 281 &dr.details.ok.exchange_pub), 282 GNUNET_JSON_spec_timestamp ("exchange_timestamp", 283 &dr.details.ok.exchange_timestamp), 284 TALER_JSON_spec_amount ("total_deposited", 285 pch->purse_value_after_fees.currency, 286 &dr.details.ok.total_deposited), 287 GNUNET_JSON_spec_end () 288 }; 289 290 if (GNUNET_OK != 291 GNUNET_JSON_parse (j, 292 spec, 293 NULL, NULL)) 294 { 295 GNUNET_break_op (0); 296 dr.hr.http_status = 0; 297 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 298 break; 299 } 300 if (GNUNET_OK != 301 TALER_EXCHANGE_test_signing_key (keys, 302 &dr.details.ok.exchange_pub)) 303 { 304 GNUNET_break_op (0); 305 dr.hr.http_status = 0; 306 dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID; 307 break; 308 } 309 if (GNUNET_OK != 310 TALER_exchange_online_purse_created_verify ( 311 dr.details.ok.exchange_timestamp, 312 pch->purse_expiration, 313 &pch->purse_value_after_fees, 314 &dr.details.ok.total_deposited, 315 &pch->purse_pub, 316 &pch->h_contract_terms, 317 &dr.details.ok.exchange_pub, 318 &dr.details.ok.exchange_sig)) 319 { 320 GNUNET_break_op (0); 321 dr.hr.http_status = 0; 322 dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID; 323 break; 324 } 325 } 326 break; 327 case MHD_HTTP_BAD_REQUEST: 328 /* This should never happen, either us or the exchange is buggy 329 (or API version conflict); just pass JSON reply to the application */ 330 dr.hr.ec = TALER_JSON_get_error_code (j); 331 dr.hr.hint = TALER_JSON_get_error_hint (j); 332 break; 333 case MHD_HTTP_FORBIDDEN: 334 dr.hr.ec = TALER_JSON_get_error_code (j); 335 dr.hr.hint = TALER_JSON_get_error_hint (j); 336 /* Nothing really to verify, exchange says one of the signatures is 337 invalid; as we checked them, this should never happen, we 338 should pass the JSON reply to the application */ 339 break; 340 case MHD_HTTP_NOT_FOUND: 341 dr.hr.ec = TALER_JSON_get_error_code (j); 342 dr.hr.hint = TALER_JSON_get_error_hint (j); 343 /* Nothing really to verify, this should never 344 happen, we should pass the JSON reply to the application */ 345 break; 346 case MHD_HTTP_CONFLICT: 347 { 348 dr.hr.ec = TALER_JSON_get_error_code (j); 349 switch (dr.hr.ec) 350 { 351 case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA: 352 if (GNUNET_OK != 353 TALER_EXCHANGE_check_purse_create_conflict_ ( 354 &pch->purse_sig, 355 &pch->purse_pub, 356 j)) 357 { 358 GNUNET_break_op (0); 359 dr.hr.http_status = 0; 360 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 361 break; 362 } 363 break; 364 case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: 365 /* Nothing to check anymore here, proof needs to be 366 checked in the GET /coins/$COIN_PUB handler */ 367 break; 368 case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: 369 case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH: 370 if (GNUNET_OK != 371 TALER_EXCHANGE_check_coin_conflict_ (pch->keys, 372 dr.hr.ec, 373 j, 374 &purse_create_coin_lookup, 375 pch, 376 &dr.details.conflict)) 377 { 378 GNUNET_break_op (0); 379 dr.hr.http_status = 0; 380 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 381 } 382 break; 383 case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA: 384 { 385 struct TALER_CoinSpendPublicKeyP coin_pub; 386 struct TALER_CoinSpendSignatureP coin_sig; 387 struct TALER_DenominationHashP h_denom_pub; 388 struct TALER_AgeCommitmentHashP phac; 389 bool found = false; 390 391 if (GNUNET_OK != 392 TALER_EXCHANGE_check_purse_coin_conflict_ ( 393 &pch->purse_pub, 394 pch->base_url, 395 j, 396 &h_denom_pub, 397 &phac, 398 &coin_pub, 399 &coin_sig)) 400 { 401 GNUNET_break_op (0); 402 dr.hr.http_status = 0; 403 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 404 break; 405 } 406 for (unsigned int i = 0; i<pch->num_deposits; i++) 407 { 408 struct Deposit *deposit = &pch->deposits[i]; 409 410 if (0 != 411 GNUNET_memcmp (&coin_pub, 412 &deposit->coin_pub)) 413 continue; 414 if (0 != 415 GNUNET_memcmp (&deposit->h_denom_pub, 416 &h_denom_pub)) 417 { 418 found = true; 419 break; 420 } 421 if (0 != 422 GNUNET_memcmp (&deposit->ahac, 423 &phac)) 424 { 425 found = true; 426 break; 427 } 428 if (0 == 429 GNUNET_memcmp (&coin_sig, 430 &deposit->coin_sig)) 431 { 432 GNUNET_break_op (0); 433 continue; 434 } 435 found = true; 436 break; 437 } 438 if (! found) 439 { 440 /* conflict is for a different coin! */ 441 GNUNET_break_op (0); 442 dr.hr.http_status = 0; 443 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 444 break; 445 } 446 } 447 break; 448 case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA: 449 if (GNUNET_OK != 450 TALER_EXCHANGE_check_purse_econtract_conflict_ ( 451 &pch->econtract.econtract_sig, 452 &pch->purse_pub, 453 j)) 454 { 455 GNUNET_break_op (0); 456 dr.hr.http_status = 0; 457 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 458 break; 459 } 460 break; 461 default: 462 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 463 "Unexpected error code %d for conflicting deposit\n", 464 dr.hr.ec); 465 GNUNET_break_op (0); 466 dr.hr.http_status = 0; 467 dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 468 } /* switch on (ec) */ 469 } 470 break; 471 case MHD_HTTP_GONE: 472 /* could happen if denomination was revoked */ 473 /* Note: one might want to check /keys for revocation 474 signature here, alas tricky in case our /keys 475 is outdated => left to clients */ 476 dr.hr.ec = TALER_JSON_get_error_code (j); 477 dr.hr.hint = TALER_JSON_get_error_hint (j); 478 break; 479 case MHD_HTTP_INTERNAL_SERVER_ERROR: 480 dr.hr.ec = TALER_JSON_get_error_code (j); 481 dr.hr.hint = TALER_JSON_get_error_hint (j); 482 /* Server had an internal issue; we should retry, but this API 483 leaves this to the application */ 484 break; 485 default: 486 /* unexpected response code */ 487 dr.hr.ec = TALER_JSON_get_error_code (j); 488 dr.hr.hint = TALER_JSON_get_error_hint (j); 489 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 490 "Unexpected response code %u/%d for exchange deposit\n", 491 (unsigned int) response_code, 492 dr.hr.ec); 493 GNUNET_break_op (0); 494 break; 495 } 496 if (NULL != pch->cb) 497 { 498 pch->cb (pch->cb_cls, 499 &dr); 500 pch->cb = NULL; 501 } 502 TALER_EXCHANGE_free_coin_conflict_ (&dr.hr, 503 &dr.details.conflict); 504 TALER_EXCHANGE_post_purses_create_cancel (pch); 505 } 506 507 508 struct TALER_EXCHANGE_PostPursesCreateHandle * 509 TALER_EXCHANGE_post_purses_create_create ( 510 struct GNUNET_CURL_Context *ctx, 511 const char *url, 512 struct TALER_EXCHANGE_Keys *keys, 513 const struct TALER_PurseContractPrivateKeyP *purse_priv, 514 const struct TALER_PurseMergePrivateKeyP *merge_priv, 515 const struct TALER_ContractDiffiePrivateP *contract_priv, 516 const json_t *contract_terms, 517 unsigned int num_deposits, 518 const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits]) 519 { 520 struct TALER_EXCHANGE_PostPursesCreateHandle *pch; 521 522 pch = GNUNET_new (struct TALER_EXCHANGE_PostPursesCreateHandle); 523 pch->ctx = ctx; 524 pch->base_url = GNUNET_strdup (url); 525 pch->purse_priv = *purse_priv; 526 pch->merge_priv = *merge_priv; 527 pch->contract_priv = *contract_priv; 528 pch->contract_terms = json_incref ((json_t *) contract_terms); 529 { 530 struct GNUNET_JSON_Specification spec[] = { 531 GNUNET_JSON_spec_timestamp ("pay_deadline", 532 &pch->purse_expiration), 533 TALER_JSON_spec_amount_any ("amount", 534 &pch->purse_value_after_fees), 535 GNUNET_JSON_spec_mark_optional ( 536 GNUNET_JSON_spec_uint32 ("minimum_age", 537 &pch->min_age), 538 NULL), 539 GNUNET_JSON_spec_end () 540 }; 541 542 if (GNUNET_OK != 543 GNUNET_JSON_parse (contract_terms, 544 spec, 545 NULL, NULL)) 546 { 547 GNUNET_break (0); 548 GNUNET_free (pch->base_url); 549 GNUNET_free (pch); 550 return NULL; 551 } 552 } 553 if (GNUNET_OK != 554 TALER_JSON_contract_hash (contract_terms, 555 &pch->h_contract_terms)) 556 { 557 GNUNET_break (0); 558 GNUNET_free (pch->base_url); 559 GNUNET_free (pch); 560 return NULL; 561 } 562 GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, 563 &pch->purse_pub.eddsa_pub); 564 GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv, 565 &pch->merge_pub.eddsa_pub); 566 pch->num_deposits = num_deposits; 567 pch->deposits = GNUNET_new_array (num_deposits, 568 struct Deposit); 569 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 570 "Signing with URL `%s'\n", 571 url); 572 for (unsigned int i = 0; i<num_deposits; i++) 573 { 574 const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i]; 575 const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof; 576 struct Deposit *d = &pch->deposits[i]; 577 578 if (NULL != acp) 579 { 580 TALER_age_commitment_hash (&acp->commitment, 581 &d->ahac); 582 d->have_age = true; 583 if (GNUNET_OK != 584 TALER_age_commitment_attest (acp, 585 pch->min_age, 586 &d->attest)) 587 { 588 GNUNET_break (0); 589 GNUNET_array_grow (pch->deposits, 590 pch->num_deposits, 591 0); 592 GNUNET_free (pch->base_url); 593 GNUNET_free (pch); 594 return NULL; 595 } 596 } 597 d->contribution = deposit->amount; 598 d->h_denom_pub = deposit->h_denom_pub; 599 TALER_denom_sig_copy (&d->denom_sig, 600 &deposit->denom_sig); 601 GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv, 602 &d->coin_pub.eddsa_pub); 603 TALER_wallet_purse_deposit_sign ( 604 url, 605 &pch->purse_pub, 606 &deposit->amount, 607 &d->h_denom_pub, 608 &d->ahac, 609 &deposit->coin_priv, 610 &d->coin_sig); 611 } 612 pch->keys = TALER_EXCHANGE_keys_incref (keys); 613 return pch; 614 } 615 616 617 enum GNUNET_GenericReturnValue 618 TALER_EXCHANGE_post_purses_create_set_options_ ( 619 struct TALER_EXCHANGE_PostPursesCreateHandle *ppch, 620 unsigned int num_options, 621 const struct TALER_EXCHANGE_PostPursesCreateOptionValue options[]) 622 { 623 for (unsigned int i = 0; i < num_options; i++) 624 { 625 const struct TALER_EXCHANGE_PostPursesCreateOptionValue *opt = &options[i]; 626 627 switch (opt->option) 628 { 629 case TALER_EXCHANGE_POST_PURSES_CREATE_OPTION_END: 630 return GNUNET_OK; 631 case TALER_EXCHANGE_POST_PURSES_CREATE_OPTION_UPLOAD_CONTRACT: 632 ppch->options.upload_contract = true; 633 break; 634 } 635 } 636 return GNUNET_OK; 637 } 638 639 640 enum TALER_ErrorCode 641 TALER_EXCHANGE_post_purses_create_start ( 642 struct TALER_EXCHANGE_PostPursesCreateHandle *pch, 643 TALER_EXCHANGE_PostPursesCreateCallback cb, 644 TALER_EXCHANGE_POST_PURSES_CREATE_RESULT_CLOSURE *cb_cls) 645 { 646 char arg_str[sizeof (pch->purse_pub) * 2 + 32]; 647 CURL *eh; 648 json_t *deposit_arr; 649 json_t *body; 650 651 pch->cb = cb; 652 pch->cb_cls = cb_cls; 653 deposit_arr = json_array (); 654 GNUNET_assert (NULL != deposit_arr); 655 656 for (unsigned int i = 0; i<pch->num_deposits; i++) 657 { 658 struct Deposit *d = &pch->deposits[i]; 659 struct TALER_AgeCommitmentHashP *aghp = NULL; 660 struct TALER_AgeAttestationP *attestp = NULL; 661 json_t *jdeposit; 662 663 if (d->have_age) 664 { 665 aghp = &d->ahac; 666 attestp = &d->attest; 667 } 668 jdeposit = GNUNET_JSON_PACK ( 669 GNUNET_JSON_pack_allow_null ( 670 GNUNET_JSON_pack_data_auto ("h_age_commitment", 671 aghp)), 672 GNUNET_JSON_pack_allow_null ( 673 GNUNET_JSON_pack_data_auto ("age_attestation", 674 attestp)), 675 TALER_JSON_pack_amount ("amount", 676 &d->contribution), 677 GNUNET_JSON_pack_data_auto ("denom_pub_hash", 678 &d->h_denom_pub), 679 TALER_JSON_pack_denom_sig ("ub_sig", 680 &d->denom_sig), 681 GNUNET_JSON_pack_data_auto ("coin_sig", 682 &d->coin_sig), 683 GNUNET_JSON_pack_data_auto ("coin_pub", 684 &d->coin_pub)); 685 GNUNET_assert (0 == 686 json_array_append_new (deposit_arr, 687 jdeposit)); 688 689 } 690 691 if (pch->options.upload_contract) 692 { 693 TALER_CRYPTO_contract_encrypt_for_merge ( 694 &pch->purse_pub, 695 &pch->contract_priv, 696 &pch->merge_priv, 697 pch->contract_terms, 698 &pch->econtract.econtract, 699 &pch->econtract.econtract_size); 700 GNUNET_CRYPTO_ecdhe_key_get_public ( 701 &pch->contract_priv.ecdhe_priv, 702 &pch->econtract.contract_pub.ecdhe_pub); 703 TALER_wallet_econtract_upload_sign ( 704 pch->econtract.econtract, 705 pch->econtract.econtract_size, 706 &pch->econtract.contract_pub, 707 &pch->purse_priv, 708 &pch->econtract.econtract_sig); 709 } 710 TALER_wallet_purse_create_sign (pch->purse_expiration, 711 &pch->h_contract_terms, 712 &pch->merge_pub, 713 pch->min_age, 714 &pch->purse_value_after_fees, 715 &pch->purse_priv, 716 &pch->purse_sig); 717 718 body = GNUNET_JSON_PACK ( 719 TALER_JSON_pack_amount ("amount", 720 &pch->purse_value_after_fees), 721 GNUNET_JSON_pack_uint64 ("min_age", 722 pch->min_age), 723 GNUNET_JSON_pack_allow_null ( 724 TALER_JSON_pack_econtract ("econtract", 725 pch->options.upload_contract 726 ? &pch->econtract 727 : NULL)), 728 GNUNET_JSON_pack_data_auto ("purse_sig", 729 &pch->purse_sig), 730 GNUNET_JSON_pack_data_auto ("merge_pub", 731 &pch->merge_pub), 732 GNUNET_JSON_pack_data_auto ("h_contract_terms", 733 &pch->h_contract_terms), 734 GNUNET_JSON_pack_timestamp ("purse_expiration", 735 pch->purse_expiration), 736 GNUNET_JSON_pack_array_steal ("deposits", 737 deposit_arr)); 738 if (NULL == body) 739 { 740 GNUNET_break (0); 741 return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE; 742 } 743 744 { 745 char pub_str[sizeof (pch->purse_pub) * 2]; 746 char *end; 747 748 end = GNUNET_STRINGS_data_to_string ( 749 &pch->purse_pub, 750 sizeof (pch->purse_pub), 751 pub_str, 752 sizeof (pub_str)); 753 *end = '\0'; 754 GNUNET_snprintf (arg_str, 755 sizeof (arg_str), 756 "purses/%s/create", 757 pub_str); 758 } 759 pch->url = TALER_url_join (pch->base_url, 760 arg_str, 761 NULL); 762 if (NULL == pch->url) 763 { 764 GNUNET_break (0); 765 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 766 } 767 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 768 "URL for purse create with deposit: `%s'\n", 769 pch->url); 770 eh = TALER_EXCHANGE_curl_easy_get_ (pch->url); 771 if ( (NULL == eh) || 772 (GNUNET_OK != 773 TALER_curl_easy_post (&pch->post_ctx, 774 eh, 775 body)) ) 776 { 777 GNUNET_break (0); 778 if (NULL != eh) 779 curl_easy_cleanup (eh); 780 json_decref (body); 781 return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE; 782 } 783 json_decref (body); 784 pch->job = GNUNET_CURL_job_add2 (pch->ctx, 785 eh, 786 pch->post_ctx.headers, 787 &handle_purse_create_deposit_finished, 788 pch); 789 if (NULL == pch->job) 790 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 791 return TALER_EC_NONE; 792 } 793 794 795 void 796 TALER_EXCHANGE_post_purses_create_cancel ( 797 struct TALER_EXCHANGE_PostPursesCreateHandle *pch) 798 { 799 if (NULL != pch->job) 800 { 801 GNUNET_CURL_job_cancel (pch->job); 802 pch->job = NULL; 803 } 804 json_decref (pch->contract_terms); 805 GNUNET_free (pch->econtract.econtract); 806 GNUNET_free (pch->base_url); 807 GNUNET_free (pch->url); 808 809 for (unsigned int i = 0; i<pch->num_deposits; i++) 810 { 811 struct Deposit *d = &pch->deposits[i]; 812 813 TALER_denom_sig_free (&d->denom_sig); 814 } 815 GNUNET_array_grow (pch->deposits, 816 pch->num_deposits, 817 0); 818 TALER_EXCHANGE_keys_decref (pch->keys); 819 TALER_curl_easy_post_finished (&pch->post_ctx); 820 GNUNET_free (pch); 821 } 822 823 824 /* end of exchange_api_post-purses-PURSE_PUB-create.c */