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


      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  * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON
    217  * response.
    218  *
    219  * @param proh handle of the request
    220  * @param j JSON response
    221  * @return #GNUNET_OK on success
    222  */
    223 static enum GNUNET_GenericReturnValue
    224 handle_reserves_open_kyc (struct TALER_EXCHANGE_PostReservesOpenHandle *proh,
    225                           const json_t *j)
    226 {
    227   struct TALER_EXCHANGE_PostReservesOpenResponse rs = {
    228     .hr.reply = j,
    229     .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
    230   };
    231   struct GNUNET_JSON_Specification spec[] = {
    232     GNUNET_JSON_spec_fixed_auto (
    233       "h_payto",
    234       &rs.details.unavailable_for_legal_reasons.h_payto),
    235     GNUNET_JSON_spec_uint64 (
    236       "requirement_row",
    237       &rs.details.unavailable_for_legal_reasons.requirement_row),
    238     GNUNET_JSON_spec_end ()
    239   };
    240 
    241   if (GNUNET_OK !=
    242       GNUNET_JSON_parse (j,
    243                          spec,
    244                          NULL,
    245                          NULL))
    246   {
    247     GNUNET_break_op (0);
    248     return GNUNET_SYSERR;
    249   }
    250   proh->cb (proh->cb_cls,
    251             &rs);
    252   proh->cb = NULL;
    253   GNUNET_JSON_parse_free (spec);
    254   return GNUNET_OK;
    255 }
    256 
    257 
    258 /**
    259  * Function called when we're done processing the
    260  * HTTP /reserves/$RID/open request.
    261  *
    262  * @param cls the `struct TALER_EXCHANGE_PostReservesOpenHandle`
    263  * @param response_code HTTP response code, 0 on error
    264  * @param response parsed JSON result, NULL on error
    265  */
    266 static void
    267 handle_reserves_open_finished (void *cls,
    268                                long response_code,
    269                                const void *response)
    270 {
    271   struct TALER_EXCHANGE_PostReservesOpenHandle *proh = cls;
    272   const json_t *j = response;
    273   struct TALER_EXCHANGE_PostReservesOpenResponse rs = {
    274     .hr.reply = j,
    275     .hr.http_status = (unsigned int) response_code
    276   };
    277 
    278   proh->job = NULL;
    279   switch (response_code)
    280   {
    281   case 0:
    282     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    283     break;
    284   case MHD_HTTP_OK:
    285     if (GNUNET_OK !=
    286         handle_reserves_open_ok (proh,
    287                                  j))
    288     {
    289       GNUNET_break_op (0);
    290       rs.hr.http_status = 0;
    291       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    292     }
    293     break;
    294   case MHD_HTTP_BAD_REQUEST:
    295     /* This should never happen, either us or the exchange is buggy
    296        (or API version conflict); just pass JSON reply to the application */
    297     GNUNET_break (0);
    298     json_dumpf (j,
    299                 stderr,
    300                 JSON_INDENT (2));
    301     rs.hr.ec = TALER_JSON_get_error_code (j);
    302     rs.hr.hint = TALER_JSON_get_error_hint (j);
    303     break;
    304   case MHD_HTTP_PAYMENT_REQUIRED:
    305     if (GNUNET_OK !=
    306         handle_reserves_open_pr (proh,
    307                                  j))
    308     {
    309       GNUNET_break_op (0);
    310       rs.hr.http_status = 0;
    311       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    312     }
    313     break;
    314   case MHD_HTTP_FORBIDDEN:
    315     /* This should never happen, either us or the exchange is buggy
    316        (or API version conflict); just pass JSON reply to the application */
    317     GNUNET_break (0);
    318     rs.hr.ec = TALER_JSON_get_error_code (j);
    319     rs.hr.hint = TALER_JSON_get_error_hint (j);
    320     break;
    321   case MHD_HTTP_NOT_FOUND:
    322     /* Nothing really to verify, this should never
    323        happen, we should pass the JSON reply to the application */
    324     rs.hr.ec = TALER_JSON_get_error_code (j);
    325     rs.hr.hint = TALER_JSON_get_error_hint (j);
    326     break;
    327   case MHD_HTTP_CONFLICT:
    328     {
    329       const struct CoinData *cd = NULL;
    330       struct GNUNET_JSON_Specification spec[] = {
    331         GNUNET_JSON_spec_fixed_auto ("coin_pub",
    332                                      &rs.details.conflict.coin_pub),
    333         GNUNET_JSON_spec_end ()
    334       };
    335 
    336       if (GNUNET_OK !=
    337           GNUNET_JSON_parse (j,
    338                              spec,
    339                              NULL,
    340                              NULL))
    341       {
    342         GNUNET_break_op (0);
    343         rs.hr.http_status = 0;
    344         rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    345         break;
    346       }
    347       for (unsigned int i = 0; i < proh->num_coins; i++)
    348       {
    349         const struct CoinData *cdi = &proh->coins[i];
    350 
    351         if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub,
    352                                 &cdi->coin_pub))
    353         {
    354           cd = cdi;
    355           break;
    356         }
    357       }
    358       if (NULL == cd)
    359       {
    360         GNUNET_break_op (0);
    361         rs.hr.http_status = 0;
    362         rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    363         break;
    364       }
    365       rs.hr.ec = TALER_JSON_get_error_code (j);
    366       rs.hr.hint = TALER_JSON_get_error_hint (j);
    367       break;
    368     }
    369   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    370     if (GNUNET_OK !=
    371         handle_reserves_open_kyc (proh,
    372                                   j))
    373     {
    374       GNUNET_break_op (0);
    375       rs.hr.http_status = 0;
    376       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    377     }
    378     break;
    379   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    380     /* Server had an internal issue; we should retry, but this API
    381        leaves this to the application */
    382     rs.hr.ec = TALER_JSON_get_error_code (j);
    383     rs.hr.hint = TALER_JSON_get_error_hint (j);
    384     break;
    385   default:
    386     /* unexpected response code */
    387     GNUNET_break_op (0);
    388     rs.hr.ec = TALER_JSON_get_error_code (j);
    389     rs.hr.hint = TALER_JSON_get_error_hint (j);
    390     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    391                 "Unexpected response code %u/%d for reserves open\n",
    392                 (unsigned int) response_code,
    393                 (int) rs.hr.ec);
    394     break;
    395   }
    396   if (NULL != proh->cb)
    397   {
    398     proh->cb (proh->cb_cls,
    399               &rs);
    400     proh->cb = NULL;
    401   }
    402   TALER_EXCHANGE_post_reserves_open_cancel (proh);
    403 }
    404 
    405 
    406 struct TALER_EXCHANGE_PostReservesOpenHandle *
    407 TALER_EXCHANGE_post_reserves_open_create (
    408   struct GNUNET_CURL_Context *ctx,
    409   const char *url,
    410   struct TALER_EXCHANGE_Keys *keys,
    411   const struct TALER_ReservePrivateKeyP *reserve_priv,
    412   const struct TALER_Amount *reserve_contribution,
    413   unsigned int coin_payments_length,
    414   const struct TALER_EXCHANGE_PurseDeposit coin_payments[
    415     static coin_payments_length],
    416   struct GNUNET_TIME_Timestamp expiration_time,
    417   uint32_t min_purses)
    418 {
    419   struct TALER_EXCHANGE_PostReservesOpenHandle *proh;
    420   struct GNUNET_TIME_Timestamp ts;
    421   struct TALER_ReserveSignatureP reserve_sig;
    422   json_t *cpa;
    423 
    424   proh = GNUNET_new (struct TALER_EXCHANGE_PostReservesOpenHandle);
    425   proh->ctx = ctx;
    426   proh->base_url = GNUNET_strdup (url);
    427   proh->keys = TALER_EXCHANGE_keys_incref (keys);
    428   ts = GNUNET_TIME_timestamp_get ();
    429   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
    430                                       &proh->reserve_pub.eddsa_pub);
    431   TALER_wallet_reserve_open_sign (reserve_contribution,
    432                                   ts,
    433                                   expiration_time,
    434                                   min_purses,
    435                                   reserve_priv,
    436                                   &reserve_sig);
    437   proh->coins = GNUNET_new_array (coin_payments_length,
    438                                   struct CoinData);
    439   proh->num_coins = coin_payments_length;
    440   cpa = json_array ();
    441   GNUNET_assert (NULL != cpa);
    442   for (unsigned int i = 0; i < coin_payments_length; i++)
    443   {
    444     const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i];
    445     const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof;
    446     struct TALER_AgeCommitmentHashP ahac;
    447     struct TALER_AgeCommitmentHashP *achp = NULL;
    448     struct CoinData *cd = &proh->coins[i];
    449     json_t *cp;
    450 
    451     cd->contribution = pd->amount;
    452     cd->h_denom_pub = pd->h_denom_pub;
    453     if (NULL != acp)
    454     {
    455       TALER_age_commitment_hash (&acp->commitment,
    456                                  &ahac);
    457       achp = &ahac;
    458     }
    459     TALER_wallet_reserve_open_deposit_sign (&pd->amount,
    460                                             &reserve_sig,
    461                                             &pd->coin_priv,
    462                                             &cd->coin_sig);
    463     GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv,
    464                                         &cd->coin_pub.eddsa_pub);
    465 
    466     cp = GNUNET_JSON_PACK (
    467       GNUNET_JSON_pack_allow_null (
    468         GNUNET_JSON_pack_data_auto ("h_age_commitment",
    469                                     achp)),
    470       TALER_JSON_pack_amount ("amount",
    471                               &pd->amount),
    472       GNUNET_JSON_pack_data_auto ("denom_pub_hash",
    473                                   &pd->h_denom_pub),
    474       TALER_JSON_pack_denom_sig ("ub_sig",
    475                                  &pd->denom_sig),
    476       GNUNET_JSON_pack_data_auto ("coin_pub",
    477                                   &cd->coin_pub),
    478       GNUNET_JSON_pack_data_auto ("coin_sig",
    479                                   &cd->coin_sig));
    480     GNUNET_assert (0 ==
    481                    json_array_append_new (cpa,
    482                                           cp));
    483   }
    484   proh->body = GNUNET_JSON_PACK (
    485     GNUNET_JSON_pack_timestamp ("request_timestamp",
    486                                 ts),
    487     GNUNET_JSON_pack_timestamp ("reserve_expiration",
    488                                 expiration_time),
    489     GNUNET_JSON_pack_array_steal ("payments",
    490                                   cpa),
    491     TALER_JSON_pack_amount ("reserve_payment",
    492                             reserve_contribution),
    493     GNUNET_JSON_pack_uint64 ("purse_limit",
    494                              min_purses),
    495     GNUNET_JSON_pack_data_auto ("reserve_sig",
    496                                 &reserve_sig));
    497   if (NULL == proh->body)
    498   {
    499     GNUNET_break (0);
    500     GNUNET_free (proh->coins);
    501     GNUNET_free (proh->base_url);
    502     TALER_EXCHANGE_keys_decref (proh->keys);
    503     GNUNET_free (proh);
    504     return NULL;
    505   }
    506   return proh;
    507 }
    508 
    509 
    510 enum TALER_ErrorCode
    511 TALER_EXCHANGE_post_reserves_open_start (
    512   struct TALER_EXCHANGE_PostReservesOpenHandle *proh,
    513   TALER_EXCHANGE_PostReservesOpenCallback cb,
    514   TALER_EXCHANGE_POST_RESERVES_OPEN_RESULT_CLOSURE *cb_cls)
    515 {
    516   CURL *eh;
    517   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
    518 
    519   proh->cb = cb;
    520   proh->cb_cls = cb_cls;
    521   {
    522     char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
    523     char *end;
    524 
    525     end = GNUNET_STRINGS_data_to_string (
    526       &proh->reserve_pub,
    527       sizeof (proh->reserve_pub),
    528       pub_str,
    529       sizeof (pub_str));
    530     *end = '\0';
    531     GNUNET_snprintf (arg_str,
    532                      sizeof (arg_str),
    533                      "reserves/%s/open",
    534                      pub_str);
    535   }
    536   proh->url = TALER_url_join (proh->base_url,
    537                               arg_str,
    538                               NULL);
    539   if (NULL == proh->url)
    540     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    541   eh = TALER_EXCHANGE_curl_easy_get_ (proh->url);
    542   if ( (NULL == eh) ||
    543        (GNUNET_OK !=
    544         TALER_curl_easy_post (&proh->post_ctx,
    545                               eh,
    546                               proh->body)) )
    547   {
    548     GNUNET_break (0);
    549     if (NULL != eh)
    550       curl_easy_cleanup (eh);
    551     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    552   }
    553   proh->job = GNUNET_CURL_job_add2 (proh->ctx,
    554                                     eh,
    555                                     proh->post_ctx.headers,
    556                                     &handle_reserves_open_finished,
    557                                     proh);
    558   if (NULL == proh->job)
    559     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    560   return TALER_EC_NONE;
    561 }
    562 
    563 
    564 void
    565 TALER_EXCHANGE_post_reserves_open_cancel (
    566   struct TALER_EXCHANGE_PostReservesOpenHandle *proh)
    567 {
    568   if (NULL != proh->job)
    569   {
    570     GNUNET_CURL_job_cancel (proh->job);
    571     proh->job = NULL;
    572   }
    573   TALER_curl_easy_post_finished (&proh->post_ctx);
    574   json_decref (proh->body);
    575   GNUNET_free (proh->coins);
    576   GNUNET_free (proh->url);
    577   GNUNET_free (proh->base_url);
    578   TALER_EXCHANGE_keys_decref (proh->keys);
    579   GNUNET_free (proh);
    580 }
    581 
    582 
    583 /* end of exchange_api_post-reserves-RESERVE_PUB-open.c */