exchange

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

exchange_api_blinding_prepare.c (11925B)


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