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