exchange_api_post-reveal-melt.c (13193B)
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 GNUNET_JSON_parse_free (spec); 180 return GNUNET_SYSERR; 181 } 182 GNUNET_JSON_parse_free (spec); 183 rci->sig = coin.sig; 184 } 185 186 response.details.ok.num_coins = mrh->num_expected_coins; 187 response.details.ok.coins = coins; 188 mrh->callback (mrh->callback_cls, 189 &response); 190 /* Make sure the callback isn't called again */ 191 mrh->callback = NULL; 192 /* Free resources */ 193 for (size_t i = 0; i < mrh->num_expected_coins; i++) 194 { 195 struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i]; 196 197 TALER_denom_sig_free (&rci->sig); 198 TALER_blinded_denom_sig_free (&blind_sigs[i]); 199 } 200 } 201 202 return GNUNET_OK; 203 } 204 205 206 /** 207 * Function called when we're done processing the 208 * HTTP /reveal-melt request. 209 * 210 * @param cls the `struct TALER_EXCHANGE_RevealMeltHandle` 211 * @param response_code The HTTP response code 212 * @param response response data 213 */ 214 static void 215 handle_reveal_melt_finished ( 216 void *cls, 217 long response_code, 218 const void *response) 219 { 220 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh = cls; 221 const json_t *j_response = response; 222 struct TALER_EXCHANGE_PostRevealMeltResponse awr = { 223 .hr.reply = j_response, 224 .hr.http_status = (unsigned int) response_code 225 }; 226 227 mrh->job = NULL; 228 switch (response_code) 229 { 230 case 0: 231 awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 232 break; 233 case MHD_HTTP_OK: 234 { 235 enum GNUNET_GenericReturnValue ret; 236 237 ret = reveal_melt_ok (mrh, 238 j_response); 239 if (GNUNET_OK != ret) 240 { 241 GNUNET_break_op (0); 242 awr.hr.http_status = 0; 243 awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 244 break; 245 } 246 GNUNET_assert (NULL == mrh->callback); 247 TALER_EXCHANGE_post_reveal_melt_cancel (mrh); 248 return; 249 } 250 case MHD_HTTP_BAD_REQUEST: 251 /* This should never happen, either us or the exchange is buggy 252 (or API version conflict); just pass JSON reply to the application */ 253 awr.hr.ec = TALER_JSON_get_error_code (j_response); 254 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 255 break; 256 case MHD_HTTP_NOT_FOUND: 257 /* Nothing really to verify, the exchange basically just says 258 that it doesn't know this age-melt commitment. */ 259 awr.hr.ec = TALER_JSON_get_error_code (j_response); 260 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 261 break; 262 case MHD_HTTP_CONFLICT: 263 /* An age commitment for one of the coins did not fulfill 264 * the required maximum age requirement of the corresponding 265 * reserve. 266 * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE 267 * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH. 268 */ 269 awr.hr.ec = TALER_JSON_get_error_code (j_response); 270 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 271 break; 272 case MHD_HTTP_INTERNAL_SERVER_ERROR: 273 /* Server had an internal issue; we should retry, but this API 274 leaves this to the application */ 275 awr.hr.ec = TALER_JSON_get_error_code (j_response); 276 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 277 break; 278 default: 279 /* unexpected response code */ 280 GNUNET_break_op (0); 281 awr.hr.ec = TALER_JSON_get_error_code (j_response); 282 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 283 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 284 "Unexpected response code %u/%d for exchange melt\n", 285 (unsigned int) response_code, 286 (int) awr.hr.ec); 287 break; 288 } 289 mrh->callback (mrh->callback_cls, 290 &awr); 291 TALER_EXCHANGE_post_reveal_melt_cancel (mrh); 292 } 293 294 295 /** 296 * Call /reveal-melt 297 * 298 * @param curl_ctx The context for CURL 299 * @param mrh The handler 300 */ 301 static void 302 perform_protocol ( 303 struct GNUNET_CURL_Context *curl_ctx, 304 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh) 305 { 306 CURL *curlh; 307 json_t *j_batch_seeds; 308 309 310 j_batch_seeds = json_array (); 311 GNUNET_assert (NULL != j_batch_seeds); 312 313 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 314 { 315 if (mrh->reveal_input->noreveal_index == k) 316 continue; 317 318 GNUNET_assert (0 == json_array_append_new ( 319 j_batch_seeds, 320 GNUNET_JSON_from_data_auto ( 321 &mrh->md.kappa_batch_seeds.tuple[k]))); 322 } 323 { 324 json_t *j_request_body; 325 326 j_request_body = GNUNET_JSON_PACK ( 327 GNUNET_JSON_pack_data_auto ("rc", 328 &mrh->md.rc), 329 GNUNET_JSON_pack_array_steal ("batch_seeds", 330 j_batch_seeds)); 331 GNUNET_assert (NULL != j_request_body); 332 333 if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof) 334 { 335 json_t *j_age = GNUNET_JSON_PACK ( 336 TALER_JSON_pack_age_commitment ( 337 "age_commitment", 338 &mrh->reveal_input->melt_input->melt_age_commitment_proof->commitment) 339 ); 340 GNUNET_assert (NULL != j_age); 341 GNUNET_assert (0 == 342 json_object_update_new (j_request_body, 343 j_age)); 344 } 345 curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url); 346 GNUNET_assert (NULL != curlh); 347 GNUNET_assert (GNUNET_OK == 348 TALER_curl_easy_post (&mrh->post_ctx, 349 curlh, 350 j_request_body)); 351 json_decref (j_request_body); 352 } 353 mrh->job = GNUNET_CURL_job_add2 ( 354 curl_ctx, 355 curlh, 356 mrh->post_ctx.headers, 357 &handle_reveal_melt_finished, 358 mrh); 359 if (NULL == mrh->job) 360 { 361 GNUNET_break (0); 362 if (NULL != curlh) 363 curl_easy_cleanup (curlh); 364 /* caller must call _cancel to free mrh */ 365 } 366 } 367 368 369 struct TALER_EXCHANGE_PostRevealMeltHandle * 370 TALER_EXCHANGE_post_reveal_melt_create ( 371 struct GNUNET_CURL_Context *curl_ctx, 372 const char *exchange_url, 373 const struct TALER_EXCHANGE_RevealMeltInput *reveal_melt_input) 374 { 375 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh; 376 377 if (reveal_melt_input->num_blinding_values != 378 reveal_melt_input->melt_input->num_fresh_denom_pubs) 379 { 380 GNUNET_break (0); 381 return NULL; 382 } 383 mrh = GNUNET_new (struct TALER_EXCHANGE_PostRevealMeltHandle); 384 mrh->reveal_input = reveal_melt_input; 385 mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs; 386 mrh->curl_ctx = curl_ctx; 387 mrh->exchange_url = GNUNET_strdup (exchange_url); 388 TALER_EXCHANGE_get_melt_data ( 389 reveal_melt_input->rms, 390 reveal_melt_input->melt_input, 391 reveal_melt_input->blinding_seed, 392 reveal_melt_input->blinding_values, 393 &mrh->md); 394 return mrh; 395 } 396 397 398 enum TALER_ErrorCode 399 TALER_EXCHANGE_post_reveal_melt_start ( 400 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh, 401 TALER_EXCHANGE_PostRevealMeltCallback reveal_cb, 402 TALER_EXCHANGE_POST_REVEAL_MELT_RESULT_CLOSURE *reveal_cb_cls) 403 { 404 mrh->callback = reveal_cb; 405 mrh->callback_cls = reveal_cb_cls; 406 mrh->request_url = TALER_url_join (mrh->exchange_url, 407 "reveal-melt", 408 NULL); 409 if (NULL == mrh->request_url) 410 { 411 GNUNET_break (0); 412 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 413 } 414 perform_protocol (mrh->curl_ctx, 415 mrh); 416 if (NULL == mrh->job) 417 { 418 GNUNET_break (0); 419 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 420 } 421 return TALER_EC_NONE; 422 } 423 424 425 void 426 TALER_EXCHANGE_post_reveal_melt_cancel ( 427 struct TALER_EXCHANGE_PostRevealMeltHandle *mrh) 428 { 429 if (NULL != mrh->job) 430 { 431 GNUNET_CURL_job_cancel (mrh->job); 432 mrh->job = NULL; 433 } 434 TALER_curl_easy_post_finished (&mrh->post_ctx); 435 TALER_EXCHANGE_free_melt_data (&mrh->md); 436 GNUNET_free (mrh->request_url); 437 GNUNET_free (mrh->exchange_url); 438 GNUNET_free (mrh); 439 }