exchange_api_post-reveal-melt.c (13263B)
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-reveal-melt.c 19 * @brief Implementation of the /reveal-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 * Handler for a running reveal-melt request 39 */ 40 struct TALER_EXCHANGE_PostRevealMeltHandle 41 { 42 /** 43 * The url for the request 44 */ 45 char *request_url; 46 47 /** 48 * The exchange base URL. 49 */ 50 char *exchange_url; 51 52 /** 53 * CURL handle for the request job. 54 */ 55 struct GNUNET_CURL_Job *job; 56 57 /** 58 * Post Context 59 */ 60 struct TALER_CURL_PostContext post_ctx; 61 62 /** 63 * Number of coins to expect 64 */ 65 size_t num_expected_coins; 66 67 /** 68 * The input provided 69 */ 70 const struct TALER_EXCHANGE_RevealMeltInput *reveal_input; 71 72 /** 73 * The melt data 74 */ 75 struct MeltData md; 76 77 /** 78 * The curl context 79 */ 80 struct GNUNET_CURL_Context *curl_ctx; 81 82 /** 83 * Callback to pass the result onto 84 */ 85 TALER_EXCHANGE_PostRevealMeltCallback callback; 86 87 /** 88 * Closure for @e callback 89 */ 90 void *callback_cls; 91 92 }; 93 94 /** 95 * We got a 200 OK response for the /reveal-melt operation. 96 * Extract the signed blinded coins and return it to the caller. 97 * 98 * @param mrh operation handle 99 * @param j_response reply from the exchange 100 * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors 101 */ 102 static enum GNUNET_GenericReturnValue 103 reveal_melt_ok ( 104 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh, 105 const json_t *j_response) 106 { 107 struct TALER_EXCHANGE_PostRevealMeltResponse response = { 108 .hr.reply = j_response, 109 .hr.http_status = MHD_HTTP_OK, 110 }; 111 struct TALER_BlindedDenominationSignature blind_sigs[mrh->num_expected_coins]; 112 struct GNUNET_JSON_Specification spec[] = { 113 TALER_JSON_spec_array_of_blinded_denom_sigs ("ev_sigs", 114 mrh->num_expected_coins, 115 blind_sigs), 116 GNUNET_JSON_spec_end () 117 }; 118 if (GNUNET_OK != 119 GNUNET_JSON_parse (j_response, 120 spec, 121 NULL, NULL)) 122 { 123 GNUNET_break_op (0); 124 return GNUNET_SYSERR; 125 } 126 127 { 128 struct TALER_EXCHANGE_RevealedCoinInfo coins[mrh->num_expected_coins]; 129 130 /* Reconstruct the coins and unblind the signatures */ 131 for (unsigned int i = 0; i<mrh->num_expected_coins; i++) 132 { 133 struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i]; 134 const struct FreshCoinData *fcd = &mrh->md.fcds[i]; 135 const struct TALER_DenominationPublicKey *pk; 136 struct TALER_CoinSpendPublicKeyP coin_pub; 137 struct TALER_CoinPubHashP coin_hash; 138 struct TALER_FreshCoin coin; 139 union GNUNET_CRYPTO_BlindingSecretP bks; 140 const struct TALER_AgeCommitmentHashP *pah = NULL; 141 142 rci->ps = fcd->ps[mrh->reveal_input->noreveal_index]; 143 rci->bks = fcd->bks[mrh->reveal_input->noreveal_index]; 144 rci->age_commitment_proof = NULL; 145 pk = &fcd->fresh_pk; 146 if (NULL != mrh->md.melted_coin.age_commitment_proof) 147 { 148 rci->age_commitment_proof 149 = fcd->age_commitment_proofs[mrh->reveal_input->noreveal_index]; 150 TALER_age_commitment_hash ( 151 &rci->age_commitment_proof->commitment, 152 &rci->h_age_commitment); 153 pah = &rci->h_age_commitment; 154 } 155 156 TALER_planchet_setup_coin_priv (&rci->ps, 157 &mrh->reveal_input->blinding_values[i], 158 &rci->coin_priv); 159 TALER_planchet_blinding_secret_create (&rci->ps, 160 &mrh->reveal_input->blinding_values 161 [i], 162 &bks); 163 /* needed to verify the signature, and we didn't store it earlier, 164 hence recomputing it here... */ 165 GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv, 166 &coin_pub.eddsa_pub); 167 TALER_coin_pub_hash (&coin_pub, 168 pah, 169 &coin_hash); 170 if (GNUNET_OK != 171 TALER_planchet_to_coin (pk, 172 &blind_sigs[i], 173 &bks, 174 &rci->coin_priv, 175 pah, 176 &coin_hash, 177 &mrh->reveal_input->blinding_values[i], 178 &coin)) 179 { 180 GNUNET_break_op (0); 181 GNUNET_JSON_parse_free (spec); 182 return GNUNET_SYSERR; 183 } 184 GNUNET_JSON_parse_free (spec); 185 rci->sig = coin.sig; 186 } 187 188 response.details.ok.num_coins = mrh->num_expected_coins; 189 response.details.ok.coins = coins; 190 mrh->callback (mrh->callback_cls, 191 &response); 192 /* Make sure the callback isn't called again */ 193 mrh->callback = NULL; 194 /* Free resources */ 195 for (size_t i = 0; i < mrh->num_expected_coins; i++) 196 { 197 struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i]; 198 199 TALER_denom_sig_free (&rci->sig); 200 TALER_blinded_denom_sig_free (&blind_sigs[i]); 201 } 202 } 203 204 return GNUNET_OK; 205 } 206 207 208 /** 209 * Function called when we're done processing the 210 * HTTP /reveal-melt request. 211 * 212 * @param cls the `struct TALER_EXCHANGE_RevealMeltHandle` 213 * @param response_code The HTTP response code 214 * @param response response data 215 */ 216 static void 217 handle_reveal_melt_finished ( 218 void *cls, 219 long response_code, 220 const void *response) 221 { 222 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh = cls; 223 const json_t *j_response = response; 224 struct TALER_EXCHANGE_PostRevealMeltResponse awr = { 225 .hr.reply = j_response, 226 .hr.http_status = (unsigned int) response_code 227 }; 228 229 mrh->job = NULL; 230 switch (response_code) 231 { 232 case 0: 233 awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 234 break; 235 case MHD_HTTP_OK: 236 { 237 enum GNUNET_GenericReturnValue ret; 238 239 ret = reveal_melt_ok (mrh, 240 j_response); 241 if (GNUNET_OK != ret) 242 { 243 GNUNET_break_op (0); 244 awr.hr.http_status = 0; 245 awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 246 break; 247 } 248 GNUNET_assert (NULL == mrh->callback); 249 TALER_EXCHANGE_post_reveal_melt_cancel (mrh); 250 return; 251 } 252 case MHD_HTTP_BAD_REQUEST: 253 /* This should never happen, either us or the exchange is buggy 254 (or API version conflict); just pass JSON reply to the application */ 255 awr.hr.ec = TALER_JSON_get_error_code (j_response); 256 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 257 break; 258 case MHD_HTTP_NOT_FOUND: 259 /* Nothing really to verify, the exchange basically just says 260 that it doesn't know this age-melt commitment. */ 261 awr.hr.ec = TALER_JSON_get_error_code (j_response); 262 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 263 break; 264 case MHD_HTTP_CONFLICT: 265 /* An age commitment for one of the coins did not fulfill 266 * the required maximum age requirement of the corresponding 267 * reserve. 268 * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE 269 * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH. 270 */ 271 awr.hr.ec = TALER_JSON_get_error_code (j_response); 272 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 273 break; 274 case MHD_HTTP_INTERNAL_SERVER_ERROR: 275 /* Server had an internal issue; we should retry, but this API 276 leaves this to the application */ 277 awr.hr.ec = TALER_JSON_get_error_code (j_response); 278 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 279 break; 280 default: 281 /* unexpected response code */ 282 GNUNET_break_op (0); 283 awr.hr.ec = TALER_JSON_get_error_code (j_response); 284 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 285 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 286 "Unexpected response code %u/%d for exchange melt\n", 287 (unsigned int) response_code, 288 (int) awr.hr.ec); 289 break; 290 } 291 mrh->callback (mrh->callback_cls, 292 &awr); 293 TALER_EXCHANGE_post_reveal_melt_cancel (mrh); 294 } 295 296 297 /** 298 * Call /reveal-melt 299 * 300 * @param curl_ctx The context for CURL 301 * @param mrh The handler 302 */ 303 static void 304 perform_protocol ( 305 struct GNUNET_CURL_Context *curl_ctx, 306 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh) 307 { 308 CURL *curlh; 309 json_t *j_batch_seeds; 310 311 312 j_batch_seeds = json_array (); 313 GNUNET_assert (NULL != j_batch_seeds); 314 315 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 316 { 317 if (mrh->reveal_input->noreveal_index == k) 318 continue; 319 320 GNUNET_assert (0 == json_array_append_new ( 321 j_batch_seeds, 322 GNUNET_JSON_from_data_auto ( 323 &mrh->md.kappa_batch_seeds.tuple[k]))); 324 } 325 { 326 json_t *j_request_body; 327 328 j_request_body = GNUNET_JSON_PACK ( 329 GNUNET_JSON_pack_data_auto ("rc", 330 &mrh->md.rc), 331 GNUNET_JSON_pack_array_steal ("batch_seeds", 332 j_batch_seeds)); 333 GNUNET_assert (NULL != j_request_body); 334 335 if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof) 336 { 337 json_t *j_age = GNUNET_JSON_PACK ( 338 TALER_JSON_pack_age_commitment ( 339 "age_commitment", 340 &mrh->reveal_input->melt_input->melt_age_commitment_proof->commitment) 341 ); 342 GNUNET_assert (NULL != j_age); 343 GNUNET_assert (0 == 344 json_object_update_new (j_request_body, 345 j_age)); 346 } 347 curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url); 348 GNUNET_assert (NULL != curlh); 349 GNUNET_assert (GNUNET_OK == 350 TALER_curl_easy_post (&mrh->post_ctx, 351 curlh, 352 j_request_body)); 353 json_decref (j_request_body); 354 } 355 mrh->job = GNUNET_CURL_job_add2 ( 356 curl_ctx, 357 curlh, 358 mrh->post_ctx.headers, 359 &handle_reveal_melt_finished, 360 mrh); 361 if (NULL == mrh->job) 362 { 363 GNUNET_break (0); 364 if (NULL != curlh) 365 curl_easy_cleanup (curlh); 366 /* caller must call _cancel to free mrh */ 367 } 368 } 369 370 371 struct TALER_EXCHANGE_PostRevealMeltHandle * 372 TALER_EXCHANGE_post_reveal_melt_create ( 373 struct GNUNET_CURL_Context *curl_ctx, 374 const char *exchange_url, 375 const struct TALER_EXCHANGE_RevealMeltInput *reveal_melt_input) 376 { 377 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh; 378 379 if (reveal_melt_input->num_blinding_values != 380 reveal_melt_input->melt_input->num_fresh_denom_pubs) 381 { 382 GNUNET_break (0); 383 return NULL; 384 } 385 mrh = GNUNET_new (struct TALER_EXCHANGE_PostRevealMeltHandle); 386 mrh->reveal_input = reveal_melt_input; 387 mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs; 388 mrh->curl_ctx = curl_ctx; 389 mrh->exchange_url = GNUNET_strdup (exchange_url); 390 TALER_EXCHANGE_get_melt_data ( 391 reveal_melt_input->rms, 392 reveal_melt_input->melt_input, 393 reveal_melt_input->blinding_seed, 394 reveal_melt_input->blinding_values, 395 &mrh->md); 396 return mrh; 397 } 398 399 400 enum TALER_ErrorCode 401 TALER_EXCHANGE_post_reveal_melt_start ( 402 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh, 403 TALER_EXCHANGE_PostRevealMeltCallback reveal_cb, 404 TALER_EXCHANGE_POST_REVEAL_MELT_RESULT_CLOSURE *reveal_cb_cls) 405 { 406 mrh->callback = reveal_cb; 407 mrh->callback_cls = reveal_cb_cls; 408 mrh->request_url = TALER_url_join (mrh->exchange_url, 409 "reveal-melt", 410 NULL); 411 if (NULL == mrh->request_url) 412 { 413 GNUNET_break (0); 414 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 415 } 416 perform_protocol (mrh->curl_ctx, 417 mrh); 418 if (NULL == mrh->job) 419 { 420 GNUNET_break (0); 421 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 422 } 423 return TALER_EC_NONE; 424 } 425 426 427 void 428 TALER_EXCHANGE_post_reveal_melt_cancel ( 429 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh) 430 { 431 if (NULL != mrh->job) 432 { 433 GNUNET_CURL_job_cancel (mrh->job); 434 mrh->job = NULL; 435 } 436 TALER_curl_easy_post_finished (&mrh->post_ctx); 437 TALER_EXCHANGE_free_melt_data (&mrh->md); 438 GNUNET_free (mrh->request_url); 439 GNUNET_free (mrh->exchange_url); 440 GNUNET_free (mrh); 441 }