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


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