exchange

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

exchange_api_reserves_open.c (16079B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2023 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_reserves_open.c
     19  * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <jansson.h>
     24 #include <microhttpd.h> /* just for HTTP open codes */
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include <gnunet/gnunet_json_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include "taler/taler_exchange_service.h"
     29 #include "taler/taler_json_lib.h"
     30 #include "exchange_api_common.h"
     31 #include "exchange_api_handle.h"
     32 #include "taler/taler_signatures.h"
     33 #include "exchange_api_curl_defaults.h"
     34 
     35 
     36 /**
     37  * Information we keep per coin to validate the reply.
     38  */
     39 struct CoinData
     40 {
     41   /**
     42    * Public key of the coin.
     43    */
     44   struct TALER_CoinSpendPublicKeyP coin_pub;
     45 
     46   /**
     47    * Signature by the coin.
     48    */
     49   struct TALER_CoinSpendSignatureP coin_sig;
     50 
     51   /**
     52    * The hash of the denomination's public key
     53    */
     54   struct TALER_DenominationHashP h_denom_pub;
     55 
     56   /**
     57    * How much did this coin contribute.
     58    */
     59   struct TALER_Amount contribution;
     60 };
     61 
     62 
     63 /**
     64  * @brief A /reserves/$RID/open Handle
     65  */
     66 struct TALER_EXCHANGE_ReservesOpenHandle
     67 {
     68 
     69   /**
     70    * The keys of the exchange this request handle will use
     71    */
     72   struct TALER_EXCHANGE_Keys *keys;
     73 
     74   /**
     75    * The url for this request.
     76    */
     77   char *url;
     78 
     79   /**
     80    * Handle for the request.
     81    */
     82   struct GNUNET_CURL_Job *job;
     83 
     84   /**
     85    * Context for #TEH_curl_easy_post(). Keeps the data that must
     86    * persist for Curl to make the upload.
     87    */
     88   struct TALER_CURL_PostContext post_ctx;
     89 
     90   /**
     91    * Function to call with the result.
     92    */
     93   TALER_EXCHANGE_ReservesOpenCallback cb;
     94 
     95   /**
     96    * Closure for @a cb.
     97    */
     98   void *cb_cls;
     99 
    100   /**
    101    * Information we keep per coin to validate the reply.
    102    */
    103   struct CoinData *coins;
    104 
    105   /**
    106    * Length of the @e coins array.
    107    */
    108   unsigned int num_coins;
    109 
    110   /**
    111    * Public key of the reserve we are querying.
    112    */
    113   struct TALER_ReservePublicKeyP reserve_pub;
    114 
    115   /**
    116    * Our signature.
    117    */
    118   struct TALER_ReserveSignatureP reserve_sig;
    119 
    120   /**
    121    * When did we make the request.
    122    */
    123   struct GNUNET_TIME_Timestamp ts;
    124 
    125 };
    126 
    127 
    128 /**
    129  * We received an #MHD_HTTP_OK open code. Handle the JSON
    130  * response.
    131  *
    132  * @param roh handle of the request
    133  * @param j JSON response
    134  * @return #GNUNET_OK on success
    135  */
    136 static enum GNUNET_GenericReturnValue
    137 handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
    138                          const json_t *j)
    139 {
    140   struct TALER_EXCHANGE_ReserveOpenResult rs = {
    141     .hr.reply = j,
    142     .hr.http_status = MHD_HTTP_OK,
    143   };
    144   struct GNUNET_JSON_Specification spec[] = {
    145     TALER_JSON_spec_amount_any ("open_cost",
    146                                 &rs.details.ok.open_cost),
    147     GNUNET_JSON_spec_timestamp ("reserve_expiration",
    148                                 &rs.details.ok.expiration_time),
    149     GNUNET_JSON_spec_end ()
    150   };
    151 
    152   if (GNUNET_OK !=
    153       GNUNET_JSON_parse (j,
    154                          spec,
    155                          NULL,
    156                          NULL))
    157   {
    158     GNUNET_break_op (0);
    159     return GNUNET_SYSERR;
    160   }
    161   roh->cb (roh->cb_cls,
    162            &rs);
    163   roh->cb = NULL;
    164   GNUNET_JSON_parse_free (spec);
    165   return GNUNET_OK;
    166 }
    167 
    168 
    169 /**
    170  * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON
    171  * response.
    172  *
    173  * @param roh handle of the request
    174  * @param j JSON response
    175  * @return #GNUNET_OK on success
    176  */
    177 static enum GNUNET_GenericReturnValue
    178 handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
    179                          const json_t *j)
    180 {
    181   struct TALER_EXCHANGE_ReserveOpenResult rs = {
    182     .hr.reply = j,
    183     .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED,
    184   };
    185   struct GNUNET_JSON_Specification spec[] = {
    186     TALER_JSON_spec_amount_any ("open_cost",
    187                                 &rs.details.payment_required.open_cost),
    188     GNUNET_JSON_spec_timestamp ("reserve_expiration",
    189                                 &rs.details.payment_required.expiration_time),
    190     GNUNET_JSON_spec_end ()
    191   };
    192 
    193   if (GNUNET_OK !=
    194       GNUNET_JSON_parse (j,
    195                          spec,
    196                          NULL,
    197                          NULL))
    198   {
    199     GNUNET_break_op (0);
    200     return GNUNET_SYSERR;
    201   }
    202   roh->cb (roh->cb_cls,
    203            &rs);
    204   roh->cb = NULL;
    205   GNUNET_JSON_parse_free (spec);
    206   return GNUNET_OK;
    207 }
    208 
    209 
    210 /**
    211  * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON
    212  * response.
    213  *
    214  * @param roh handle of the request
    215  * @param j JSON response
    216  * @return #GNUNET_OK on success
    217  */
    218 static enum GNUNET_GenericReturnValue
    219 handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
    220                           const json_t *j)
    221 {
    222   struct TALER_EXCHANGE_ReserveOpenResult rs = {
    223     .hr.reply = j,
    224     .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
    225   };
    226   struct GNUNET_JSON_Specification spec[] = {
    227     GNUNET_JSON_spec_fixed_auto (
    228       "h_payto",
    229       &rs.details.unavailable_for_legal_reasons.h_payto),
    230     GNUNET_JSON_spec_uint64 (
    231       "requirement_row",
    232       &rs.details.unavailable_for_legal_reasons.requirement_row),
    233     GNUNET_JSON_spec_end ()
    234   };
    235 
    236   if (GNUNET_OK !=
    237       GNUNET_JSON_parse (j,
    238                          spec,
    239                          NULL,
    240                          NULL))
    241   {
    242     GNUNET_break_op (0);
    243     return GNUNET_SYSERR;
    244   }
    245   roh->cb (roh->cb_cls,
    246            &rs);
    247   roh->cb = NULL;
    248   GNUNET_JSON_parse_free (spec);
    249   return GNUNET_OK;
    250 }
    251 
    252 
    253 /**
    254  * Function called when we're done processing the
    255  * HTTP /reserves/$RID/open request.
    256  *
    257  * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle`
    258  * @param response_code HTTP response code, 0 on error
    259  * @param response parsed JSON result, NULL on error
    260  */
    261 static void
    262 handle_reserves_open_finished (void *cls,
    263                                long response_code,
    264                                const void *response)
    265 {
    266   struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls;
    267   const json_t *j = response;
    268   struct TALER_EXCHANGE_ReserveOpenResult rs = {
    269     .hr.reply = j,
    270     .hr.http_status = (unsigned int) response_code
    271   };
    272 
    273   roh->job = NULL;
    274   switch (response_code)
    275   {
    276   case 0:
    277     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    278     break;
    279   case MHD_HTTP_OK:
    280     if (GNUNET_OK !=
    281         handle_reserves_open_ok (roh,
    282                                  j))
    283     {
    284       GNUNET_break_op (0);
    285       rs.hr.http_status = 0;
    286       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    287     }
    288     break;
    289   case MHD_HTTP_BAD_REQUEST:
    290     /* This should never happen, either us or the exchange is buggy
    291        (or API version conflict); just pass JSON reply to the application */
    292     GNUNET_break (0);
    293     json_dumpf (j,
    294                 stderr,
    295                 JSON_INDENT (2));
    296     rs.hr.ec = TALER_JSON_get_error_code (j);
    297     rs.hr.hint = TALER_JSON_get_error_hint (j);
    298     break;
    299   case MHD_HTTP_PAYMENT_REQUIRED:
    300     if (GNUNET_OK !=
    301         handle_reserves_open_pr (roh,
    302                                  j))
    303     {
    304       GNUNET_break_op (0);
    305       rs.hr.http_status = 0;
    306       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    307     }
    308     break;
    309   case MHD_HTTP_FORBIDDEN:
    310     /* This should never happen, either us or the exchange is buggy
    311        (or API version conflict); just pass JSON reply to the application */
    312     GNUNET_break (0);
    313     rs.hr.ec = TALER_JSON_get_error_code (j);
    314     rs.hr.hint = TALER_JSON_get_error_hint (j);
    315     break;
    316   case MHD_HTTP_NOT_FOUND:
    317     /* Nothing really to verify, this should never
    318        happen, we should pass the JSON reply to the application */
    319     rs.hr.ec = TALER_JSON_get_error_code (j);
    320     rs.hr.hint = TALER_JSON_get_error_hint (j);
    321     break;
    322   case MHD_HTTP_CONFLICT:
    323     {
    324       const struct CoinData *cd = NULL;
    325       struct GNUNET_JSON_Specification spec[] = {
    326         GNUNET_JSON_spec_fixed_auto ("coin_pub",
    327                                      &rs.details.conflict.coin_pub),
    328         GNUNET_JSON_spec_end ()
    329       };
    330 
    331       if (GNUNET_OK !=
    332           GNUNET_JSON_parse (j,
    333                              spec,
    334                              NULL,
    335                              NULL))
    336       {
    337         GNUNET_break_op (0);
    338         rs.hr.http_status = 0;
    339         rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    340         break;
    341       }
    342       for (unsigned int i = 0; i<roh->num_coins; i++)
    343       {
    344         const struct CoinData *cdi = &roh->coins[i];
    345 
    346         if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub,
    347                                 &cdi->coin_pub))
    348         {
    349           cd = cdi;
    350           break;
    351         }
    352       }
    353       if (NULL == cd)
    354       {
    355         GNUNET_break_op (0);
    356         rs.hr.http_status = 0;
    357         rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    358         break;
    359       }
    360       rs.hr.ec = TALER_JSON_get_error_code (j);
    361       rs.hr.hint = TALER_JSON_get_error_hint (j);
    362       break;
    363     }
    364   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    365     if (GNUNET_OK !=
    366         handle_reserves_open_kyc (roh,
    367                                   j))
    368     {
    369       GNUNET_break_op (0);
    370       rs.hr.http_status = 0;
    371       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    372     }
    373     break;
    374   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    375     /* Server had an internal issue; we should retry, but this API
    376        leaves this to the application */
    377     rs.hr.ec = TALER_JSON_get_error_code (j);
    378     rs.hr.hint = TALER_JSON_get_error_hint (j);
    379     break;
    380   default:
    381     /* unexpected response code */
    382     GNUNET_break_op (0);
    383     rs.hr.ec = TALER_JSON_get_error_code (j);
    384     rs.hr.hint = TALER_JSON_get_error_hint (j);
    385     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    386                 "Unexpected response code %u/%d for reserves open\n",
    387                 (unsigned int) response_code,
    388                 (int) rs.hr.ec);
    389     break;
    390   }
    391   if (NULL != roh->cb)
    392   {
    393     roh->cb (roh->cb_cls,
    394              &rs);
    395     roh->cb = NULL;
    396   }
    397   TALER_EXCHANGE_reserves_open_cancel (roh);
    398 }
    399 
    400 
    401 struct TALER_EXCHANGE_ReservesOpenHandle *
    402 TALER_EXCHANGE_reserves_open (
    403   struct GNUNET_CURL_Context *ctx,
    404   const char *url,
    405   struct TALER_EXCHANGE_Keys *keys,
    406   const struct TALER_ReservePrivateKeyP *reserve_priv,
    407   const struct TALER_Amount *reserve_contribution,
    408   unsigned int coin_payments_length,
    409   const struct TALER_EXCHANGE_PurseDeposit coin_payments[
    410     static coin_payments_length],
    411   struct GNUNET_TIME_Timestamp expiration_time,
    412   uint32_t min_purses,
    413   TALER_EXCHANGE_ReservesOpenCallback cb,
    414   void *cb_cls)
    415 {
    416   struct TALER_EXCHANGE_ReservesOpenHandle *roh;
    417   CURL *eh;
    418   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
    419   json_t *cpa;
    420 
    421   roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle);
    422   roh->cb = cb;
    423   roh->cb_cls = cb_cls;
    424   roh->ts = GNUNET_TIME_timestamp_get ();
    425   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
    426                                       &roh->reserve_pub.eddsa_pub);
    427   {
    428     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
    429     char *end;
    430 
    431     end = GNUNET_STRINGS_data_to_string (
    432       &roh->reserve_pub,
    433       sizeof (roh->reserve_pub),
    434       pub_str,
    435       sizeof (pub_str));
    436     *end = '\0';
    437     GNUNET_snprintf (arg_str,
    438                      sizeof (arg_str),
    439                      "reserves/%s/open",
    440                      pub_str);
    441   }
    442   roh->url = TALER_url_join (url,
    443                              arg_str,
    444                              NULL);
    445   if (NULL == roh->url)
    446   {
    447     GNUNET_free (roh);
    448     return NULL;
    449   }
    450   eh = TALER_EXCHANGE_curl_easy_get_ (roh->url);
    451   if (NULL == eh)
    452   {
    453     GNUNET_break (0);
    454     GNUNET_free (roh->url);
    455     GNUNET_free (roh);
    456     return NULL;
    457   }
    458   TALER_wallet_reserve_open_sign (reserve_contribution,
    459                                   roh->ts,
    460                                   expiration_time,
    461                                   min_purses,
    462                                   reserve_priv,
    463                                   &roh->reserve_sig);
    464   roh->coins = GNUNET_new_array (coin_payments_length,
    465                                  struct CoinData);
    466   cpa = json_array ();
    467   GNUNET_assert (NULL != cpa);
    468   for (unsigned int i = 0; i<coin_payments_length; i++)
    469   {
    470     const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i];
    471     const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof;
    472     struct TALER_AgeCommitmentHashP ahac;
    473     struct TALER_AgeCommitmentHashP *achp = NULL;
    474     struct CoinData *cd = &roh->coins[i];
    475     json_t *cp;
    476 
    477     cd->contribution = pd->amount;
    478     cd->h_denom_pub = pd->h_denom_pub;
    479     if (NULL != acp)
    480     {
    481       TALER_age_commitment_hash (&acp->commitment,
    482                                  &ahac);
    483       achp = &ahac;
    484     }
    485     TALER_wallet_reserve_open_deposit_sign (&pd->amount,
    486                                             &roh->reserve_sig,
    487                                             &pd->coin_priv,
    488                                             &cd->coin_sig);
    489     GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv,
    490                                         &cd->coin_pub.eddsa_pub);
    491 
    492     cp = GNUNET_JSON_PACK (
    493       GNUNET_JSON_pack_allow_null (
    494         GNUNET_JSON_pack_data_auto ("h_age_commitment",
    495                                     achp)),
    496       TALER_JSON_pack_amount ("amount",
    497                               &pd->amount),
    498       GNUNET_JSON_pack_data_auto ("denom_pub_hash",
    499                                   &pd->h_denom_pub),
    500       TALER_JSON_pack_denom_sig ("ub_sig",
    501                                  &pd->denom_sig),
    502       GNUNET_JSON_pack_data_auto ("coin_pub",
    503                                   &cd->coin_pub),
    504       GNUNET_JSON_pack_data_auto ("coin_sig",
    505                                   &cd->coin_sig));
    506     GNUNET_assert (0 ==
    507                    json_array_append_new (cpa,
    508                                           cp));
    509   }
    510   {
    511     json_t *open_obj = GNUNET_JSON_PACK (
    512       GNUNET_JSON_pack_timestamp ("request_timestamp",
    513                                   roh->ts),
    514       GNUNET_JSON_pack_timestamp ("reserve_expiration",
    515                                   expiration_time),
    516       GNUNET_JSON_pack_array_steal ("payments",
    517                                     cpa),
    518       TALER_JSON_pack_amount ("reserve_payment",
    519                               reserve_contribution),
    520       GNUNET_JSON_pack_uint64 ("purse_limit",
    521                                min_purses),
    522       GNUNET_JSON_pack_data_auto ("reserve_sig",
    523                                   &roh->reserve_sig));
    524 
    525     if (GNUNET_OK !=
    526         TALER_curl_easy_post (&roh->post_ctx,
    527                               eh,
    528                               open_obj))
    529     {
    530       GNUNET_break (0);
    531       curl_easy_cleanup (eh);
    532       json_decref (open_obj);
    533       GNUNET_free (roh->coins);
    534       GNUNET_free (roh->url);
    535       GNUNET_free (roh);
    536       return NULL;
    537     }
    538     json_decref (open_obj);
    539   }
    540   roh->keys = TALER_EXCHANGE_keys_incref (keys);
    541   roh->job = GNUNET_CURL_job_add2 (ctx,
    542                                    eh,
    543                                    roh->post_ctx.headers,
    544                                    &handle_reserves_open_finished,
    545                                    roh);
    546   return roh;
    547 }
    548 
    549 
    550 void
    551 TALER_EXCHANGE_reserves_open_cancel (
    552   struct TALER_EXCHANGE_ReservesOpenHandle *roh)
    553 {
    554   if (NULL != roh->job)
    555   {
    556     GNUNET_CURL_job_cancel (roh->job);
    557     roh->job = NULL;
    558   }
    559   TALER_curl_easy_post_finished (&roh->post_ctx);
    560   GNUNET_free (roh->coins);
    561   GNUNET_free (roh->url);
    562   TALER_EXCHANGE_keys_decref (roh->keys);
    563   GNUNET_free (roh);
    564 }
    565 
    566 
    567 /* end of exchange_api_reserves_open.c */