exchange

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

exchange_api_purse_create_with_merge.c (18008B)


      1 /*
      2    This file is part of TALER
      3    Copyright (C) 2022-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_purse_create_with_merge.c
     19  * @brief Implementation of the client to create a
     20  *        purse for an account
     21  * @author Christian Grothoff
     22  */
     23 #include "taler/platform.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_json_lib.h"
     30 #include "taler/taler_exchange_service.h"
     31 #include "exchange_api_handle.h"
     32 #include "exchange_api_common.h"
     33 #include "taler/taler_signatures.h"
     34 #include "exchange_api_curl_defaults.h"
     35 
     36 
     37 /**
     38  * @brief A purse create with merge handle
     39  */
     40 struct TALER_EXCHANGE_PurseCreateMergeHandle
     41 {
     42 
     43   /**
     44    * The keys of the exchange this request handle will use
     45    */
     46   struct TALER_EXCHANGE_Keys *keys;
     47 
     48   /**
     49    * The url for this request.
     50    */
     51   char *url;
     52 
     53   /**
     54    * The exchange base URL.
     55    */
     56   char *exchange_url;
     57 
     58   /**
     59    * Context for #TEH_curl_easy_post(). Keeps the data that must
     60    * persist for Curl to make the upload.
     61    */
     62   struct TALER_CURL_PostContext ctx;
     63 
     64   /**
     65    * Handle for the request.
     66    */
     67   struct GNUNET_CURL_Job *job;
     68 
     69   /**
     70    * Function to call with the result.
     71    */
     72   TALER_EXCHANGE_PurseCreateMergeCallback cb;
     73 
     74   /**
     75    * Closure for @a cb.
     76    */
     77   void *cb_cls;
     78 
     79   /**
     80    * The encrypted contract (if any).
     81    */
     82   struct TALER_EncryptedContract econtract;
     83 
     84   /**
     85    * Expected value in the purse after fees.
     86    */
     87   struct TALER_Amount purse_value_after_fees;
     88 
     89   /**
     90    * Public key of the reserve public key.
     91    */
     92   struct TALER_ReservePublicKeyP reserve_pub;
     93 
     94   /**
     95    * Reserve signature affirming our merge.
     96    */
     97   struct TALER_ReserveSignatureP reserve_sig;
     98 
     99   /**
    100    * Merge capability key.
    101    */
    102   struct TALER_PurseMergePublicKeyP merge_pub;
    103 
    104   /**
    105    * Our merge signature (if any).
    106    */
    107   struct TALER_PurseMergeSignatureP merge_sig;
    108 
    109   /**
    110    * Public key of the purse.
    111    */
    112   struct TALER_PurseContractPublicKeyP purse_pub;
    113 
    114   /**
    115    * Request data we signed over.
    116    */
    117   struct TALER_PurseContractSignatureP purse_sig;
    118 
    119   /**
    120    * Hash over the purse's contrac terms.
    121    */
    122   struct TALER_PrivateContractHashP h_contract_terms;
    123 
    124   /**
    125    * When does the purse expire.
    126    */
    127   struct GNUNET_TIME_Timestamp purse_expiration;
    128 
    129   /**
    130    * When does the purse get merged/created.
    131    */
    132   struct GNUNET_TIME_Timestamp merge_timestamp;
    133 };
    134 
    135 
    136 /**
    137  * Function called when we're done processing the
    138  * HTTP /reserves/$RID/purse request.
    139  *
    140  * @param cls the `struct TALER_EXCHANGE_PurseCreateMergeHandle`
    141  * @param response_code HTTP response code, 0 on error
    142  * @param response parsed JSON result, NULL on error
    143  */
    144 static void
    145 handle_purse_create_with_merge_finished (void *cls,
    146                                          long response_code,
    147                                          const void *response)
    148 {
    149   struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm = cls;
    150   const json_t *j = response;
    151   struct TALER_EXCHANGE_PurseCreateMergeResponse dr = {
    152     .hr.reply = j,
    153     .hr.http_status = (unsigned int) response_code,
    154     .reserve_sig = &pcm->reserve_sig
    155   };
    156 
    157   pcm->job = NULL;
    158   switch (response_code)
    159   {
    160   case 0:
    161     dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    162     break;
    163   case MHD_HTTP_OK:
    164     {
    165       struct GNUNET_TIME_Timestamp etime;
    166       struct TALER_Amount total_deposited;
    167       struct TALER_ExchangeSignatureP exchange_sig;
    168       struct TALER_ExchangePublicKeyP exchange_pub;
    169       struct GNUNET_JSON_Specification spec[] = {
    170         TALER_JSON_spec_amount_any ("total_deposited",
    171                                     &total_deposited),
    172         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    173                                      &exchange_sig),
    174         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    175                                      &exchange_pub),
    176         GNUNET_JSON_spec_timestamp ("exchange_timestamp",
    177                                     &etime),
    178         GNUNET_JSON_spec_end ()
    179       };
    180 
    181       if (GNUNET_OK !=
    182           GNUNET_JSON_parse (j,
    183                              spec,
    184                              NULL, NULL))
    185       {
    186         GNUNET_break_op (0);
    187         dr.hr.http_status = 0;
    188         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    189         break;
    190       }
    191       if (GNUNET_OK !=
    192           TALER_EXCHANGE_test_signing_key (pcm->keys,
    193                                            &exchange_pub))
    194       {
    195         GNUNET_break_op (0);
    196         dr.hr.http_status = 0;
    197         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    198         break;
    199       }
    200       if (GNUNET_OK !=
    201           TALER_exchange_online_purse_created_verify (
    202             etime,
    203             pcm->purse_expiration,
    204             &pcm->purse_value_after_fees,
    205             &total_deposited,
    206             &pcm->purse_pub,
    207             &pcm->h_contract_terms,
    208             &exchange_pub,
    209             &exchange_sig))
    210       {
    211         GNUNET_break_op (0);
    212         dr.hr.http_status = 0;
    213         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    214         break;
    215       }
    216     }
    217     break;
    218   case MHD_HTTP_BAD_REQUEST:
    219     /* This should never happen, either us or the exchange is buggy
    220        (or API version conflict); just pass JSON reply to the application */
    221     dr.hr.ec = TALER_JSON_get_error_code (j);
    222     dr.hr.hint = TALER_JSON_get_error_hint (j);
    223     break;
    224   case MHD_HTTP_FORBIDDEN:
    225     dr.hr.ec = TALER_JSON_get_error_code (j);
    226     dr.hr.hint = TALER_JSON_get_error_hint (j);
    227     /* Nothing really to verify, exchange says one of the signatures is
    228        invalid; as we checked them, this should never happen, we
    229        should pass the JSON reply to the application */
    230     break;
    231   case MHD_HTTP_NOT_FOUND:
    232     dr.hr.ec = TALER_JSON_get_error_code (j);
    233     dr.hr.hint = TALER_JSON_get_error_hint (j);
    234     /* Nothing really to verify, this should never
    235        happen, we should pass the JSON reply to the application */
    236     break;
    237   case MHD_HTTP_CONFLICT:
    238     dr.hr.ec = TALER_JSON_get_error_code (j);
    239     switch (dr.hr.ec)
    240     {
    241     case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA:
    242       if (GNUNET_OK !=
    243           TALER_EXCHANGE_check_purse_create_conflict_ (
    244             &pcm->purse_sig,
    245             &pcm->purse_pub,
    246             j))
    247       {
    248         dr.hr.http_status = 0;
    249         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    250         break;
    251       }
    252       break;
    253     case TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA:
    254       if (GNUNET_OK !=
    255           TALER_EXCHANGE_check_purse_merge_conflict_ (
    256             &pcm->merge_sig,
    257             &pcm->merge_pub,
    258             &pcm->purse_pub,
    259             pcm->exchange_url,
    260             j))
    261       {
    262         GNUNET_break_op (0);
    263         dr.hr.http_status = 0;
    264         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    265         break;
    266       }
    267       break;
    268     case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS:
    269       /* nothing to verify */
    270       break;
    271     case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
    272       if (GNUNET_OK !=
    273           TALER_EXCHANGE_check_purse_econtract_conflict_ (
    274             &pcm->econtract.econtract_sig,
    275             &pcm->purse_pub,
    276             j))
    277       {
    278         GNUNET_break_op (0);
    279         dr.hr.http_status = 0;
    280         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    281         break;
    282       }
    283       break;
    284     default:
    285       /* unexpected EC! */
    286       GNUNET_break_op (0);
    287       dr.hr.http_status = 0;
    288       dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    289       break;
    290     } /* end inner (EC) switch */
    291     break;
    292   case MHD_HTTP_GONE:
    293     /* could happen if denomination was revoked */
    294     /* Note: one might want to check /keys for revocation
    295        signature here, alas tricky in case our /keys
    296        is outdated => left to clients */
    297     dr.hr.ec = TALER_JSON_get_error_code (j);
    298     dr.hr.hint = TALER_JSON_get_error_hint (j);
    299     break;
    300   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    301     {
    302       struct GNUNET_JSON_Specification spec[] = {
    303         GNUNET_JSON_spec_uint64 (
    304           "requirement_row",
    305           &dr.details.unavailable_for_legal_reasons.requirement_row),
    306         GNUNET_JSON_spec_end ()
    307       };
    308 
    309       if (GNUNET_OK !=
    310           GNUNET_JSON_parse (j,
    311                              spec,
    312                              NULL, NULL))
    313       {
    314         GNUNET_break_op (0);
    315         dr.hr.http_status = 0;
    316         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    317         break;
    318       }
    319     }
    320     break;
    321   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    322     dr.hr.ec = TALER_JSON_get_error_code (j);
    323     dr.hr.hint = TALER_JSON_get_error_hint (j);
    324     /* Server had an internal issue; we should retry, but this API
    325        leaves this to the application */
    326     break;
    327   default:
    328     /* unexpected response code */
    329     dr.hr.ec = TALER_JSON_get_error_code (j);
    330     dr.hr.hint = TALER_JSON_get_error_hint (j);
    331     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    332                 "Unexpected response code %u/%d for exchange deposit\n",
    333                 (unsigned int) response_code,
    334                 dr.hr.ec);
    335     GNUNET_break_op (0);
    336     break;
    337   }
    338   pcm->cb (pcm->cb_cls,
    339            &dr);
    340   TALER_EXCHANGE_purse_create_with_merge_cancel (pcm);
    341 }
    342 
    343 
    344 struct TALER_EXCHANGE_PurseCreateMergeHandle *
    345 TALER_EXCHANGE_purse_create_with_merge (
    346   struct GNUNET_CURL_Context *ctx,
    347   const char *url,
    348   struct TALER_EXCHANGE_Keys *keys,
    349   const struct TALER_ReservePrivateKeyP *reserve_priv,
    350   const struct TALER_PurseContractPrivateKeyP *purse_priv,
    351   const struct TALER_PurseMergePrivateKeyP *merge_priv,
    352   const struct TALER_ContractDiffiePrivateP *contract_priv,
    353   const json_t *contract_terms,
    354   bool upload_contract,
    355   bool pay_for_purse,
    356   struct GNUNET_TIME_Timestamp merge_timestamp,
    357   TALER_EXCHANGE_PurseCreateMergeCallback cb,
    358   void *cb_cls)
    359 {
    360   struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm;
    361   json_t *create_with_merge_obj;
    362   CURL *eh;
    363   char arg_str[sizeof (pcm->reserve_pub) * 2 + 32];
    364   uint32_t min_age = 0;
    365   struct TALER_Amount purse_fee;
    366   enum TALER_WalletAccountMergeFlags flags;
    367 
    368   pcm = GNUNET_new (struct TALER_EXCHANGE_PurseCreateMergeHandle);
    369   pcm->cb = cb;
    370   pcm->cb_cls = cb_cls;
    371   if (GNUNET_OK !=
    372       TALER_JSON_contract_hash (contract_terms,
    373                                 &pcm->h_contract_terms))
    374   {
    375     GNUNET_break (0);
    376     GNUNET_free (pcm);
    377     return NULL;
    378   }
    379   pcm->merge_timestamp = merge_timestamp;
    380   GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
    381                                       &pcm->purse_pub.eddsa_pub);
    382   GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
    383                                       &pcm->reserve_pub.eddsa_pub);
    384   GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
    385                                       &pcm->merge_pub.eddsa_pub);
    386 
    387   {
    388     struct GNUNET_JSON_Specification spec[] = {
    389       TALER_JSON_spec_amount_any ("amount",
    390                                   &pcm->purse_value_after_fees),
    391       GNUNET_JSON_spec_mark_optional (
    392         GNUNET_JSON_spec_uint32 ("minimum_age",
    393                                  &min_age),
    394         NULL),
    395       GNUNET_JSON_spec_timestamp ("pay_deadline",
    396                                   &pcm->purse_expiration),
    397       GNUNET_JSON_spec_end ()
    398     };
    399 
    400     if (GNUNET_OK !=
    401         GNUNET_JSON_parse (contract_terms,
    402                            spec,
    403                            NULL, NULL))
    404     {
    405       GNUNET_break (0);
    406       GNUNET_free (pcm);
    407       return NULL;
    408     }
    409   }
    410   if (pay_for_purse)
    411   {
    412     const struct TALER_EXCHANGE_GlobalFee *gf;
    413 
    414     gf = TALER_EXCHANGE_get_global_fee (
    415       keys,
    416       GNUNET_TIME_timestamp_get ());
    417     purse_fee = gf->fees.purse;
    418     flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
    419   }
    420   else
    421   {
    422     GNUNET_assert (GNUNET_OK ==
    423                    TALER_amount_set_zero (pcm->purse_value_after_fees.currency,
    424                                           &purse_fee));
    425     flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
    426   }
    427 
    428   {
    429     char pub_str[sizeof (pcm->reserve_pub) * 2];
    430     char *end;
    431 
    432     end = GNUNET_STRINGS_data_to_string (
    433       &pcm->reserve_pub,
    434       sizeof (pcm->reserve_pub),
    435       pub_str,
    436       sizeof (pub_str));
    437     *end = '\0';
    438     GNUNET_snprintf (arg_str,
    439                      sizeof (arg_str),
    440                      "reserves/%s/purse",
    441                      pub_str);
    442   }
    443   pcm->url = TALER_url_join (url,
    444                              arg_str,
    445                              NULL);
    446   if (NULL == pcm->url)
    447   {
    448     GNUNET_break (0);
    449     GNUNET_free (pcm);
    450     return NULL;
    451   }
    452   TALER_wallet_purse_create_sign (pcm->purse_expiration,
    453                                   &pcm->h_contract_terms,
    454                                   &pcm->merge_pub,
    455                                   min_age,
    456                                   &pcm->purse_value_after_fees,
    457                                   purse_priv,
    458                                   &pcm->purse_sig);
    459   {
    460     struct TALER_NormalizedPayto payto_uri;
    461 
    462     payto_uri = TALER_reserve_make_payto (url,
    463                                           &pcm->reserve_pub);
    464     TALER_wallet_purse_merge_sign (payto_uri,
    465                                    merge_timestamp,
    466                                    &pcm->purse_pub,
    467                                    merge_priv,
    468                                    &pcm->merge_sig);
    469     GNUNET_free (payto_uri.normalized_payto);
    470   }
    471   TALER_wallet_account_merge_sign (merge_timestamp,
    472                                    &pcm->purse_pub,
    473                                    pcm->purse_expiration,
    474                                    &pcm->h_contract_terms,
    475                                    &pcm->purse_value_after_fees,
    476                                    &purse_fee,
    477                                    min_age,
    478                                    flags,
    479                                    reserve_priv,
    480                                    &pcm->reserve_sig);
    481   if (upload_contract)
    482   {
    483     TALER_CRYPTO_contract_encrypt_for_deposit (
    484       &pcm->purse_pub,
    485       contract_priv,
    486       contract_terms,
    487       &pcm->econtract.econtract,
    488       &pcm->econtract.econtract_size);
    489     GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
    490                                         &pcm->econtract.contract_pub.ecdhe_pub);
    491     TALER_wallet_econtract_upload_sign (
    492       pcm->econtract.econtract,
    493       pcm->econtract.econtract_size,
    494       &pcm->econtract.contract_pub,
    495       purse_priv,
    496       &pcm->econtract.econtract_sig);
    497   }
    498   create_with_merge_obj = GNUNET_JSON_PACK (
    499     TALER_JSON_pack_amount ("purse_value",
    500                             &pcm->purse_value_after_fees),
    501     GNUNET_JSON_pack_uint64 ("min_age",
    502                              min_age),
    503     GNUNET_JSON_pack_allow_null (
    504       TALER_JSON_pack_econtract ("econtract",
    505                                  upload_contract
    506                                  ? &pcm->econtract
    507                                  : NULL)),
    508     GNUNET_JSON_pack_allow_null (
    509       pay_for_purse
    510       ? TALER_JSON_pack_amount ("purse_fee",
    511                                 &purse_fee)
    512       : GNUNET_JSON_pack_string ("dummy2",
    513                                  NULL)),
    514     GNUNET_JSON_pack_data_auto ("merge_pub",
    515                                 &pcm->merge_pub),
    516     GNUNET_JSON_pack_data_auto ("merge_sig",
    517                                 &pcm->merge_sig),
    518     GNUNET_JSON_pack_data_auto ("reserve_sig",
    519                                 &pcm->reserve_sig),
    520     GNUNET_JSON_pack_data_auto ("purse_pub",
    521                                 &pcm->purse_pub),
    522     GNUNET_JSON_pack_data_auto ("purse_sig",
    523                                 &pcm->purse_sig),
    524     GNUNET_JSON_pack_data_auto ("h_contract_terms",
    525                                 &pcm->h_contract_terms),
    526     GNUNET_JSON_pack_timestamp ("merge_timestamp",
    527                                 merge_timestamp),
    528     GNUNET_JSON_pack_timestamp ("purse_expiration",
    529                                 pcm->purse_expiration));
    530   GNUNET_assert (NULL != create_with_merge_obj);
    531   eh = TALER_EXCHANGE_curl_easy_get_ (pcm->url);
    532   if ( (NULL == eh) ||
    533        (GNUNET_OK !=
    534         TALER_curl_easy_post (&pcm->ctx,
    535                               eh,
    536                               create_with_merge_obj)) )
    537   {
    538     GNUNET_break (0);
    539     if (NULL != eh)
    540       curl_easy_cleanup (eh);
    541     json_decref (create_with_merge_obj);
    542     GNUNET_free (pcm->econtract.econtract);
    543     GNUNET_free (pcm->url);
    544     GNUNET_free (pcm);
    545     return NULL;
    546   }
    547   json_decref (create_with_merge_obj);
    548   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    549               "URL for purse create_with_merge: `%s'\n",
    550               pcm->url);
    551   pcm->keys = TALER_EXCHANGE_keys_incref (keys);
    552   pcm->exchange_url = GNUNET_strdup (url);
    553   pcm->job = GNUNET_CURL_job_add2 (ctx,
    554                                    eh,
    555                                    pcm->ctx.headers,
    556                                    &handle_purse_create_with_merge_finished,
    557                                    pcm);
    558   return pcm;
    559 }
    560 
    561 
    562 void
    563 TALER_EXCHANGE_purse_create_with_merge_cancel (
    564   struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm)
    565 {
    566   if (NULL != pcm->job)
    567   {
    568     GNUNET_CURL_job_cancel (pcm->job);
    569     pcm->job = NULL;
    570   }
    571   GNUNET_free (pcm->url);
    572   GNUNET_free (pcm->exchange_url);
    573   TALER_curl_easy_post_finished (&pcm->ctx);
    574   TALER_EXCHANGE_keys_decref (pcm->keys);
    575   GNUNET_free (pcm->econtract.econtract);
    576   GNUNET_free (pcm);
    577 }
    578 
    579 
    580 /* end of exchange_api_purse_create_with_merge.c */