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-reserves-RESERVE_PUB-open.c (15412B)


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