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 (11034B)


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