exchange

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

exchange_api_reveal_withdraw.c (10126B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023-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_reveal_withdraw.c
     19  * @brief Implementation of /reveal-withdraw 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 "taler/taler_curl_lib.h"
     31 #include "taler/taler_json_lib.h"
     32 #include "taler/taler_exchange_service.h"
     33 #include "exchange_api_common.h"
     34 #include "exchange_api_handle.h"
     35 #include "taler/taler_signatures.h"
     36 #include "exchange_api_curl_defaults.h"
     37 
     38 /**
     39  * Handler for a running reveal-withdraw  request
     40  */
     41 struct TALER_EXCHANGE_RevealWithdrawHandle
     42 {
     43   /**
     44    * The commitment from the previous call withdraw
     45    */
     46   const struct TALER_HashBlindedPlanchetsP *planchets_h;
     47 
     48   /**
     49    * Number of coins for which to reveal tuples of seeds
     50    */
     51   size_t num_coins;
     52 
     53   /**
     54    * The TALER_CNC_KAPPA-1 tuple of seeds to reveal
     55    */
     56   struct TALER_RevealWithdrawMasterSeedsP seeds;
     57 
     58   /**
     59    * The url for the reveal request
     60    */
     61   char *request_url;
     62 
     63   /**
     64    * CURL handle for the request job.
     65    */
     66   struct GNUNET_CURL_Job *job;
     67 
     68   /**
     69    * Post Context
     70    */
     71   struct TALER_CURL_PostContext post_ctx;
     72 
     73   /**
     74    * Callback
     75    */
     76   TALER_EXCHANGE_RevealWithdrawCallback callback;
     77 
     78   /**
     79    * Reveal
     80    */
     81   void *callback_cls;
     82 };
     83 
     84 
     85 /**
     86  * We got a 200 OK response for the /reveal-withdraw operation.
     87  * Extract the signed blindedcoins and return it to the caller.
     88  *
     89  * @param wrh operation handle
     90  * @param j_response reply from the exchange
     91  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     92  */
     93 static enum GNUNET_GenericReturnValue
     94 reveal_withdraw_ok (
     95   struct TALER_EXCHANGE_RevealWithdrawHandle *wrh,
     96   const json_t *j_response)
     97 {
     98   struct TALER_EXCHANGE_RevealWithdrawResponse response = {
     99     .hr.reply = j_response,
    100     .hr.http_status = MHD_HTTP_OK,
    101   };
    102   const json_t *j_sigs;
    103   struct GNUNET_JSON_Specification spec[] = {
    104     GNUNET_JSON_spec_array_const ("ev_sigs",
    105                                   &j_sigs),
    106     GNUNET_JSON_spec_end ()
    107   };
    108 
    109   if (GNUNET_OK !=
    110       GNUNET_JSON_parse (j_response,
    111                          spec,
    112                          NULL, NULL))
    113   {
    114     GNUNET_break_op (0);
    115     return GNUNET_SYSERR;
    116   }
    117 
    118   if (wrh->num_coins != json_array_size (j_sigs))
    119   {
    120     /* Number of coins generated does not match our expectation */
    121     GNUNET_break_op (0);
    122     return GNUNET_SYSERR;
    123   }
    124 
    125   {
    126     struct TALER_BlindedDenominationSignature denom_sigs[wrh->num_coins];
    127     json_t *j_sig;
    128     size_t n;
    129 
    130     /* Reconstruct the coins and unblind the signatures */
    131     json_array_foreach (j_sigs, n, j_sig)
    132     {
    133       struct GNUNET_JSON_Specification ispec[] = {
    134         TALER_JSON_spec_blinded_denom_sig (NULL,
    135                                            &denom_sigs[n]),
    136         GNUNET_JSON_spec_end ()
    137       };
    138 
    139       if (GNUNET_OK !=
    140           GNUNET_JSON_parse (j_sig,
    141                              ispec,
    142                              NULL, NULL))
    143       {
    144         GNUNET_break_op (0);
    145         return GNUNET_SYSERR;
    146       }
    147     }
    148 
    149     response.details.ok.num_sigs = wrh->num_coins;
    150     response.details.ok.blinded_denom_sigs = denom_sigs;
    151     wrh->callback (wrh->callback_cls,
    152                    &response);
    153     /* Make sure the callback isn't called again */
    154     wrh->callback = NULL;
    155     /* Free resources */
    156     for (size_t i = 0; i < wrh->num_coins; i++)
    157       TALER_blinded_denom_sig_free (&denom_sigs[i]);
    158   }
    159 
    160   return GNUNET_OK;
    161 }
    162 
    163 
    164 /**
    165  * Function called when we're done processing the
    166  * HTTP /reveal-withdraw request.
    167  *
    168  * @param cls the `struct TALER_EXCHANGE_RevealWithdrawHandle`
    169  * @param response_code The HTTP response code
    170  * @param response response data
    171  */
    172 static void
    173 handle_reveal_withdraw_finished (
    174   void *cls,
    175   long response_code,
    176   const void *response)
    177 {
    178   struct TALER_EXCHANGE_RevealWithdrawHandle *wrh = cls;
    179   const json_t *j_response = response;
    180   struct TALER_EXCHANGE_RevealWithdrawResponse awr = {
    181     .hr.reply = j_response,
    182     .hr.http_status = (unsigned int) response_code
    183   };
    184 
    185   wrh->job = NULL;
    186   switch (response_code)
    187   {
    188   case 0:
    189     awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    190     break;
    191   case MHD_HTTP_OK:
    192     {
    193       enum GNUNET_GenericReturnValue ret;
    194 
    195       ret = reveal_withdraw_ok (wrh,
    196                                 j_response);
    197       if (GNUNET_OK != ret)
    198       {
    199         GNUNET_break_op (0);
    200         awr.hr.http_status = 0;
    201         awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    202         break;
    203       }
    204       GNUNET_assert (NULL == wrh->callback);
    205       TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
    206       return;
    207     }
    208   case MHD_HTTP_BAD_REQUEST:
    209     /* This should never happen, either us or the exchange is buggy
    210        (or API version conflict); just pass JSON reply to the application */
    211     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    212     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    213     break;
    214   case MHD_HTTP_NOT_FOUND:
    215     /* Nothing really to verify, the exchange basically just says
    216        that it doesn't know this age-withdraw commitment. */
    217     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    218     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    219     break;
    220   case MHD_HTTP_CONFLICT:
    221     /* An age commitment for one of the coins did not fulfill
    222      * the required maximum age requirement of the corresponding
    223      * reserve.
    224      * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE
    225      * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH.
    226      */
    227     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    228     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    229     break;
    230   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    231     /* Server had an internal issue; we should retry, but this API
    232        leaves this to the application */
    233     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    234     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    235     break;
    236   default:
    237     /* unexpected response code */
    238     GNUNET_break_op (0);
    239     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    240     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    241     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    242                 "Unexpected response code %u/%d for exchange age-withdraw\n",
    243                 (unsigned int) response_code,
    244                 (int) awr.hr.ec);
    245     break;
    246   }
    247   wrh->callback (wrh->callback_cls,
    248                  &awr);
    249   TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
    250 }
    251 
    252 
    253 /**
    254  * Call /reveal-withdraw
    255  *
    256  * @param curl_ctx The context for CURL
    257  * @param wrh The handler
    258  */
    259 static void
    260 perform_protocol (
    261   struct GNUNET_CURL_Context *curl_ctx,
    262   struct TALER_EXCHANGE_RevealWithdrawHandle *wrh)
    263 {
    264   CURL *curlh;
    265   json_t *j_array_of_secrets;
    266 
    267   j_array_of_secrets = json_array ();
    268   GNUNET_assert (NULL != j_array_of_secrets);
    269 
    270   for (uint8_t k = 0; k < TALER_CNC_KAPPA - 1; k++)
    271   {
    272     json_t *j_sec = GNUNET_JSON_from_data_auto (&wrh->seeds.tuple[k]);
    273     GNUNET_assert (NULL != j_sec);
    274     GNUNET_assert (0 == json_array_append_new (j_array_of_secrets,
    275                                                j_sec));
    276   }
    277   {
    278     json_t *j_request_body;
    279 
    280     j_request_body = GNUNET_JSON_PACK (
    281       GNUNET_JSON_pack_data_auto ("planchets_h",
    282                                   wrh->planchets_h),
    283       GNUNET_JSON_pack_array_steal ("disclosed_batch_seeds",
    284                                     j_array_of_secrets));
    285     GNUNET_assert (NULL != j_request_body);
    286 
    287     curlh = TALER_EXCHANGE_curl_easy_get_ (wrh->request_url);
    288     GNUNET_assert (NULL != curlh);
    289     GNUNET_assert (GNUNET_OK ==
    290                    TALER_curl_easy_post (&wrh->post_ctx,
    291                                          curlh,
    292                                          j_request_body));
    293 
    294     json_decref (j_request_body);
    295   }
    296 
    297   wrh->job = GNUNET_CURL_job_add2 (
    298     curl_ctx,
    299     curlh,
    300     wrh->post_ctx.headers,
    301     &handle_reveal_withdraw_finished,
    302     wrh);
    303   if (NULL == wrh->job)
    304   {
    305     GNUNET_break (0);
    306     if (NULL != curlh)
    307       curl_easy_cleanup (curlh);
    308     TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
    309   }
    310 
    311   return;
    312 }
    313 
    314 
    315 struct TALER_EXCHANGE_RevealWithdrawHandle *
    316 TALER_EXCHANGE_reveal_withdraw (
    317   struct GNUNET_CURL_Context *curl_ctx,
    318   const char *exchange_url,
    319   size_t num_coins,
    320   const struct TALER_HashBlindedPlanchetsP *planchets_h,
    321   const struct TALER_RevealWithdrawMasterSeedsP *seeds,
    322   TALER_EXCHANGE_RevealWithdrawCallback reveal_cb,
    323   void *reveal_cb_cls)
    324 {
    325   struct TALER_EXCHANGE_RevealWithdrawHandle *wrh =
    326     GNUNET_new (struct TALER_EXCHANGE_RevealWithdrawHandle);
    327   wrh->planchets_h = planchets_h;
    328   wrh->num_coins = num_coins;
    329   wrh->seeds = *seeds;
    330   wrh->callback = reveal_cb;
    331   wrh->callback_cls = reveal_cb_cls;
    332   wrh->request_url = TALER_url_join (exchange_url,
    333                                      "reveal-withdraw",
    334                                      NULL);
    335   if (NULL == wrh->request_url)
    336   {
    337     GNUNET_break (0);
    338     GNUNET_free (wrh);
    339     return NULL;
    340   }
    341 
    342   perform_protocol (curl_ctx, wrh);
    343 
    344   return wrh;
    345 }
    346 
    347 
    348 void
    349 TALER_EXCHANGE_reveal_withdraw_cancel (
    350   struct TALER_EXCHANGE_RevealWithdrawHandle *wrh)
    351 {
    352   if (NULL != wrh->job)
    353   {
    354     GNUNET_CURL_job_cancel (wrh->job);
    355     wrh->job = NULL;
    356   }
    357   TALER_curl_easy_post_finished (&wrh->post_ctx);
    358 
    359   if (NULL != wrh->request_url)
    360     GNUNET_free (wrh->request_url);
    361 
    362   GNUNET_free (wrh);
    363 }
    364 
    365 
    366 /* exchange_api_reveal_withdraw.c */