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-reveal-withdraw.c (10697B)


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