exchange_api_post-melt.c (19774B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025 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-melt.c 19 * @brief Implementation of the /melt request 20 * @author Özgür Kesim 21 */ 22 #include <jansson.h> 23 #include <microhttpd.h> /* just for HTTP status codes */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_json_lib.h> 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_json_lib.h" 28 #include "exchange_api_common.h" 29 #include "exchange_api_handle.h" 30 #include "taler/taler_signatures.h" 31 #include "exchange_api_curl_defaults.h" 32 #include "exchange_api_refresh_common.h" 33 34 35 /** 36 * @brief A /melt Handle 37 */ 38 struct TALER_EXCHANGE_PostMeltHandle 39 { 40 41 /** 42 * The keys of the this request handle will use 43 */ 44 struct TALER_EXCHANGE_Keys *keys; 45 46 /** 47 * The url for this request. 48 */ 49 char *url; 50 51 /** 52 * The exchange base url. 53 */ 54 char *exchange_url; 55 56 /** 57 * Curl context. 58 */ 59 struct GNUNET_CURL_Context *cctx; 60 61 /** 62 * Context for #TEH_curl_easy_post(). Keeps the data that must 63 * persist for Curl to make the upload. 64 */ 65 struct TALER_CURL_PostContext ctx; 66 67 /** 68 * Handle for the request. 69 */ 70 struct GNUNET_CURL_Job *job; 71 72 /** 73 * Function to call with refresh melt failure results. 74 */ 75 TALER_EXCHANGE_PostMeltCallback melt_cb; 76 77 /** 78 * Closure for @e result_cb and @e melt_failure_cb. 79 */ 80 void *melt_cb_cls; 81 82 /** 83 * Actual information about the melt operation. 84 */ 85 struct MeltData md; 86 87 /** 88 * The seed for the melt operation. 89 */ 90 struct TALER_PublicRefreshMasterSeedP rms; 91 92 /** 93 * Details about the characteristics of the requested melt operation. 94 */ 95 const struct TALER_EXCHANGE_MeltInput *rd; 96 97 /** 98 * True, if no blinding_seed is needed (no CS denominations involved) 99 */ 100 bool no_blinding_seed; 101 102 /** 103 * If @e no_blinding_seed is false, the blinding seed for the intermediate 104 * call to /blinding-prepare, in order to retrieve the R-values from the 105 * exchange for the blind Clause-Schnorr signature. 106 */ 107 struct TALER_BlindingMasterSeedP blinding_seed; 108 109 /** 110 * Array of `num_fresh_denom_pubs` per-coin values 111 * returned from melt operation. 112 */ 113 struct TALER_ExchangeBlindingValues *melt_blinding_values; 114 115 /** 116 * Handle for the preflight request, or NULL. 117 */ 118 struct TALER_EXCHANGE_PostBlindingPrepareHandle *bpr; 119 120 /** 121 * Public key of the coin being melted. 122 */ 123 struct TALER_CoinSpendPublicKeyP coin_pub; 124 125 /** 126 * Signature affirming the melt. 127 */ 128 struct TALER_CoinSpendSignatureP coin_sig; 129 130 /** 131 * @brief Public information about the coin's denomination key 132 */ 133 const struct TALER_EXCHANGE_DenomPublicKey *dki; 134 135 /** 136 * Gamma value chosen by the exchange during melt. 137 */ 138 uint32_t noreveal_index; 139 140 }; 141 142 143 /** 144 * Verify that the signature on the "200 OK" response 145 * from the exchange is valid. 146 * 147 * @param[in,out] mh melt handle 148 * @param json json reply with the signature 149 * @param[out] exchange_pub public key of the exchange used for the signature 150 * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not 151 */ 152 static enum GNUNET_GenericReturnValue 153 verify_melt_signature_ok (struct TALER_EXCHANGE_PostMeltHandle *mh, 154 const json_t *json, 155 struct TALER_ExchangePublicKeyP *exchange_pub) 156 { 157 struct TALER_ExchangeSignatureP exchange_sig; 158 struct GNUNET_JSON_Specification spec[] = { 159 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 160 &exchange_sig), 161 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 162 exchange_pub), 163 GNUNET_JSON_spec_uint32 ("noreveal_index", 164 &mh->noreveal_index), 165 GNUNET_JSON_spec_end () 166 }; 167 168 if (GNUNET_OK != 169 GNUNET_JSON_parse (json, 170 spec, 171 NULL, NULL)) 172 { 173 GNUNET_break_op (0); 174 return GNUNET_SYSERR; 175 } 176 /* check that exchange signing key is permitted */ 177 if (GNUNET_OK != 178 TALER_EXCHANGE_test_signing_key (mh->keys, 179 exchange_pub)) 180 { 181 GNUNET_break_op (0); 182 return GNUNET_SYSERR; 183 } 184 185 /* check that noreveal index is in permitted range */ 186 if (TALER_CNC_KAPPA <= mh->noreveal_index) 187 { 188 GNUNET_break_op (0); 189 return GNUNET_SYSERR; 190 } 191 192 if (GNUNET_OK != 193 TALER_exchange_online_melt_confirmation_verify ( 194 &mh->md.rc, 195 mh->noreveal_index, 196 exchange_pub, 197 &exchange_sig)) 198 { 199 GNUNET_break_op (0); 200 return GNUNET_SYSERR; 201 } 202 return GNUNET_OK; 203 } 204 205 206 /** 207 * Function called when we're done processing the 208 * HTTP /melt request. 209 * 210 * @param cls the `struct TALER_EXCHANGE_MeltHandle` 211 * @param response_code HTTP response code, 0 on error 212 * @param response parsed JSON result, NULL on error 213 */ 214 static void 215 handle_melt_finished (void *cls, 216 long response_code, 217 const void *response) 218 { 219 struct TALER_EXCHANGE_PostMeltHandle *mh = cls; 220 const json_t *j = response; 221 struct TALER_EXCHANGE_PostMeltResponse mr = { 222 .hr.reply = j, 223 .hr.http_status = (unsigned int) response_code 224 }; 225 226 mh->job = NULL; 227 switch (response_code) 228 { 229 case 0: 230 mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 231 break; 232 case MHD_HTTP_OK: 233 if (GNUNET_OK != 234 verify_melt_signature_ok (mh, 235 j, 236 &mr.details.ok.sign_key)) 237 { 238 GNUNET_break_op (0); 239 mr.hr.http_status = 0; 240 mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE; 241 break; 242 } 243 mr.details.ok.noreveal_index = mh->noreveal_index; 244 mr.details.ok.num_melt_blinding_values = mh->rd->num_fresh_denom_pubs; 245 mr.details.ok.melt_blinding_values = mh->melt_blinding_values; 246 mr.details.ok.blinding_seed = mh->no_blinding_seed 247 ? NULL 248 : &mh->blinding_seed; 249 mh->melt_cb (mh->melt_cb_cls, 250 &mr); 251 mh->melt_cb = NULL; 252 break; 253 case MHD_HTTP_BAD_REQUEST: 254 /* This should never happen, either us or the exchange is buggy 255 (or API version conflict); just pass JSON reply to the application */ 256 mr.hr.ec = TALER_JSON_get_error_code (j); 257 mr.hr.hint = TALER_JSON_get_error_hint (j); 258 break; 259 case MHD_HTTP_CONFLICT: 260 mr.hr.ec = TALER_JSON_get_error_code (j); 261 mr.hr.hint = TALER_JSON_get_error_hint (j); 262 break; 263 case MHD_HTTP_FORBIDDEN: 264 /* Nothing really to verify, exchange says one of the signatures is 265 invalid; assuming we checked them, this should never happen, we 266 should pass the JSON reply to the application */ 267 mr.hr.ec = TALER_JSON_get_error_code (j); 268 mr.hr.hint = TALER_JSON_get_error_hint (j); 269 break; 270 case MHD_HTTP_NOT_FOUND: 271 /* Nothing really to verify, this should never 272 happen, we should pass the JSON reply to the application */ 273 mr.hr.ec = TALER_JSON_get_error_code (j); 274 mr.hr.hint = TALER_JSON_get_error_hint (j); 275 break; 276 case MHD_HTTP_INTERNAL_SERVER_ERROR: 277 /* Server had an internal issue; we should retry, but this API 278 leaves this to the application */ 279 mr.hr.ec = TALER_JSON_get_error_code (j); 280 mr.hr.hint = TALER_JSON_get_error_hint (j); 281 break; 282 default: 283 /* unexpected response code */ 284 mr.hr.ec = TALER_JSON_get_error_code (j); 285 mr.hr.hint = TALER_JSON_get_error_hint (j); 286 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 287 "Unexpected response code %u/%d for exchange melt\n", 288 (unsigned int) response_code, 289 mr.hr.ec); 290 GNUNET_break_op (0); 291 break; 292 } 293 if (NULL != mh->melt_cb) 294 mh->melt_cb (mh->melt_cb_cls, 295 &mr); 296 TALER_EXCHANGE_post_melt_cancel (mh); 297 } 298 299 300 /** 301 * Start the actual melt operation, now that we have 302 * the exchange's input values. 303 * 304 * @param[in,out] mh melt operation to run 305 * @return #GNUNET_OK if we could start the operation 306 */ 307 static enum GNUNET_GenericReturnValue 308 start_melt (struct TALER_EXCHANGE_PostMeltHandle *mh) 309 { 310 json_t *j_request_body; 311 json_t *j_transfer_pubs; 312 json_t *j_coin_evs; 313 CURL *eh; 314 struct TALER_DenominationHashP h_denom_pub; 315 316 if (GNUNET_OK != 317 TALER_EXCHANGE_get_melt_data (&mh->rms, 318 mh->rd, 319 mh->no_blinding_seed 320 ? NULL 321 : &mh->blinding_seed, 322 mh->melt_blinding_values, 323 &mh->md)) 324 { 325 GNUNET_break (0); 326 return GNUNET_SYSERR; 327 } 328 TALER_denom_pub_hash ( 329 &mh->md.melted_coin.pub_key, 330 &h_denom_pub); 331 TALER_wallet_melt_sign ( 332 &mh->md.melted_coin.melt_amount_with_fee, 333 &mh->md.melted_coin.fee_melt, 334 &mh->md.rc, 335 &h_denom_pub, 336 mh->md.melted_coin.h_age_commitment, 337 &mh->md.melted_coin.coin_priv, 338 &mh->coin_sig); 339 GNUNET_CRYPTO_eddsa_key_get_public ( 340 &mh->md.melted_coin.coin_priv.eddsa_priv, 341 &mh->coin_pub.eddsa_pub); 342 mh->dki = TALER_EXCHANGE_get_denomination_key ( 343 mh->keys, 344 &mh->md.melted_coin.pub_key); 345 j_request_body = GNUNET_JSON_PACK ( 346 GNUNET_JSON_pack_data_auto ("old_coin_pub", 347 &mh->coin_pub), 348 GNUNET_JSON_pack_data_auto ("old_denom_pub_h", 349 &h_denom_pub), 350 TALER_JSON_pack_denom_sig ("old_denom_sig", 351 &mh->md.melted_coin.sig), 352 GNUNET_JSON_pack_data_auto ("confirm_sig", 353 &mh->coin_sig), 354 TALER_JSON_pack_amount ("value_with_fee", 355 &mh->md.melted_coin.melt_amount_with_fee), 356 GNUNET_JSON_pack_allow_null ( 357 (NULL != mh->md.melted_coin.h_age_commitment) 358 ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h", 359 mh->md.melted_coin.h_age_commitment) 360 : GNUNET_JSON_pack_string ("old_age_commitment_h", 361 NULL)), 362 GNUNET_JSON_pack_data_auto ("refresh_seed", 363 &mh->md.refresh_seed), 364 GNUNET_JSON_pack_allow_null ( 365 (mh->md.no_blinding_seed) 366 ? GNUNET_JSON_pack_string ("blinding_seed", 367 NULL) 368 : GNUNET_JSON_pack_data_auto ("blinding_seed", 369 &mh->md.blinding_seed)), 370 TALER_JSON_pack_array_of_data_auto ("denoms_h", 371 mh->md.num_fresh_coins, 372 mh->md.denoms_h) 373 ); 374 GNUNET_assert (NULL != j_request_body); 375 GNUNET_assert (NULL != 376 (j_transfer_pubs = json_array ())); 377 GNUNET_assert (NULL != 378 (j_coin_evs = json_array ())); 379 /** 380 * Fill the kappa array of coin envelopes and 381 * the array of transfer pubs. 382 */ 383 for (uint8_t k=0; k<TALER_CNC_KAPPA; k++) 384 { 385 json_t *j_envs; 386 json_t *j_tbs = GNUNET_JSON_PACK ( 387 TALER_JSON_pack_array_of_data_auto (NULL, 388 mh->md.num_fresh_coins, 389 mh->md.kappa_transfer_pubs[k]) 390 ); 391 392 GNUNET_assert (NULL != (j_envs = json_array ())); 393 GNUNET_assert (NULL !=j_tbs); 394 395 for (size_t i = 0; i < mh->md.num_fresh_coins; i++) 396 { 397 json_t *j_coin = GNUNET_JSON_PACK ( 398 TALER_JSON_pack_blinded_planchet (NULL, 399 &mh->md.kappa_blinded_planchets[k][i]) 400 ); 401 GNUNET_assert (NULL != j_coin); 402 GNUNET_assert (0 == 403 json_array_append_new (j_envs, j_coin)); 404 } 405 GNUNET_assert (0 == 406 json_array_append_new (j_coin_evs, j_envs)); 407 GNUNET_assert (0 == 408 json_array_append_new (j_transfer_pubs, j_tbs)); 409 } 410 GNUNET_assert (0 == 411 json_object_set_new (j_request_body, 412 "coin_evs", 413 j_coin_evs)); 414 GNUNET_assert (0 == 415 json_object_set_new (j_request_body, 416 "transfer_pubs", 417 j_transfer_pubs)); 418 /* and now we can at last begin the actual request handling */ 419 mh->url = TALER_url_join (mh->exchange_url, 420 "melt", 421 NULL); 422 if (NULL == mh->url) 423 { 424 json_decref (j_request_body); 425 return GNUNET_SYSERR; 426 } 427 eh = TALER_EXCHANGE_curl_easy_get_ (mh->url); 428 if ( (NULL == eh) || 429 (GNUNET_OK != 430 TALER_curl_easy_post (&mh->ctx, 431 eh, 432 j_request_body)) ) 433 { 434 GNUNET_break (0); 435 if (NULL != eh) 436 curl_easy_cleanup (eh); 437 json_decref (j_request_body); 438 return GNUNET_SYSERR; 439 } 440 json_decref (j_request_body); 441 mh->job = GNUNET_CURL_job_add2 (mh->cctx, 442 eh, 443 mh->ctx.headers, 444 &handle_melt_finished, 445 mh); 446 return GNUNET_OK; 447 } 448 449 450 /** 451 * The melt request @a mh failed, return an error to 452 * the application and cancel the operation. 453 * 454 * @param[in] mh melt request that failed 455 * @param ec error code to fail with 456 */ 457 static void 458 fail_mh (struct TALER_EXCHANGE_PostMeltHandle *mh, 459 enum TALER_ErrorCode ec) 460 { 461 struct TALER_EXCHANGE_PostMeltResponse mr = { 462 .hr.ec = ec 463 }; 464 465 mh->melt_cb (mh->melt_cb_cls, 466 &mr); 467 TALER_EXCHANGE_post_melt_cancel (mh); 468 } 469 470 471 /** 472 * Callbacks of this type are used to serve the result of submitting a 473 * /blinding-prepare request to a exchange. 474 * 475 * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *` 476 * @param bpr response details 477 */ 478 static void 479 blinding_prepare_cb (void *cls, 480 const struct TALER_EXCHANGE_PostBlindingPrepareResponse * 481 bpr) 482 { 483 struct TALER_EXCHANGE_PostMeltHandle *mh = cls; 484 unsigned int nks_off = 0; 485 486 mh->bpr = NULL; 487 if (MHD_HTTP_OK != bpr->hr.http_status) 488 { 489 struct TALER_EXCHANGE_PostMeltResponse mr = { 490 .hr = bpr->hr 491 }; 492 493 mr.hr.hint = "/blinding-prepare failed"; 494 mh->melt_cb (mh->melt_cb_cls, 495 &mr); 496 TALER_EXCHANGE_post_melt_cancel (mh); 497 return; 498 } 499 for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++) 500 { 501 const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = 502 &mh->rd->fresh_denom_pubs[i]; 503 struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i]; 504 505 switch (fresh_pk->key.bsign_pub_key->cipher) 506 { 507 case GNUNET_CRYPTO_BSA_INVALID: 508 GNUNET_break (0); 509 fail_mh (mh, 510 TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR); 511 return; 512 case GNUNET_CRYPTO_BSA_RSA: 513 break; 514 case GNUNET_CRYPTO_BSA_CS: 515 TALER_denom_ewv_copy (wv, 516 &bpr->details.ok.blinding_values[nks_off]); 517 nks_off++; 518 break; 519 } 520 } 521 if (GNUNET_OK != 522 start_melt (mh)) 523 { 524 GNUNET_break (0); 525 fail_mh (mh, 526 TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR); 527 return; 528 } 529 } 530 531 532 struct TALER_EXCHANGE_PostMeltHandle * 533 TALER_EXCHANGE_post_melt_create ( 534 struct GNUNET_CURL_Context *ctx, 535 const char *url, 536 struct TALER_EXCHANGE_Keys *keys, 537 const struct TALER_PublicRefreshMasterSeedP *rms, 538 const struct TALER_EXCHANGE_MeltInput *rd) 539 { 540 struct TALER_EXCHANGE_PostMeltHandle *mh; 541 542 if (0 == rd->num_fresh_denom_pubs) 543 { 544 GNUNET_break (0); 545 return NULL; 546 } 547 mh = GNUNET_new (struct TALER_EXCHANGE_PostMeltHandle); 548 mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */ 549 mh->cctx = ctx; 550 mh->exchange_url = GNUNET_strdup (url); 551 mh->rd = rd; 552 mh->rms = *rms; 553 mh->no_blinding_seed = true; 554 mh->melt_blinding_values = 555 GNUNET_new_array (rd->num_fresh_denom_pubs, 556 struct TALER_ExchangeBlindingValues); 557 for (unsigned int i = 0; i < rd->num_fresh_denom_pubs; i++) 558 { 559 const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = 560 &rd->fresh_denom_pubs[i]; 561 562 switch (fresh_pk->key.bsign_pub_key->cipher) 563 { 564 case GNUNET_CRYPTO_BSA_INVALID: 565 GNUNET_break (0); 566 GNUNET_free (mh->melt_blinding_values); 567 GNUNET_free (mh->exchange_url); 568 GNUNET_free (mh); 569 return NULL; 570 case GNUNET_CRYPTO_BSA_RSA: 571 TALER_denom_ewv_copy (&mh->melt_blinding_values[i], 572 TALER_denom_ewv_rsa_singleton ()); 573 break; 574 case GNUNET_CRYPTO_BSA_CS: 575 mh->no_blinding_seed = false; 576 break; 577 } 578 } 579 mh->keys = TALER_EXCHANGE_keys_incref (keys); 580 return mh; 581 } 582 583 584 enum TALER_ErrorCode 585 TALER_EXCHANGE_post_melt_start ( 586 struct TALER_EXCHANGE_PostMeltHandle *mh, 587 TALER_EXCHANGE_PostMeltCallback melt_cb, 588 TALER_EXCHANGE_POST_MELT_RESULT_CLOSURE *melt_cb_cls) 589 { 590 mh->melt_cb = melt_cb; 591 mh->melt_cb_cls = melt_cb_cls; 592 593 if (! mh->no_blinding_seed) 594 { 595 struct TALER_EXCHANGE_NonceKey nks[ 596 GNUNET_NZL (mh->rd->num_fresh_denom_pubs)]; 597 unsigned int nks_off = 0; 598 599 for (unsigned int i = 0; i < mh->rd->num_fresh_denom_pubs; i++) 600 { 601 const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = 602 &mh->rd->fresh_denom_pubs[i]; 603 604 if (GNUNET_CRYPTO_BSA_CS == 605 fresh_pk->key.bsign_pub_key->cipher) 606 { 607 nks[nks_off].pk = fresh_pk; 608 nks[nks_off].cnc_num = i; 609 nks_off++; 610 } 611 } 612 TALER_cs_refresh_seed_to_blinding_seed ( 613 &mh->rms, 614 &mh->md.melted_coin.coin_priv, 615 &mh->blinding_seed); 616 mh->bpr = TALER_EXCHANGE_post_blinding_prepare_for_melt_create ( 617 mh->cctx, 618 mh->exchange_url, 619 &mh->blinding_seed, 620 nks_off, 621 nks); 622 if (NULL == mh->bpr) 623 { 624 GNUNET_break (0); 625 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 626 } 627 { 628 enum TALER_ErrorCode ec; 629 630 ec = TALER_EXCHANGE_post_blinding_prepare_start (mh->bpr, 631 &blinding_prepare_cb, 632 mh); 633 if (TALER_EC_NONE != ec) 634 { 635 GNUNET_break (0); 636 TALER_EXCHANGE_post_blinding_prepare_cancel (mh->bpr); 637 mh->bpr = NULL; 638 return ec; 639 } 640 } 641 return TALER_EC_NONE; 642 } 643 if (GNUNET_OK != 644 start_melt (mh)) 645 { 646 GNUNET_break (0); 647 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 648 } 649 return TALER_EC_NONE; 650 } 651 652 653 void 654 TALER_EXCHANGE_post_melt_cancel (struct TALER_EXCHANGE_PostMeltHandle *mh) 655 { 656 for (unsigned int i = 0; i < mh->rd->num_fresh_denom_pubs; i++) 657 TALER_denom_ewv_free (&mh->melt_blinding_values[i]); 658 if (NULL != mh->job) 659 { 660 GNUNET_CURL_job_cancel (mh->job); 661 mh->job = NULL; 662 } 663 if (NULL != mh->bpr) 664 { 665 TALER_EXCHANGE_post_blinding_prepare_cancel (mh->bpr); 666 mh->bpr = NULL; 667 } 668 TALER_EXCHANGE_free_melt_data (&mh->md); /* does not free 'md' itself */ 669 GNUNET_free (mh->melt_blinding_values); 670 GNUNET_free (mh->url); 671 GNUNET_free (mh->exchange_url); 672 TALER_curl_easy_post_finished (&mh->ctx); 673 TALER_EXCHANGE_keys_decref (mh->keys); 674 GNUNET_free (mh); 675 } 676 677 678 /* end of exchange_api_melt.c */