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