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_deposit.c (20627B)


      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_deposit.c
     19  * @brief Implementation of the client to create a purse with
     20  *        an initial set of deposits (and a contract)
     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  * Information we track per deposited coin.
     39  */
     40 struct Deposit
     41 {
     42   /**
     43    * Coin's public key.
     44    */
     45   struct TALER_CoinSpendPublicKeyP coin_pub;
     46 
     47   /**
     48    * Signature made with the coin.
     49    */
     50   struct TALER_CoinSpendSignatureP coin_sig;
     51 
     52   /**
     53    * Coin's denomination.
     54    */
     55   struct TALER_DenominationHashP h_denom_pub;
     56 
     57   /**
     58    * Age restriction hash for the coin.
     59    */
     60   struct TALER_AgeCommitmentHashP ahac;
     61 
     62   /**
     63    * How much did we say the coin contributed.
     64    */
     65   struct TALER_Amount contribution;
     66 };
     67 
     68 
     69 /**
     70  * @brief A purse create with deposit handle
     71  */
     72 struct TALER_EXCHANGE_PurseCreateDepositHandle
     73 {
     74 
     75   /**
     76    * The keys of the exchange this request handle will use
     77    */
     78   struct TALER_EXCHANGE_Keys *keys;
     79 
     80   /**
     81    * The url for this request.
     82    */
     83   char *url;
     84 
     85   /**
     86    * The base URL of the exchange.
     87    */
     88   char *exchange_url;
     89 
     90   /**
     91    * Context for #TEH_curl_easy_post(). Keeps the data that must
     92    * persist for Curl to make the upload.
     93    */
     94   struct TALER_CURL_PostContext ctx;
     95 
     96   /**
     97    * Handle for the request.
     98    */
     99   struct GNUNET_CURL_Job *job;
    100 
    101   /**
    102    * Function to call with the result.
    103    */
    104   TALER_EXCHANGE_PurseCreateDepositCallback cb;
    105 
    106   /**
    107    * Closure for @a cb.
    108    */
    109   void *cb_cls;
    110 
    111   /**
    112    * Expected value in the purse after fees.
    113    */
    114   struct TALER_Amount purse_value_after_fees;
    115 
    116   /**
    117    * Our encrypted contract (if we had any).
    118    */
    119   struct TALER_EncryptedContract econtract;
    120 
    121   /**
    122    * Public key of the merge capability.
    123    */
    124   struct TALER_PurseMergePublicKeyP merge_pub;
    125 
    126   /**
    127    * Public key of the purse.
    128    */
    129   struct TALER_PurseContractPublicKeyP purse_pub;
    130 
    131   /**
    132    * Signature with the purse key on the request.
    133    */
    134   struct TALER_PurseContractSignatureP purse_sig;
    135 
    136   /**
    137    * Hash over the purse's contract terms.
    138    */
    139   struct TALER_PrivateContractHashP h_contract_terms;
    140 
    141   /**
    142    * When does the purse expire.
    143    */
    144   struct GNUNET_TIME_Timestamp purse_expiration;
    145 
    146   /**
    147    * Array of @e num_deposit deposits.
    148    */
    149   struct Deposit *deposits;
    150 
    151   /**
    152    * How many deposits did we make?
    153    */
    154   unsigned int num_deposits;
    155 
    156 };
    157 
    158 
    159 /**
    160  * Function called when we're done processing the
    161  * HTTP /deposit request.
    162  *
    163  * @param cls the `struct TALER_EXCHANGE_DepositHandle`
    164  * @param response_code HTTP response code, 0 on error
    165  * @param response parsed JSON result, NULL on error
    166  */
    167 static void
    168 handle_purse_create_deposit_finished (void *cls,
    169                                       long response_code,
    170                                       const void *response)
    171 {
    172   struct TALER_EXCHANGE_PurseCreateDepositHandle *pch = cls;
    173   const json_t *j = response;
    174   struct TALER_EXCHANGE_PurseCreateDepositResponse dr = {
    175     .hr.reply = j,
    176     .hr.http_status = (unsigned int) response_code
    177   };
    178   const struct TALER_EXCHANGE_Keys *keys = pch->keys;
    179 
    180   pch->job = NULL;
    181   switch (response_code)
    182   {
    183   case 0:
    184     dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    185     break;
    186   case MHD_HTTP_OK:
    187     {
    188       struct GNUNET_TIME_Timestamp etime;
    189       struct TALER_Amount total_deposited;
    190       struct TALER_ExchangeSignatureP exchange_sig;
    191       struct TALER_ExchangePublicKeyP exchange_pub;
    192       struct GNUNET_JSON_Specification spec[] = {
    193         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    194                                      &exchange_sig),
    195         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    196                                      &exchange_pub),
    197         GNUNET_JSON_spec_timestamp ("exchange_timestamp",
    198                                     &etime),
    199         TALER_JSON_spec_amount ("total_deposited",
    200                                 pch->purse_value_after_fees.currency,
    201                                 &total_deposited),
    202         GNUNET_JSON_spec_end ()
    203       };
    204 
    205       if (GNUNET_OK !=
    206           GNUNET_JSON_parse (j,
    207                              spec,
    208                              NULL, NULL))
    209       {
    210         GNUNET_break_op (0);
    211         dr.hr.http_status = 0;
    212         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    213         break;
    214       }
    215       if (GNUNET_OK !=
    216           TALER_EXCHANGE_test_signing_key (keys,
    217                                            &exchange_pub))
    218       {
    219         GNUNET_break_op (0);
    220         dr.hr.http_status = 0;
    221         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
    222         break;
    223       }
    224       if (GNUNET_OK !=
    225           TALER_exchange_online_purse_created_verify (
    226             etime,
    227             pch->purse_expiration,
    228             &pch->purse_value_after_fees,
    229             &total_deposited,
    230             &pch->purse_pub,
    231             &pch->h_contract_terms,
    232             &exchange_pub,
    233             &exchange_sig))
    234       {
    235         GNUNET_break_op (0);
    236         dr.hr.http_status = 0;
    237         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
    238         break;
    239       }
    240     }
    241     break;
    242   case MHD_HTTP_BAD_REQUEST:
    243     /* This should never happen, either us or the exchange is buggy
    244        (or API version conflict); just pass JSON reply to the application */
    245     dr.hr.ec = TALER_JSON_get_error_code (j);
    246     dr.hr.hint = TALER_JSON_get_error_hint (j);
    247     break;
    248   case MHD_HTTP_FORBIDDEN:
    249     dr.hr.ec = TALER_JSON_get_error_code (j);
    250     dr.hr.hint = TALER_JSON_get_error_hint (j);
    251     /* Nothing really to verify, exchange says one of the signatures is
    252        invalid; as we checked them, this should never happen, we
    253        should pass the JSON reply to the application */
    254     break;
    255   case MHD_HTTP_NOT_FOUND:
    256     dr.hr.ec = TALER_JSON_get_error_code (j);
    257     dr.hr.hint = TALER_JSON_get_error_hint (j);
    258     /* Nothing really to verify, this should never
    259        happen, we should pass the JSON reply to the application */
    260     break;
    261   case MHD_HTTP_CONFLICT:
    262     {
    263       dr.hr.ec = TALER_JSON_get_error_code (j);
    264       switch (dr.hr.ec)
    265       {
    266       case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA:
    267         if (GNUNET_OK !=
    268             TALER_EXCHANGE_check_purse_create_conflict_ (
    269               &pch->purse_sig,
    270               &pch->purse_pub,
    271               j))
    272         {
    273           GNUNET_break_op (0);
    274           dr.hr.http_status = 0;
    275           dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    276           break;
    277         }
    278         break;
    279       case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
    280         /* Nothing to check anymore here, proof needs to be
    281            checked in the GET /coins/$COIN_PUB handler */
    282         break;
    283       case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
    284         // FIXME #7267: write check (add to exchange_api_common! */
    285         break;
    286       case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
    287         {
    288           struct TALER_CoinSpendPublicKeyP coin_pub;
    289           struct TALER_CoinSpendSignatureP coin_sig;
    290           struct TALER_DenominationHashP h_denom_pub;
    291           struct TALER_AgeCommitmentHashP phac;
    292           bool found = false;
    293 
    294           if (GNUNET_OK !=
    295               TALER_EXCHANGE_check_purse_coin_conflict_ (
    296                 &pch->purse_pub,
    297                 pch->exchange_url,
    298                 j,
    299                 &h_denom_pub,
    300                 &phac,
    301                 &coin_pub,
    302                 &coin_sig))
    303           {
    304             GNUNET_break_op (0);
    305             dr.hr.http_status = 0;
    306             dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    307             break;
    308           }
    309           for (unsigned int i = 0; i<pch->num_deposits; i++)
    310           {
    311             struct Deposit *deposit = &pch->deposits[i];
    312 
    313             if (0 !=
    314                 GNUNET_memcmp (&coin_pub,
    315                                &deposit->coin_pub))
    316               continue;
    317             if (0 !=
    318                 GNUNET_memcmp (&deposit->h_denom_pub,
    319                                &h_denom_pub))
    320             {
    321               found = true;
    322               break;
    323             }
    324             if (0 !=
    325                 GNUNET_memcmp (&deposit->ahac,
    326                                &phac))
    327             {
    328               found = true;
    329               break;
    330             }
    331             if (0 ==
    332                 GNUNET_memcmp (&coin_sig,
    333                                &deposit->coin_sig))
    334             {
    335               GNUNET_break_op (0);
    336               continue;
    337             }
    338             found = true;
    339             break;
    340           }
    341           if (! found)
    342           {
    343             /* conflict is for a different coin! */
    344             GNUNET_break_op (0);
    345             dr.hr.http_status = 0;
    346             dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    347             break;
    348           }
    349         }
    350       case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
    351         if (GNUNET_OK !=
    352             TALER_EXCHANGE_check_purse_econtract_conflict_ (
    353               &pch->econtract.econtract_sig,
    354               &pch->purse_pub,
    355               j))
    356         {
    357           GNUNET_break_op (0);
    358           dr.hr.http_status = 0;
    359           dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    360           break;
    361         }
    362         break;
    363       default:
    364         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    365                     "Unexpected error code %d for conflcting deposit\n",
    366                     dr.hr.ec);
    367         GNUNET_break_op (0);
    368         dr.hr.http_status = 0;
    369         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    370       }
    371     }
    372     break;
    373   case MHD_HTTP_GONE:
    374     /* could happen if denomination was revoked */
    375     /* Note: one might want to check /keys for revocation
    376        signature here, alas tricky in case our /keys
    377        is outdated => left to clients */
    378     dr.hr.ec = TALER_JSON_get_error_code (j);
    379     dr.hr.hint = TALER_JSON_get_error_hint (j);
    380     break;
    381   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    382     dr.hr.ec = TALER_JSON_get_error_code (j);
    383     dr.hr.hint = TALER_JSON_get_error_hint (j);
    384     /* Server had an internal issue; we should retry, but this API
    385        leaves this to the application */
    386     break;
    387   default:
    388     /* unexpected response code */
    389     dr.hr.ec = TALER_JSON_get_error_code (j);
    390     dr.hr.hint = TALER_JSON_get_error_hint (j);
    391     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    392                 "Unexpected response code %u/%d for exchange deposit\n",
    393                 (unsigned int) response_code,
    394                 dr.hr.ec);
    395     GNUNET_break_op (0);
    396     break;
    397   }
    398   pch->cb (pch->cb_cls,
    399            &dr);
    400   TALER_EXCHANGE_purse_create_with_deposit_cancel (pch);
    401 }
    402 
    403 
    404 struct TALER_EXCHANGE_PurseCreateDepositHandle *
    405 TALER_EXCHANGE_purse_create_with_deposit (
    406   struct GNUNET_CURL_Context *ctx,
    407   const char *url,
    408   struct TALER_EXCHANGE_Keys *keys,
    409   const struct TALER_PurseContractPrivateKeyP *purse_priv,
    410   const struct TALER_PurseMergePrivateKeyP *merge_priv,
    411   const struct TALER_ContractDiffiePrivateP *contract_priv,
    412   const json_t *contract_terms,
    413   unsigned int num_deposits,
    414   const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
    415   bool upload_contract,
    416   TALER_EXCHANGE_PurseCreateDepositCallback cb,
    417   void *cb_cls)
    418 {
    419   struct TALER_EXCHANGE_PurseCreateDepositHandle *pch;
    420   json_t *create_obj;
    421   json_t *deposit_arr;
    422   CURL *eh;
    423   char arg_str[sizeof (pch->purse_pub) * 2 + 32];
    424   uint32_t min_age = 0;
    425 
    426   pch = GNUNET_new (struct TALER_EXCHANGE_PurseCreateDepositHandle);
    427   pch->cb = cb;
    428   pch->cb_cls = cb_cls;
    429   {
    430     struct GNUNET_JSON_Specification spec[] = {
    431       GNUNET_JSON_spec_timestamp ("pay_deadline",
    432                                   &pch->purse_expiration),
    433       TALER_JSON_spec_amount_any ("amount",
    434                                   &pch->purse_value_after_fees),
    435       GNUNET_JSON_spec_mark_optional (
    436         GNUNET_JSON_spec_uint32 ("minimum_age",
    437                                  &min_age),
    438         NULL),
    439       GNUNET_JSON_spec_end ()
    440     };
    441 
    442     if (GNUNET_OK !=
    443         GNUNET_JSON_parse (contract_terms,
    444                            spec,
    445                            NULL, NULL))
    446     {
    447       GNUNET_break (0);
    448       return NULL;
    449     }
    450   }
    451   if (GNUNET_OK !=
    452       TALER_JSON_contract_hash (contract_terms,
    453                                 &pch->h_contract_terms))
    454   {
    455     GNUNET_break (0);
    456     return NULL;
    457   }
    458   GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
    459                                       &pch->purse_pub.eddsa_pub);
    460   {
    461     char pub_str[sizeof (pch->purse_pub) * 2];
    462     char *end;
    463 
    464     end = GNUNET_STRINGS_data_to_string (
    465       &pch->purse_pub,
    466       sizeof (pch->purse_pub),
    467       pub_str,
    468       sizeof (pub_str));
    469     *end = '\0';
    470     GNUNET_snprintf (arg_str,
    471                      sizeof (arg_str),
    472                      "purses/%s/create",
    473                      pub_str);
    474   }
    475   GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
    476                                       &pch->merge_pub.eddsa_pub);
    477   pch->url = TALER_url_join (url,
    478                              arg_str,
    479                              NULL);
    480   if (NULL == pch->url)
    481   {
    482     GNUNET_break (0);
    483     GNUNET_free (pch);
    484     return NULL;
    485   }
    486   pch->num_deposits = num_deposits;
    487   pch->deposits = GNUNET_new_array (num_deposits,
    488                                     struct Deposit);
    489   deposit_arr = json_array ();
    490   GNUNET_assert (NULL != deposit_arr);
    491   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    492               "Signing with URL `%s'\n",
    493               url);
    494   for (unsigned int i = 0; i<num_deposits; i++)
    495   {
    496     const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
    497     const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
    498     struct Deposit *d = &pch->deposits[i];
    499     json_t *jdeposit;
    500     struct TALER_AgeCommitmentHashP *aghp = NULL;
    501     struct TALER_AgeAttestationP attest;
    502     struct TALER_AgeAttestationP *attestp = NULL;
    503 
    504     if (NULL != acp)
    505     {
    506       TALER_age_commitment_hash (&acp->commitment,
    507                                  &d->ahac);
    508       aghp = &d->ahac;
    509       if (GNUNET_OK !=
    510           TALER_age_commitment_attest (acp,
    511                                        min_age,
    512                                        &attest))
    513       {
    514         GNUNET_break (0);
    515         GNUNET_array_grow (pch->deposits,
    516                            pch->num_deposits,
    517                            0);
    518         GNUNET_free (pch->url);
    519         json_decref (deposit_arr);
    520         GNUNET_free (pch);
    521         return NULL;
    522       }
    523     }
    524     d->contribution = deposit->amount;
    525     d->h_denom_pub = deposit->h_denom_pub;
    526     GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
    527                                         &d->coin_pub.eddsa_pub);
    528     TALER_wallet_purse_deposit_sign (
    529       url,
    530       &pch->purse_pub,
    531       &deposit->amount,
    532       &d->h_denom_pub,
    533       &d->ahac,
    534       &deposit->coin_priv,
    535       &d->coin_sig);
    536     jdeposit = GNUNET_JSON_PACK (
    537       GNUNET_JSON_pack_allow_null (
    538         GNUNET_JSON_pack_data_auto ("h_age_commitment",
    539                                     aghp)),
    540       GNUNET_JSON_pack_allow_null (
    541         GNUNET_JSON_pack_data_auto ("age_attestation",
    542                                     attestp)),
    543       TALER_JSON_pack_amount ("amount",
    544                               &deposit->amount),
    545       GNUNET_JSON_pack_data_auto ("denom_pub_hash",
    546                                   &deposit->h_denom_pub),
    547       TALER_JSON_pack_denom_sig ("ub_sig",
    548                                  &deposit->denom_sig),
    549       GNUNET_JSON_pack_data_auto ("coin_sig",
    550                                   &d->coin_sig),
    551       GNUNET_JSON_pack_data_auto ("coin_pub",
    552                                   &d->coin_pub));
    553     GNUNET_assert (0 ==
    554                    json_array_append_new (deposit_arr,
    555                                           jdeposit));
    556   }
    557   TALER_wallet_purse_create_sign (pch->purse_expiration,
    558                                   &pch->h_contract_terms,
    559                                   &pch->merge_pub,
    560                                   min_age,
    561                                   &pch->purse_value_after_fees,
    562                                   purse_priv,
    563                                   &pch->purse_sig);
    564   if (upload_contract)
    565   {
    566     TALER_CRYPTO_contract_encrypt_for_merge (&pch->purse_pub,
    567                                              contract_priv,
    568                                              merge_priv,
    569                                              contract_terms,
    570                                              &pch->econtract.econtract,
    571                                              &pch->econtract.econtract_size);
    572     GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
    573                                         &pch->econtract.contract_pub.ecdhe_pub);
    574     TALER_wallet_econtract_upload_sign (pch->econtract.econtract,
    575                                         pch->econtract.econtract_size,
    576                                         &pch->econtract.contract_pub,
    577                                         purse_priv,
    578                                         &pch->econtract.econtract_sig);
    579   }
    580   create_obj = GNUNET_JSON_PACK (
    581     TALER_JSON_pack_amount ("amount",
    582                             &pch->purse_value_after_fees),
    583     GNUNET_JSON_pack_uint64 ("min_age",
    584                              min_age),
    585     GNUNET_JSON_pack_allow_null (
    586       TALER_JSON_pack_econtract ("econtract",
    587                                  upload_contract
    588                                  ? &pch->econtract
    589                                  : NULL)),
    590     GNUNET_JSON_pack_data_auto ("purse_sig",
    591                                 &pch->purse_sig),
    592     GNUNET_JSON_pack_data_auto ("merge_pub",
    593                                 &pch->merge_pub),
    594     GNUNET_JSON_pack_data_auto ("h_contract_terms",
    595                                 &pch->h_contract_terms),
    596     GNUNET_JSON_pack_timestamp ("purse_expiration",
    597                                 pch->purse_expiration),
    598     GNUNET_JSON_pack_array_steal ("deposits",
    599                                   deposit_arr));
    600   GNUNET_assert (NULL != create_obj);
    601   eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
    602   if ( (NULL == eh) ||
    603        (GNUNET_OK !=
    604         TALER_curl_easy_post (&pch->ctx,
    605                               eh,
    606                               create_obj)) )
    607   {
    608     GNUNET_break (0);
    609     if (NULL != eh)
    610       curl_easy_cleanup (eh);
    611     json_decref (create_obj);
    612     GNUNET_free (pch->econtract.econtract);
    613     GNUNET_array_grow (pch->deposits,
    614                        pch->num_deposits,
    615                        0);
    616     GNUNET_free (pch->url);
    617     GNUNET_free (pch);
    618     return NULL;
    619   }
    620   json_decref (create_obj);
    621   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    622               "URL for purse create with deposit: `%s'\n",
    623               pch->url);
    624   pch->keys = TALER_EXCHANGE_keys_incref (keys);
    625   pch->exchange_url = GNUNET_strdup (url);
    626   pch->job = GNUNET_CURL_job_add2 (ctx,
    627                                    eh,
    628                                    pch->ctx.headers,
    629                                    &handle_purse_create_deposit_finished,
    630                                    pch);
    631   return pch;
    632 }
    633 
    634 
    635 void
    636 TALER_EXCHANGE_purse_create_with_deposit_cancel (
    637   struct TALER_EXCHANGE_PurseCreateDepositHandle *pch)
    638 {
    639   if (NULL != pch->job)
    640   {
    641     GNUNET_CURL_job_cancel (pch->job);
    642     pch->job = NULL;
    643   }
    644   GNUNET_free (pch->econtract.econtract);
    645   GNUNET_free (pch->exchange_url);
    646   GNUNET_free (pch->url);
    647   GNUNET_array_grow (pch->deposits,
    648                      pch->num_deposits,
    649                      0);
    650   TALER_EXCHANGE_keys_decref (pch->keys);
    651   TALER_curl_easy_post_finished (&pch->ctx);
    652   GNUNET_free (pch);
    653 }
    654 
    655 
    656 /* end of exchange_api_purse_create_with_deposit.c */