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