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