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