exchange_api_blinding_prepare.c (11925B)
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_blinding_prepare.c 19 * @brief Implementation of /blinding-prepare requests 20 * @author Özgür Kesim 21 */ 22 23 #include "taler/platform.h" 24 #include <gnunet/gnunet_common.h> 25 #include <jansson.h> 26 #include <microhttpd.h> /* just for HTTP status codes */ 27 #include <gnunet/gnunet_util_lib.h> 28 #include <gnunet/gnunet_json_lib.h> 29 #include <gnunet/gnunet_curl_lib.h> 30 #include <sys/wait.h> 31 #include "taler/taler_curl_lib.h" 32 #include "taler/taler_error_codes.h" 33 #include "taler/taler_json_lib.h" 34 #include "taler/taler_exchange_service.h" 35 #include "exchange_api_common.h" 36 #include "exchange_api_handle.h" 37 #include "taler/taler_signatures.h" 38 #include "exchange_api_curl_defaults.h" 39 #include "taler/taler_util.h" 40 41 /** 42 * A /blinding-prepare request-handle 43 */ 44 struct TALER_EXCHANGE_BlindingPrepareHandle 45 { 46 /** 47 * number of elements to prepare 48 */ 49 size_t num; 50 51 /** 52 * True, if this operation is for melting (or withdraw otherwise). 53 */ 54 bool for_melt; 55 56 /** 57 * The seed for the batch of nonces. 58 */ 59 const struct TALER_BlindingMasterSeedP *seed; 60 61 /** 62 * The url for this request. 63 */ 64 char *url; 65 66 /** 67 * Context for curl. 68 */ 69 struct GNUNET_CURL_Context *curl_ctx; 70 71 /** 72 * CURL handle for the request job. 73 */ 74 struct GNUNET_CURL_Job *job; 75 76 /** 77 * Post Context 78 */ 79 struct TALER_CURL_PostContext post_ctx; 80 81 /** 82 * Function to call with withdraw response results. 83 */ 84 TALER_EXCHANGE_BlindingPrepareCallback callback; 85 86 /** 87 * Closure for @e callback 88 */ 89 void *callback_cls; 90 91 }; 92 93 94 /** 95 * We got a 200 OK response for the /blinding-prepare operation. 96 * Extract the r_pub values and return them to the caller via the callback 97 * 98 * @param handle operation handle 99 * @param response response details 100 * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors 101 */ 102 static enum GNUNET_GenericReturnValue 103 blinding_prepare_ok (struct TALER_EXCHANGE_BlindingPrepareHandle *handle, 104 struct TALER_EXCHANGE_BlindingPrepareResponse *response) 105 { 106 const json_t *j_r_pubs; 107 const char *cipher; 108 struct GNUNET_JSON_Specification spec[] = { 109 GNUNET_JSON_spec_string ("cipher", 110 &cipher), 111 GNUNET_JSON_spec_array_const ("r_pubs", 112 &j_r_pubs), 113 GNUNET_JSON_spec_end () 114 }; 115 116 if (GNUNET_OK != 117 GNUNET_JSON_parse (response->hr.reply, 118 spec, 119 NULL, NULL)) 120 { 121 GNUNET_break_op (0); 122 return GNUNET_SYSERR; 123 } 124 125 if (strcmp ("CS", cipher)) 126 { 127 GNUNET_break_op (0); 128 return GNUNET_SYSERR; 129 } 130 131 if (json_array_size (j_r_pubs) 132 != handle->num) 133 { 134 GNUNET_break_op (0); 135 return GNUNET_SYSERR; 136 } 137 138 { 139 size_t num = handle->num; 140 const json_t *j_pair; 141 size_t idx; 142 struct TALER_ExchangeBlindingValues blinding_values[GNUNET_NZL (num)]; 143 144 memset (blinding_values, 145 0, 146 sizeof(blinding_values)); 147 148 json_array_foreach (j_r_pubs, idx, j_pair) { 149 struct GNUNET_CRYPTO_BlindingInputValues *bi = 150 GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues); 151 struct GNUNET_CRYPTO_CSPublicRPairP *csv = &bi->details.cs_values; 152 struct GNUNET_JSON_Specification tuple[] = { 153 GNUNET_JSON_spec_fixed (NULL, 154 &csv->r_pub[0], 155 sizeof(csv->r_pub[0])), 156 GNUNET_JSON_spec_fixed (NULL, 157 &csv->r_pub[1], 158 sizeof(csv->r_pub[1])), 159 GNUNET_JSON_spec_end () 160 }; 161 struct GNUNET_JSON_Specification jspec[] = { 162 TALER_JSON_spec_tuple_of (NULL, tuple), 163 GNUNET_JSON_spec_end () 164 }; 165 const char *err_msg; 166 unsigned int err_line; 167 168 if (GNUNET_OK != 169 GNUNET_JSON_parse (j_pair, 170 jspec, 171 &err_msg, 172 &err_line)) 173 { 174 GNUNET_break_op (0); 175 GNUNET_free (bi); 176 for (size_t i=0; i < idx; i++) 177 TALER_denom_ewv_free (&blinding_values[i]); 178 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 179 "Error while parsing response: in line %d: %s", 180 err_line, 181 err_msg); 182 return GNUNET_SYSERR; 183 } 184 185 bi->cipher = GNUNET_CRYPTO_BSA_CS; 186 bi->rc = 1; 187 blinding_values[idx].blinding_inputs = bi; 188 } 189 190 response->details.ok.blinding_values = blinding_values; 191 response->details.ok.num_blinding_values = num; 192 193 handle->callback ( 194 handle->callback_cls, 195 response); 196 197 for (size_t i = 0; i < num; i++) 198 TALER_denom_ewv_free (&blinding_values[i]); 199 } 200 return GNUNET_OK; 201 } 202 203 204 /** 205 * Function called when we're done processing the HTTP /blinding-prepare request. 206 * 207 * @param cls the `struct TALER_EXCHANGE_BlindingPrepareHandle` 208 * @param response_code HTTP response code, 0 on error 209 * @param response parsed JSON result, NULL on error 210 */ 211 static void 212 handle_blinding_prepare_finished (void *cls, 213 long response_code, 214 const void *response) 215 { 216 struct TALER_EXCHANGE_BlindingPrepareHandle *handle = cls; 217 const json_t *j_response = response; 218 struct TALER_EXCHANGE_BlindingPrepareResponse bpr = { 219 .hr = { 220 .reply = j_response, 221 .http_status = (unsigned int) response_code 222 }, 223 }; 224 225 handle->job = NULL; 226 227 switch (response_code) 228 { 229 case 0: 230 bpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 231 break; 232 233 case MHD_HTTP_OK: 234 { 235 if (GNUNET_OK != 236 blinding_prepare_ok (handle, 237 &bpr)) 238 { 239 GNUNET_break_op (0); 240 bpr.hr.http_status = 0; 241 bpr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 242 break; 243 } 244 } 245 TALER_EXCHANGE_blinding_prepare_cancel (handle); 246 return; 247 248 case MHD_HTTP_BAD_REQUEST: 249 /* This should never happen, either us or the exchange is buggy 250 (or API version conflict); just pass JSON reply to the application */ 251 bpr.hr.ec = TALER_JSON_get_error_code (j_response); 252 bpr.hr.hint = TALER_JSON_get_error_hint (j_response); 253 break; 254 255 case MHD_HTTP_NOT_FOUND: 256 /* Nothing really to verify, the exchange basically just says 257 that it doesn't know the /csr endpoint or denomination. 258 Can happen if the exchange doesn't support Clause Schnorr. 259 We should simply pass the JSON reply to the application. */ 260 bpr.hr.ec = TALER_JSON_get_error_code (j_response); 261 bpr.hr.hint = TALER_JSON_get_error_hint (j_response); 262 break; 263 264 case MHD_HTTP_GONE: 265 /* could happen if denomination was revoked */ 266 /* Note: one might want to check /keys for revocation 267 signature here, alas tricky in case our /keys 268 is outdated => left to clients */ 269 bpr.hr.ec = TALER_JSON_get_error_code (j_response); 270 bpr.hr.hint = TALER_JSON_get_error_hint (j_response); 271 break; 272 273 case MHD_HTTP_INTERNAL_SERVER_ERROR: 274 /* Server had an internal issue; we should retry, but this API 275 leaves this to the application */ 276 bpr.hr.ec = TALER_JSON_get_error_code (j_response); 277 bpr.hr.hint = TALER_JSON_get_error_hint (j_response); 278 break; 279 280 default: 281 /* unexpected response code */ 282 GNUNET_break_op (0); 283 bpr.hr.ec = TALER_JSON_get_error_code (j_response); 284 bpr.hr.hint = TALER_JSON_get_error_hint (j_response); 285 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 286 "Unexpected response code %u/%d for the blinding-prepare request\n", 287 (unsigned int) response_code, 288 (int) bpr.hr.ec); 289 break; 290 291 } 292 293 handle->callback (handle->callback_cls, 294 &bpr); 295 handle->callback = NULL; 296 TALER_EXCHANGE_blinding_prepare_cancel (handle); 297 } 298 299 300 struct TALER_EXCHANGE_BlindingPrepareHandle * 301 TALER_EXCHANGE_blinding_prepare ( 302 struct GNUNET_CURL_Context *curl_ctx, 303 const char *exchange_url, 304 const struct TALER_BlindingMasterSeedP *seed, 305 bool for_melt, 306 size_t num, 307 const struct TALER_EXCHANGE_NonceKey nonce_keys[static num], 308 TALER_EXCHANGE_BlindingPrepareCallback callback, 309 void *callback_cls) 310 { 311 struct TALER_EXCHANGE_BlindingPrepareHandle *bph; 312 313 if (0 == num) 314 { 315 GNUNET_break (0); 316 return NULL; 317 } 318 for (unsigned int i = 0; i<num; i++) 319 if (GNUNET_CRYPTO_BSA_CS != 320 nonce_keys[i].pk->key.bsign_pub_key->cipher) 321 { 322 GNUNET_break (0); 323 return NULL; 324 } 325 bph = GNUNET_new (struct TALER_EXCHANGE_BlindingPrepareHandle); 326 bph->num = num; 327 bph->callback = callback; 328 bph->for_melt = for_melt; 329 bph->callback_cls = callback_cls; 330 bph->url = TALER_url_join (exchange_url, 331 "blinding-prepare", 332 NULL); 333 if (NULL == bph->url) 334 { 335 GNUNET_break (0); 336 GNUNET_free (bph); 337 return NULL; 338 } 339 { 340 CURL *eh; 341 json_t *j_nks; 342 json_t *j_request = GNUNET_JSON_PACK ( 343 GNUNET_JSON_pack_string ("cipher", 344 "CS"), 345 GNUNET_JSON_pack_string ("operation", 346 for_melt ? "melt" : "withdraw"), 347 GNUNET_JSON_pack_data_auto ("seed", 348 seed)); 349 GNUNET_assert (NULL != j_request); 350 351 j_nks = json_array (); 352 GNUNET_assert (NULL != j_nks); 353 354 for (size_t i = 0; i<num; i++) 355 { 356 const struct TALER_EXCHANGE_NonceKey *nk = &nonce_keys[i]; 357 json_t *j_entry = GNUNET_JSON_PACK ( 358 GNUNET_JSON_pack_uint64 ("coin_offset", 359 nk->cnc_num), 360 GNUNET_JSON_pack_data_auto ("denom_pub_hash", 361 &nk->pk->h_key)); 362 363 GNUNET_assert (NULL != j_entry); 364 GNUNET_assert (0 == 365 json_array_append_new (j_nks, 366 j_entry)); 367 } 368 GNUNET_assert (0 == 369 json_object_set_new (j_request, 370 "nks", 371 j_nks)); 372 eh = TALER_EXCHANGE_curl_easy_get_ (bph->url); 373 if ( (NULL == eh) || 374 (GNUNET_OK != 375 TALER_curl_easy_post (&bph->post_ctx, 376 eh, 377 j_request))) 378 { 379 GNUNET_break (0); 380 if (NULL != eh) 381 curl_easy_cleanup (eh); 382 json_decref (j_request); 383 GNUNET_free (bph->url); 384 GNUNET_free (bph); 385 return NULL; 386 } 387 388 json_decref (j_request); 389 bph->job = GNUNET_CURL_job_add2 (curl_ctx, 390 eh, 391 bph->post_ctx.headers, 392 &handle_blinding_prepare_finished, 393 bph); 394 if (NULL == bph->job) 395 { 396 GNUNET_break (0); 397 TALER_EXCHANGE_blinding_prepare_cancel (bph); 398 return NULL; 399 } 400 } 401 return bph; 402 } 403 404 405 void 406 TALER_EXCHANGE_blinding_prepare_cancel ( 407 struct TALER_EXCHANGE_BlindingPrepareHandle *bph) 408 { 409 if (NULL == bph) 410 return; 411 if (NULL != bph->job) 412 { 413 GNUNET_CURL_job_cancel (bph->job); 414 bph->job = NULL; 415 } 416 GNUNET_free (bph->url); 417 TALER_curl_easy_post_finished (&bph->post_ctx); 418 GNUNET_free (bph); 419 } 420 421 422 /* end of lib/exchange_api_blinding_prepare.c */