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