exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */