taler-exchange-httpd_reveal-melt.c (29386B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023,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 Affero 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_reveal-melt.c 18 * @brief Handle /reveal-melt requests 19 * @author Özgür Kesim 20 */ 21 #include "taler/platform.h" 22 #include <gnunet/gnunet_common.h> 23 #include <gnunet/gnunet_util_lib.h> 24 #include <jansson.h> 25 #include <microhttpd.h> 26 #include "taler-exchange-httpd_metrics.h" 27 #include "taler/taler_error_codes.h" 28 #include "taler/taler_exchangedb_plugin.h" 29 #include "taler/taler_mhd_lib.h" 30 #include "taler-exchange-httpd_mhd.h" 31 #include "taler-exchange-httpd_reveal-melt.h" 32 #include "taler-exchange-httpd_responses.h" 33 #include "taler-exchange-httpd_keys.h" 34 35 #define KAPPA_MINUS_1 (TALER_CNC_KAPPA - 1) 36 37 38 /** 39 * State for an /reveal-melt operation. 40 */ 41 struct MeltRevealContext 42 { 43 44 /** 45 * Commitment for the melt operation, previously called by the 46 * client. 47 */ 48 struct TALER_RefreshCommitmentP rc; 49 50 /** 51 * The data from the original melt. Will be retrieved from 52 * the DB via @a rc. 53 */ 54 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh; 55 56 /** 57 * True, if @e signatures were not provided in the request. 58 */ 59 bool no_signatures; 60 61 /** 62 * @since v27 63 * @deprecated after vDOLDPLUS 64 * 65 * TALER_CNC_KAPPA-1 disclosed signatures for public refresh nonces. 66 */ 67 struct TALER_PrivateRefreshNonceSignatureP signatures[KAPPA_MINUS_1]; 68 69 /** 70 * True, if @e transfer_secret_seeds were not provided in the request. 71 */ 72 bool no_transfer_secret_seeds; 73 74 /** 75 * @since vDOLDPLUS 76 * 77 * The transfer secret seeds for the revealed batches of coins. 78 */ 79 struct TALER_PrivateRefreshBatchSeedP transfer_secret_seeds[KAPPA_MINUS_1]; 80 81 /** 82 * True, if no @e age_commitment was provided 83 */ 84 bool no_age_commitment; 85 86 /** 87 * If @e no_age_commitment is false, the age commitment of 88 * the old coin. Needed to ensure that the age commitment 89 * is applied correctly to the fresh coins. 90 */ 91 struct TALER_AgeCommitment age_commitment; 92 }; 93 94 95 /** 96 * Check if the request belongs to an existing refresh request. 97 * If so, sets the refresh object with the request data. 98 * Otherwise, it queues an appropriate MHD response. 99 * 100 * @param connection The HTTP connection to the client 101 * @param rc Original commitment value sent with the melt request 102 * @param[out] refresh Data from the original refresh request 103 * @param[out] result In the error cases, a response will be queued with MHD and this will be the result. 104 * @return #GNUNET_OK if the refresh request has been found, 105 * #GNUNET_SYSERR if we did not find the request in the DB 106 */ 107 static enum GNUNET_GenericReturnValue 108 find_original_refresh ( 109 struct MHD_Connection *connection, 110 const struct TALER_RefreshCommitmentP *rc, 111 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh, 112 MHD_RESULT *result) 113 { 114 enum GNUNET_DB_QueryStatus qs; 115 116 for (unsigned int retry = 0; retry < 3; retry++) 117 { 118 qs = TEH_plugin->get_refresh (TEH_plugin->cls, 119 rc, 120 refresh); 121 switch (qs) 122 { 123 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 124 return GNUNET_OK; /* Only happy case */ 125 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 126 *result = TALER_MHD_reply_with_error (connection, 127 MHD_HTTP_NOT_FOUND, 128 TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN, 129 NULL); 130 return GNUNET_SYSERR; 131 case GNUNET_DB_STATUS_HARD_ERROR: 132 *result = TALER_MHD_reply_with_ec (connection, 133 TALER_EC_GENERIC_DB_FETCH_FAILED, 134 "get_refresh"); 135 return GNUNET_SYSERR; 136 case GNUNET_DB_STATUS_SOFT_ERROR: 137 break; /* try again */ 138 default: 139 GNUNET_break (0); 140 *result = TALER_MHD_reply_with_ec (connection, 141 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 142 NULL); 143 return GNUNET_SYSERR; 144 } 145 } 146 /* after unsuccessful retries*/ 147 *result = TALER_MHD_reply_with_ec (connection, 148 TALER_EC_GENERIC_DB_FETCH_FAILED, 149 "get_refresh"); 150 return GNUNET_SYSERR; 151 } 152 153 154 /** 155 * Verify that the age commitment is sound, that is, if the 156 * previous /melt provided a hash, ensure we have the corresponding 157 * age commitment. Or not, if it wasn't provided. 158 * 159 * @param connection The MHD connection to handle 160 * @param actx The context of the operation, only partially built at this time 161 * @param[out] mhd_ret The result if a reply is queued for MHD 162 * @return #GNUNET_OK on success, otherwise a reply is queued for MHD and @a mhd_ret is set 163 */ 164 static enum GNUNET_GenericReturnValue 165 compare_age_commitment ( 166 struct MHD_Connection *connection, 167 struct MeltRevealContext *actx, 168 MHD_RESULT *mhd_ret) 169 { 170 if (actx->no_age_commitment != 171 actx->refresh.coin.no_age_commitment) 172 { 173 *mhd_ret = TALER_MHD_reply_with_ec (connection, 174 TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, 175 NULL); 176 return GNUNET_SYSERR; 177 } 178 if (! actx->no_age_commitment) 179 { 180 struct TALER_AgeCommitmentHashP ach; 181 182 actx->age_commitment.mask = TEH_age_restriction_config.mask; 183 TALER_age_commitment_hash ( 184 &actx->age_commitment, 185 &ach); 186 if (0 != GNUNET_memcmp ( 187 &actx->refresh.coin.h_age_commitment, 188 &ach)) 189 { 190 GNUNET_break_op (0); 191 *mhd_ret = TALER_MHD_reply_with_ec (connection, 192 TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, 193 NULL); 194 return GNUNET_SYSERR; 195 } 196 } 197 return GNUNET_OK; 198 } 199 200 201 /** 202 * @brief Derives an planchet from a given input and returns 203 * blinded planchets detail 204 * 205 * @param connection Connection to the client 206 * @param denom_key The denomination key 207 * @param secret The secret to a planchet 208 * @param r_pub The public R-values from the exchange in case of a CS denomination; might be NULL 209 * @param nonce The derived nonce needed for CS denomination 210 * @param old_age_commitment The age commitment of the old coin, might be NULL 211 * @param[out] detail planchet detail to write to write 212 * @param[out] result On error, a HTTP-response will be queued and result set accordingly 213 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise, with an error message 214 * written to the client and @e result set. 215 */ 216 static enum GNUNET_GenericReturnValue 217 calculate_blinded_detail ( 218 struct MHD_Connection *connection, 219 struct TEH_DenominationKey *denom_key, 220 const struct TALER_PlanchetMasterSecretP *secret, 221 const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub, 222 union GNUNET_CRYPTO_BlindSessionNonce *nonce, 223 const struct TALER_AgeCommitment *old_age_commitment, 224 struct TALER_PlanchetDetail *detail, 225 MHD_RESULT *result) 226 { 227 enum GNUNET_GenericReturnValue ret; 228 struct TALER_AgeCommitmentHashP ach; 229 bool no_age_commitment = (NULL == old_age_commitment); 230 231 /* calculate age commitment hash */ 232 if (! no_age_commitment) 233 { 234 struct TALER_AgeCommitment nac; 235 236 TALER_age_commitment_derive_from_secret (old_age_commitment, 237 secret, 238 &nac); 239 TALER_age_commitment_hash (&nac, 240 &ach); 241 TALER_age_commitment_free (&nac); 242 } 243 244 /* Next: calculate planchet */ 245 { 246 struct TALER_CoinPubHashP c_hash; 247 struct TALER_CoinSpendPrivateKeyP coin_priv; 248 union GNUNET_CRYPTO_BlindingSecretP bks; 249 struct GNUNET_CRYPTO_BlindingInputValues bi = { 250 .cipher = denom_key->denom_pub.bsign_pub_key->cipher 251 }; 252 struct TALER_ExchangeBlindingValues blinding_values = { 253 .blinding_inputs = &bi 254 }; 255 256 switch (bi.cipher) 257 { 258 case GNUNET_CRYPTO_BSA_CS: 259 GNUNET_assert (NULL != r_pub); 260 GNUNET_assert (NULL != nonce); 261 bi.details.cs_values = *r_pub; 262 break; 263 case GNUNET_CRYPTO_BSA_RSA: 264 break; 265 default: 266 GNUNET_assert (0); 267 } 268 269 TALER_planchet_blinding_secret_create (secret, 270 &blinding_values, 271 &bks); 272 TALER_planchet_setup_coin_priv (secret, 273 &blinding_values, 274 &coin_priv); 275 ret = TALER_planchet_prepare (&denom_key->denom_pub, 276 &blinding_values, 277 &bks, 278 nonce, 279 &coin_priv, 280 no_age_commitment 281 ? NULL 282 : &ach, 283 &c_hash, 284 detail); 285 if (GNUNET_OK != ret) 286 { 287 GNUNET_break (0); 288 *result = TALER_MHD_REPLY_JSON_PACK (connection, 289 MHD_HTTP_INTERNAL_SERVER_ERROR, 290 GNUNET_JSON_pack_string ( 291 "details", 292 "failed to prepare planchet from base key")); 293 return ret; 294 } 295 } 296 return ret; 297 } 298 299 300 /** 301 * @brief Checks the validity of the disclosed signatures as follows: 302 * - Verifies the validity of the disclosed signatures with the old coin's public key 303 * - Derives the seeds for disclosed fresh coins 304 * - Derives the fresh coins from the seeds 305 * - Derives new age commitment 306 * - Calculates the blinded coin planchet hashes 307 * - Calculates the refresh commitment from above data 308 * - Compares the calculated commitment with existing one 309 * 310 * The derivation of a fresh coin from the old coin is defined in 311 * https://docs.taler.net/design-documents/062-pq-refresh.html 312 * 313 * The derivation of age-commitment from a coin's age-commitment 314 * https://docs.taler.net/design-documents/024-age-restriction.html#melt 315 * 316 * @param con HTTP-connection to the client 317 * @param rf Original refresh object from the previous /melt request 318 * @param old_age_commitment The age commitment of the original coin 319 * @param signatures The secrets of the disclosed coins, KAPPA_MINUS_1*num_coins many, maybe NULL 320 * @param rev_batch_seeds The seeds for the transfer secrets of the disclosed coins, KAPPA_MINUS_1 many, maybe NULL 321 * @param[out] result On error, a HTTP-response will be queued and result set accordingly 322 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise 323 */ 324 static enum GNUNET_GenericReturnValue 325 verify_commitment ( 326 struct MHD_Connection *con, 327 const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf, 328 const struct TALER_AgeCommitment *old_age_commitment, 329 const struct TALER_PrivateRefreshNonceSignatureP (*signatures)[KAPPA_MINUS_1], 330 const struct TALER_PrivateRefreshBatchSeedP (*rev_batch_seeds)[KAPPA_MINUS_1], 331 MHD_RESULT *result) 332 { 333 enum GNUNET_GenericReturnValue ret; 334 struct TEH_KeyStateHandle *keys; 335 struct TEH_DenominationKey *denom_keys[rf->num_coins]; 336 struct TALER_DenominationHashP *denoms_h[rf->num_coins]; 337 struct TALER_Amount total_amount; 338 struct TALER_Amount total_fee; 339 bool is_cs[rf->num_coins]; 340 size_t cs_count = 0; 341 342 GNUNET_assert (rf->noreveal_index < TALER_CNC_KAPPA); 343 GNUNET_assert (GNUNET_OK == 344 TALER_amount_set_zero (TEH_currency, 345 &total_amount)); 346 GNUNET_assert (GNUNET_OK == 347 TALER_amount_set_zero (TEH_currency, 348 &total_fee)); 349 memset (denom_keys, 350 0, 351 sizeof(denom_keys)); 352 memset (is_cs, 353 0, 354 sizeof(is_cs)); 355 356 /** 357 * Consistency check: 358 * If the refresh was for v27, signatures must not be NULL, 359 * otherwise the revealed batch seeds must not be NULL. 360 */ 361 if (rf->is_v27_refresh) 362 { 363 if (NULL == signatures) 364 { 365 *result = TALER_MHD_reply_with_error (con, 366 MHD_HTTP_BAD_REQUEST, 367 TALER_EC_GENERIC_PARAMETER_MALFORMED, 368 "signatures missing"); 369 return GNUNET_SYSERR; 370 } 371 } 372 else /* vDOLDPLUS */ 373 { 374 if (NULL == rev_batch_seeds) 375 { 376 *result = TALER_MHD_reply_with_error (con, 377 MHD_HTTP_BAD_REQUEST, 378 TALER_EC_GENERIC_PARAMETER_MALFORMED, 379 "batch_seeds missing"); 380 return GNUNET_SYSERR; 381 } 382 } 383 /** 384 * We need the current keys in memory for the meta-data of the denominations 385 */ 386 keys = TEH_keys_get_state (); 387 if (NULL == keys) 388 { 389 *result = TALER_MHD_reply_with_ec (con, 390 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 391 NULL); 392 return GNUNET_SYSERR; 393 } 394 395 /** 396 * Find the denomination keys from the original request to /melt 397 * and keep track of those of type CS. 398 */ 399 for (size_t i = 0; i < rf->num_coins; i++) 400 { 401 denom_keys[i] = 402 TEH_keys_denomination_by_serial_from_state ( 403 keys, 404 rf->denom_serials[i]); 405 if (NULL == denom_keys[i]) 406 { 407 GNUNET_break_op (0); 408 *result = TALER_MHD_reply_with_ec (con, 409 TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, 410 NULL); 411 return GNUNET_SYSERR; 412 } 413 414 /* Accumulate amount and fees */ 415 GNUNET_assert (0 <= TALER_amount_add (&total_amount, 416 &total_amount, 417 &denom_keys[i]->meta.value)); 418 GNUNET_assert (0 <= TALER_amount_add (&total_fee, 419 &total_fee, 420 &denom_keys[i]->meta.fees.refresh)); 421 422 if (GNUNET_CRYPTO_BSA_CS == 423 denom_keys[i]->denom_pub.bsign_pub_key->cipher) 424 { 425 is_cs[i] = true; 426 cs_count++; 427 } 428 429 /* Remember the hash of the public key of the denomination for later */ 430 denoms_h[i] = &denom_keys[i]->h_denom_pub; 431 } 432 433 /** 434 * Sanity check: 435 * The number CS denominations must match those from the /melt request 436 */ 437 if (cs_count != rf->num_cs_r_values) 438 { 439 GNUNET_break (0); 440 *result = TALER_MHD_reply_with_ec (con, 441 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 442 NULL); 443 return GNUNET_SYSERR; 444 } 445 /** 446 * First things first for v27 clients: Verify the signature of the old coin 447 * over the refresh nonce. This proves the ownership 448 * for the fresh coin. 449 */ 450 if (rf->is_v27_refresh) 451 { 452 size_t sig_idx = 0; 453 struct TALER_KappaPublicRefreshNoncesP kappa_nonces; 454 455 GNUNET_assert (NULL != signatures); 456 457 /** 458 * We expand the provided refresh_seed from the original call to /melt, 459 * into kappa many batch seeds, from which we will later use all except the 460 * noreveal_index one. 461 */ 462 TALER_refresh_expand_kappa_nonces_v27 ( 463 &rf->refresh_seed, 464 &kappa_nonces); 465 466 for (uint8_t k=0; k < TALER_CNC_KAPPA; k++) 467 { 468 if (rf->noreveal_index == k) 469 continue; 470 if (GNUNET_OK != 471 TALER_wallet_refresh_nonce_verify ( 472 &rf->coin.coin_pub, 473 &kappa_nonces.tuple[k], 474 rf->num_coins, 475 denoms_h, 476 k, 477 &(*signatures)[sig_idx++])) 478 { 479 GNUNET_break_op (0); 480 *result = TALER_MHD_reply_with_ec (con, 481 TALER_EC_EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID, 482 NULL); 483 return GNUNET_SYSERR; 484 } 485 } 486 } 487 /** 488 * In the following scope, we start collecting blinded coin planchet hashes, 489 * either those persisted from the original request to /melt, or we 490 * derive and calculate them from the provided signatures, after having 491 * verified that each of them was signed by the old coin's private key. 492 * 493 * After collecting the blinded coin planchet hashes, we can then get 494 * the commitment for the calculated values and compare the result with 495 * the commitment from the /melt request. 496 */ 497 { 498 struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h; 499 union GNUNET_CRYPTO_BlindSessionNonce b_nonces[GNUNET_NZL (cs_count)]; 500 size_t cs_idx = 0; /* [0...cs_count) */ 501 uint8_t sig_idx = 0; /* [0..KAPPA_MINUS_1) */ 502 /* These two are only necessary for non-v27 clients, but this is the common case. */ 503 struct TALER_TransferPublicKeyP k_tpbs[TALER_CNC_KAPPA][rf->num_coins]; 504 struct TALER_KappaTransferPublicKeys kappa_transfer_pubs = { 505 .num_transfer_pubs = rf->num_coins 506 }; 507 /** 508 * First, derive the blinding nonces for the CS denominations all at once. 509 */ 510 if (0 < cs_count) 511 { 512 uint32_t cs_indices[cs_count]; 513 size_t idx = 0; /* [0...cs_count) */ 514 515 for (size_t i = 0; i < rf->num_coins; i++) 516 if (is_cs[i]) 517 cs_indices[idx++] = i; 518 519 TALER_cs_derive_only_cs_blind_nonces_from_seed (&rf->blinding_seed, 520 true, /* for melt */ 521 cs_count, 522 cs_indices, 523 b_nonces); 524 } 525 /** 526 * We handle the kappa batches of rf->num_coins depths first. 527 */ 528 for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++) 529 { 530 if (k == rf->noreveal_index) 531 { 532 /** 533 * We take the stored value for the hash of selected batch 534 */ 535 kappa_planchets_h.tuple[k] = rf->selected_h; 536 } 537 else 538 { 539 /** 540 * We have to generate all the planchets' details from 541 * the disclosed input material and generate the 542 * hashes of them. 543 */ 544 struct TALER_PlanchetMasterSecretP planchet_secrets[rf->num_coins]; 545 struct TALER_PlanchetDetail details[rf->num_coins]; 546 547 memset (planchet_secrets, 548 0, 549 sizeof(planchet_secrets)); 550 memset (details, 551 0, 552 sizeof(details)); 553 554 GNUNET_assert (sig_idx < KAPPA_MINUS_1); 555 556 /** 557 * Expand from the k-th signature all num_coin planchet secrets, 558 * except for the noreveal_index. 559 */ 560 if (rf->is_v27_refresh) 561 { 562 TALER_refresh_signature_to_secrets_v27 ( 563 &(*signatures)[sig_idx++], 564 rf->num_coins, 565 planchet_secrets); 566 } 567 else /* vDOLDPLUS */ 568 { 569 TALER_refresh_expand_batch_seed_to_transfer_data ( 570 &(*rev_batch_seeds)[sig_idx++], 571 &rf->coin.coin_pub, 572 rf->num_coins, 573 planchet_secrets, 574 k_tpbs[k]); 575 576 kappa_transfer_pubs.batch[k] = k_tpbs[k]; 577 } 578 /** 579 * Reset the index for the CS denominations. 580 */ 581 cs_idx = 0; 582 583 for (size_t coin_idx = 0; coin_idx < rf->num_coins; coin_idx++) 584 { 585 struct GNUNET_CRYPTO_CSPublicRPairP *rp; 586 union GNUNET_CRYPTO_BlindSessionNonce *np; 587 588 if (is_cs[coin_idx]) 589 { 590 GNUNET_assert (cs_idx < cs_count); 591 np = &b_nonces[cs_idx]; 592 rp = &rf->cs_r_values[cs_idx]; 593 cs_idx++; 594 } 595 else 596 { 597 np = NULL; 598 rp = NULL; 599 } 600 ret = calculate_blinded_detail (con, 601 denom_keys[coin_idx], 602 &planchet_secrets[coin_idx], 603 rp, 604 np, 605 old_age_commitment, 606 &details[coin_idx], 607 result); 608 if (GNUNET_OK != ret) 609 return GNUNET_SYSERR; 610 } 611 /** 612 * Now we can generate the hashes for the kappa-th batch of coins 613 */ 614 TALER_wallet_blinded_planchet_details_hash ( 615 rf->num_coins, 616 details, 617 &kappa_planchets_h.tuple[k]); 618 619 for (size_t i =0; i<rf->num_coins; i++) 620 TALER_planchet_detail_free (&details[i]); 621 } 622 } 623 /** 624 * Finally, calculate the refresh commitment and compare it with the original. 625 */ 626 { 627 struct TALER_RefreshCommitmentP rc; 628 629 if (rf->is_v27_refresh) 630 { 631 TALER_refresh_get_commitment_v27 (&rc, 632 &rf->refresh_seed, 633 rf->no_blinding_seed 634 ? NULL 635 : &rf->blinding_seed, 636 &kappa_planchets_h, 637 &rf->coin.coin_pub, 638 &rf->amount_with_fee); 639 } 640 else 641 { 642 TALER_refresh_get_commitment (&rc, 643 &rf->refresh_seed, 644 rf->no_blinding_seed 645 ? NULL 646 : &rf->blinding_seed, 647 &kappa_transfer_pubs, 648 &kappa_planchets_h, 649 &rf->coin.coin_pub, 650 &rf->amount_with_fee); 651 } 652 653 if (0 != GNUNET_CRYPTO_hash_cmp ( 654 &rf->rc.session_hash, 655 &rc.session_hash)) 656 { 657 GNUNET_break_op (0); 658 *result = TALER_MHD_reply_with_ec (con, 659 TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH, 660 "rc"); 661 return GNUNET_SYSERR; 662 } 663 } 664 } 665 return GNUNET_OK; 666 } 667 668 669 /** 670 * @brief Commit the successful reveal to the database 671 * 672 * @param con HTTP-connection to the client 673 * @param rc Original refresh commitment from the previous /melt request 674 * @param[out] result On error, a HTTP-response will be queued and result set accordingly 675 * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise 676 */ 677 static enum GNUNET_GenericReturnValue 678 commit_reveal ( 679 struct MHD_Connection *con, 680 const struct TALER_RefreshCommitmentP *rc, 681 MHD_RESULT *result) 682 { 683 enum GNUNET_DB_QueryStatus qs; 684 685 for (unsigned int retry = 0; retry < 3; retry++) 686 { 687 qs = TEH_plugin->mark_refresh_reveal_success ( 688 TEH_plugin->cls, 689 rc); 690 switch (qs) 691 { 692 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 693 return GNUNET_OK; /* Only happy case */ 694 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 695 *result = TALER_MHD_reply_with_error (con, 696 MHD_HTTP_NOT_FOUND, 697 TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN, 698 NULL); 699 return GNUNET_SYSERR; 700 case GNUNET_DB_STATUS_HARD_ERROR: 701 *result = TALER_MHD_reply_with_ec (con, 702 TALER_EC_GENERIC_DB_STORE_FAILED, 703 "mark_refresh_reveal_success"); 704 return GNUNET_SYSERR; 705 case GNUNET_DB_STATUS_SOFT_ERROR: 706 break; /* try again */ 707 default: 708 GNUNET_break (0); 709 *result = TALER_MHD_reply_with_ec (con, 710 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 711 NULL); 712 return GNUNET_SYSERR; 713 } 714 } 715 /* after unsuccessful retries*/ 716 *result = TALER_MHD_reply_with_ec (con, 717 TALER_EC_GENERIC_DB_STORE_FAILED, 718 "mark_refresh_reveal_success"); 719 return GNUNET_SYSERR; 720 721 } 722 723 724 /** 725 * @brief Send a response for "/reveal-melt" 726 * 727 * @param connection The http connection to the client to send the response to 728 * @param refresh The data from the previous call to /melt with signatures 729 * @return a MHD result code 730 */ 731 static MHD_RESULT 732 reply_melt_reveal_success ( 733 struct MHD_Connection *connection, 734 const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh) 735 { 736 json_t *list = json_array (); 737 GNUNET_assert (NULL != list); 738 739 for (unsigned int i = 0; i < refresh->num_coins; i++) 740 { 741 json_t *obj = GNUNET_JSON_PACK ( 742 TALER_JSON_pack_blinded_denom_sig (NULL, 743 &refresh->denom_sigs[i])); 744 GNUNET_assert (0 == 745 json_array_append_new (list, 746 obj)); 747 } 748 749 return TALER_MHD_REPLY_JSON_PACK ( 750 connection, 751 MHD_HTTP_OK, 752 GNUNET_JSON_pack_array_steal ("ev_sigs", 753 list)); 754 } 755 756 757 MHD_RESULT 758 TEH_handler_reveal_melt ( 759 struct TEH_RequestContext *rc, 760 const json_t *root, 761 const char *const args[2]) 762 { 763 MHD_RESULT result = MHD_NO; 764 enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; 765 struct MeltRevealContext actx = {0}; 766 struct GNUNET_JSON_Specification sig_tuple[] = { 767 GNUNET_JSON_spec_fixed_auto (NULL, 768 &actx.signatures[0]), 769 GNUNET_JSON_spec_fixed_auto (NULL, 770 &actx.signatures[1]), 771 GNUNET_JSON_spec_end () 772 }; 773 struct GNUNET_JSON_Specification seeds_tuple[] = { 774 GNUNET_JSON_spec_fixed_auto (NULL, 775 &actx.transfer_secret_seeds[0]), 776 GNUNET_JSON_spec_fixed_auto (NULL, 777 &actx.transfer_secret_seeds[1]), 778 GNUNET_JSON_spec_end () 779 }; 780 struct GNUNET_JSON_Specification spec[] = { 781 GNUNET_JSON_spec_fixed_auto ("rc", 782 &actx.rc), 783 GNUNET_JSON_spec_mark_optional ( 784 TALER_JSON_spec_tuple_of ("signatures", 785 sig_tuple), 786 &actx.no_signatures), 787 GNUNET_JSON_spec_mark_optional ( 788 TALER_JSON_spec_tuple_of ("batch_seeds", 789 seeds_tuple), 790 &actx.no_transfer_secret_seeds), 791 GNUNET_JSON_spec_mark_optional ( 792 TALER_JSON_spec_age_commitment ("age_commitment", 793 &actx.age_commitment), 794 &actx.no_age_commitment), 795 GNUNET_JSON_spec_end () 796 }; 797 798 /** 799 * Note that above, we have hard-wired 800 * the size of TALER_CNC_KAPPA. 801 * Let's make sure we keep this in sync. 802 */ 803 _Static_assert (KAPPA_MINUS_1 == 2, 804 "TALER_CNC_KAPPA isn't 3!?!?"); 805 806 /* Parse JSON body*/ 807 ret = TALER_MHD_parse_json_data (rc->connection, 808 root, 809 spec); 810 if (GNUNET_OK != ret) 811 { 812 GNUNET_break_op (0); 813 return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; 814 } 815 816 (void) args; 817 818 do { 819 /* Find original commitment */ 820 if (GNUNET_OK != 821 find_original_refresh ( 822 rc->connection, 823 &actx.rc, 824 &actx.refresh, 825 &result)) 826 break; 827 828 /* Compare age commitment with the hash from the /melt request, if present */ 829 if (GNUNET_OK != 830 compare_age_commitment ( 831 rc->connection, 832 &actx, 833 &result)) 834 break; 835 836 /* verify the commitment */ 837 if (GNUNET_OK != 838 verify_commitment ( 839 rc->connection, 840 &actx.refresh, 841 actx.no_age_commitment 842 ? NULL 843 : &actx.age_commitment, 844 actx.no_signatures 845 ? NULL 846 : &actx.signatures, 847 actx.no_transfer_secret_seeds 848 ? NULL 849 : &actx.transfer_secret_seeds, 850 &result)) 851 break; 852 853 if (GNUNET_OK != 854 commit_reveal (rc->connection, 855 &actx.rc, 856 &result)) 857 break; 858 859 /* Finally, return the signatures */ 860 result = reply_melt_reveal_success (rc->connection, 861 &actx.refresh); 862 863 } while (0); 864 865 GNUNET_JSON_parse_free (spec); 866 if (NULL != actx.refresh.denom_sigs) 867 for (unsigned int i = 0; i<actx.refresh.num_coins; i++) 868 TALER_blinded_denom_sig_free (&actx.refresh.denom_sigs[i]); 869 GNUNET_free (actx.refresh.denom_sigs); 870 GNUNET_free (actx.refresh.denom_pub_hashes); 871 GNUNET_free (actx.refresh.denom_serials); 872 GNUNET_free (actx.refresh.cs_r_values); 873 return result; 874 } 875 876 877 /* end of taler-exchange-httpd_reveal_melt.c */