exchange_api_post-blinding-prepare.c (12480B)
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_INTERNAL_SERVER_ERROR: 282 /* Server had an internal issue; we should retry, but this API 283 leaves this to the application */ 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 default: 289 /* unexpected response code */ 290 GNUNET_break_op (0); 291 bpr.hr.ec = TALER_JSON_get_error_code (j_response); 292 bpr.hr.hint = TALER_JSON_get_error_hint (j_response); 293 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 294 "Unexpected response code %u/%d for the blinding-prepare request\n", 295 (unsigned int) response_code, 296 (int) bpr.hr.ec); 297 break; 298 299 } 300 301 handle->callback (handle->callback_cls, 302 &bpr); 303 handle->callback = NULL; 304 TALER_EXCHANGE_post_blinding_prepare_cancel (handle); 305 } 306 307 308 struct TALER_EXCHANGE_PostBlindingPrepareHandle * 309 TALER_EXCHANGE_post_blinding_prepare_create ( 310 struct GNUNET_CURL_Context *curl_ctx, 311 const char *exchange_url, 312 const struct TALER_BlindingMasterSeedP *seed, 313 bool for_melt, 314 size_t num, 315 const struct TALER_EXCHANGE_NonceKey nonce_keys[static num]) 316 { 317 struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph; 318 319 if (0 == num) 320 { 321 GNUNET_break (0); 322 return NULL; 323 } 324 for (unsigned int i = 0; i < num; i++) 325 if (GNUNET_CRYPTO_BSA_CS != 326 nonce_keys[i].pk->key.bsign_pub_key->cipher) 327 { 328 GNUNET_break (0); 329 return NULL; 330 } 331 bph = GNUNET_new (struct TALER_EXCHANGE_PostBlindingPrepareHandle); 332 bph->num = num; 333 bph->for_melt = for_melt; 334 bph->seed = seed; 335 bph->curl_ctx = curl_ctx; 336 bph->exchange_url = GNUNET_strdup (exchange_url); 337 bph->nonce_keys = GNUNET_new_array (num, 338 struct TALER_EXCHANGE_NonceKey); 339 memcpy (bph->nonce_keys, 340 nonce_keys, 341 num * sizeof (struct TALER_EXCHANGE_NonceKey)); 342 return bph; 343 } 344 345 346 enum TALER_ErrorCode 347 TALER_EXCHANGE_post_blinding_prepare_start ( 348 struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph, 349 TALER_EXCHANGE_PostBlindingPrepareCallback cb, 350 TALER_EXCHANGE_POST_BLINDING_PREPARE_RESULT_CLOSURE *cb_cls) 351 { 352 CURL *eh; 353 json_t *j_nks; 354 json_t *j_request; 355 356 bph->callback = cb; 357 bph->callback_cls = cb_cls; 358 359 bph->url = TALER_url_join (bph->exchange_url, 360 "blinding-prepare", 361 NULL); 362 if (NULL == bph->url) 363 { 364 GNUNET_break (0); 365 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 366 } 367 368 j_request = GNUNET_JSON_PACK ( 369 GNUNET_JSON_pack_string ("cipher", 370 "CS"), 371 GNUNET_JSON_pack_string ("operation", 372 bph->for_melt ? "melt" : "withdraw"), 373 GNUNET_JSON_pack_data_auto ("seed", 374 bph->seed)); 375 GNUNET_assert (NULL != j_request); 376 377 j_nks = json_array (); 378 GNUNET_assert (NULL != j_nks); 379 380 for (size_t i = 0; i < bph->num; i++) 381 { 382 const struct TALER_EXCHANGE_NonceKey *nk = &bph->nonce_keys[i]; 383 json_t *j_entry = GNUNET_JSON_PACK ( 384 GNUNET_JSON_pack_uint64 ("coin_offset", 385 nk->cnc_num), 386 GNUNET_JSON_pack_data_auto ("denom_pub_hash", 387 &nk->pk->h_key)); 388 389 GNUNET_assert (NULL != j_entry); 390 GNUNET_assert (0 == 391 json_array_append_new (j_nks, 392 j_entry)); 393 } 394 GNUNET_assert (0 == 395 json_object_set_new (j_request, 396 "nks", 397 j_nks)); 398 399 eh = TALER_EXCHANGE_curl_easy_get_ (bph->url); 400 if ( (NULL == eh) || 401 (GNUNET_OK != 402 TALER_curl_easy_post (&bph->post_ctx, 403 eh, 404 j_request))) 405 { 406 GNUNET_break (0); 407 if (NULL != eh) 408 curl_easy_cleanup (eh); 409 json_decref (j_request); 410 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 411 } 412 413 json_decref (j_request); 414 bph->job = GNUNET_CURL_job_add2 (bph->curl_ctx, 415 eh, 416 bph->post_ctx.headers, 417 &handle_blinding_prepare_finished, 418 bph); 419 if (NULL == bph->job) 420 { 421 GNUNET_break (0); 422 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 423 } 424 return TALER_EC_NONE; 425 } 426 427 428 void 429 TALER_EXCHANGE_post_blinding_prepare_cancel ( 430 struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph) 431 { 432 if (NULL == bph) 433 return; 434 if (NULL != bph->job) 435 { 436 GNUNET_CURL_job_cancel (bph->job); 437 bph->job = NULL; 438 } 439 GNUNET_free (bph->url); 440 GNUNET_free (bph->exchange_url); 441 GNUNET_free (bph->nonce_keys); 442 TALER_curl_easy_post_finished (&bph->post_ctx); 443 GNUNET_free (bph); 444 } 445 446 447 /* end of lib/exchange_api_post-blinding-prepare.c */