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-purses-PURSE_PUB-create.c (23018B)


      1 /*
      2    This file is part of TALER
      3    Copyright (C) 2022-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-purses-PURSE_PUB-create.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    * Signature proving the validity of the coin.
     59    */
     60   struct TALER_DenominationSignature denom_sig;
     61 
     62   /**
     63    * Age restriction hash for the coin.
     64    */
     65   struct TALER_AgeCommitmentHashP ahac;
     66 
     67   /**
     68    * How much did we say the coin contributed.
     69    */
     70   struct TALER_Amount contribution;
     71 
     72   /**
     73    * Age attestation for this coin. Valid if @e have_age.
     74    */
     75   struct TALER_AgeAttestationP attest;
     76 
     77   /**
     78    * True if this coin uses age attestation.
     79    */
     80   bool have_age;
     81 
     82 };
     83 
     84 
     85 /**
     86  * @brief A purse create with deposit handle
     87  */
     88 struct TALER_EXCHANGE_PostPursesCreateHandle
     89 {
     90 
     91   /**
     92    * The curl context for this request.
     93    */
     94   struct GNUNET_CURL_Context *ctx;
     95 
     96   /**
     97    * The base URL of the exchange.
     98    */
     99   char *base_url;
    100 
    101   /**
    102    * The keys of the exchange this request handle will use
    103    */
    104   struct TALER_EXCHANGE_Keys *keys;
    105 
    106   /**
    107    * The url for this request, set during _start.
    108    */
    109   char *url;
    110 
    111   /**
    112    * Context for #TEH_curl_easy_post(). Keeps the data that must
    113    * persist for Curl to make the upload.
    114    */
    115   struct TALER_CURL_PostContext post_ctx;
    116 
    117   /**
    118    * Handle for the request.
    119    */
    120   struct GNUNET_CURL_Job *job;
    121 
    122   /**
    123    * Function to call with the result.
    124    */
    125   TALER_EXCHANGE_PostPursesCreateCallback cb;
    126 
    127   /**
    128    * Closure for @a cb.
    129    */
    130   TALER_EXCHANGE_POST_PURSES_CREATE_RESULT_CLOSURE *cb_cls;
    131 
    132   /**
    133    * Expected value in the purse after fees.
    134    */
    135   struct TALER_Amount purse_value_after_fees;
    136 
    137   /**
    138    * Our encrypted contract (if we had any).
    139    */
    140   struct TALER_EncryptedContract econtract;
    141 
    142   /**
    143    * Public key of the merge capability.
    144    */
    145   struct TALER_PurseMergePublicKeyP merge_pub;
    146 
    147   /**
    148    * Private key of the purse which we are creating.
    149    */
    150   struct TALER_PurseContractPrivateKeyP purse_priv;
    151 
    152   /**
    153    * Private key for the merge operation.
    154    */
    155   struct TALER_PurseMergePrivateKeyP merge_priv;
    156 
    157   /**
    158    * Private key to decrypt the contract.
    159    */
    160   struct TALER_ContractDiffiePrivateP contract_priv;
    161 
    162   /**
    163    * Contract terms for the payment.
    164    */
    165   json_t *contract_terms;
    166 
    167   /**
    168    * Minimum age, as per @e contract_terms.
    169    */
    170   uint32_t min_age;
    171 
    172   /**
    173    * Public key of the purse.
    174    */
    175   struct TALER_PurseContractPublicKeyP purse_pub;
    176 
    177   /**
    178    * Signature for purse creation.
    179    */
    180   struct TALER_PurseContractSignatureP purse_sig;
    181 
    182   /**
    183    * Hash over the purse's contract terms.
    184    */
    185   struct TALER_PrivateContractHashP h_contract_terms;
    186 
    187   /**
    188    * When does the purse expire.
    189    */
    190   struct GNUNET_TIME_Timestamp purse_expiration;
    191 
    192   /**
    193    * Array of @e num_deposit deposits.
    194    */
    195   struct Deposit *deposits;
    196 
    197   /**
    198    * How many deposits did we make?
    199    */
    200   unsigned int num_deposits;
    201 
    202   struct
    203   {
    204 
    205     /**
    206      * Whether we are uploading a contract.
    207      */
    208     bool upload_contract;
    209 
    210   } options;
    211 
    212 };
    213 
    214 
    215 /**
    216  * Function called when we're done processing the
    217  * HTTP /purses/$PID/create request.
    218  *
    219  * @param cls the `struct TALER_EXCHANGE_PostPursesCreateHandle`
    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_purse_create_deposit_finished (void *cls,
    225                                       long response_code,
    226                                       const void *response)
    227 {
    228   struct TALER_EXCHANGE_PostPursesCreateHandle *pch = cls;
    229   const json_t *j = response;
    230   struct TALER_EXCHANGE_PostPursesCreateResponse dr = {
    231     .hr.reply = j,
    232     .hr.http_status = (unsigned int) response_code
    233   };
    234   const struct TALER_EXCHANGE_Keys *keys = pch->keys;
    235 
    236   pch->job = NULL;
    237   switch (response_code)
    238   {
    239   case 0:
    240     dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    241     break;
    242   case MHD_HTTP_OK:
    243     {
    244       struct GNUNET_TIME_Timestamp etime;
    245       struct TALER_Amount total_deposited;
    246       struct TALER_ExchangeSignatureP exchange_sig;
    247       struct TALER_ExchangePublicKeyP exchange_pub;
    248       struct GNUNET_JSON_Specification spec[] = {
    249         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    250                                      &exchange_sig),
    251         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    252                                      &exchange_pub),
    253         GNUNET_JSON_spec_timestamp ("exchange_timestamp",
    254                                     &etime),
    255         TALER_JSON_spec_amount ("total_deposited",
    256                                 pch->purse_value_after_fees.currency,
    257                                 &total_deposited),
    258         GNUNET_JSON_spec_end ()
    259       };
    260 
    261       if (GNUNET_OK !=
    262           GNUNET_JSON_parse (j,
    263                              spec,
    264                              NULL, NULL))
    265       {
    266         GNUNET_break_op (0);
    267         dr.hr.http_status = 0;
    268         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    269         break;
    270       }
    271       if (GNUNET_OK !=
    272           TALER_EXCHANGE_test_signing_key (keys,
    273                                            &exchange_pub))
    274       {
    275         GNUNET_break_op (0);
    276         dr.hr.http_status = 0;
    277         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
    278         break;
    279       }
    280       if (GNUNET_OK !=
    281           TALER_exchange_online_purse_created_verify (
    282             etime,
    283             pch->purse_expiration,
    284             &pch->purse_value_after_fees,
    285             &total_deposited,
    286             &pch->purse_pub,
    287             &pch->h_contract_terms,
    288             &exchange_pub,
    289             &exchange_sig))
    290       {
    291         GNUNET_break_op (0);
    292         dr.hr.http_status = 0;
    293         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
    294         break;
    295       }
    296       dr.details.ok.exchange_pub = exchange_pub;
    297       dr.details.ok.exchange_sig = exchange_sig;
    298     }
    299     break;
    300   case MHD_HTTP_BAD_REQUEST:
    301     /* This should never happen, either us or the exchange is buggy
    302        (or API version conflict); just pass JSON reply to the application */
    303     dr.hr.ec = TALER_JSON_get_error_code (j);
    304     dr.hr.hint = TALER_JSON_get_error_hint (j);
    305     break;
    306   case MHD_HTTP_FORBIDDEN:
    307     dr.hr.ec = TALER_JSON_get_error_code (j);
    308     dr.hr.hint = TALER_JSON_get_error_hint (j);
    309     /* Nothing really to verify, exchange says one of the signatures is
    310        invalid; as we checked them, this should never happen, we
    311        should pass the JSON reply to the application */
    312     break;
    313   case MHD_HTTP_NOT_FOUND:
    314     dr.hr.ec = TALER_JSON_get_error_code (j);
    315     dr.hr.hint = TALER_JSON_get_error_hint (j);
    316     /* Nothing really to verify, this should never
    317        happen, we should pass the JSON reply to the application */
    318     break;
    319   case MHD_HTTP_CONFLICT:
    320     {
    321       dr.hr.ec = TALER_JSON_get_error_code (j);
    322       switch (dr.hr.ec)
    323       {
    324       case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA:
    325         if (GNUNET_OK !=
    326             TALER_EXCHANGE_check_purse_create_conflict_ (
    327               &pch->purse_sig,
    328               &pch->purse_pub,
    329               j))
    330         {
    331           GNUNET_break_op (0);
    332           dr.hr.http_status = 0;
    333           dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    334           break;
    335         }
    336         break;
    337       case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
    338         /* Nothing to check anymore here, proof needs to be
    339            checked in the GET /coins/$COIN_PUB handler */
    340         break;
    341       case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
    342         // FIXME #7267: write check (add to exchange_api_common!) */
    343         break;
    344       case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
    345         {
    346           struct TALER_CoinSpendPublicKeyP coin_pub;
    347           struct TALER_CoinSpendSignatureP coin_sig;
    348           struct TALER_DenominationHashP h_denom_pub;
    349           struct TALER_AgeCommitmentHashP phac;
    350           bool found = false;
    351 
    352           if (GNUNET_OK !=
    353               TALER_EXCHANGE_check_purse_coin_conflict_ (
    354                 &pch->purse_pub,
    355                 pch->base_url,
    356                 j,
    357                 &h_denom_pub,
    358                 &phac,
    359                 &coin_pub,
    360                 &coin_sig))
    361           {
    362             GNUNET_break_op (0);
    363             dr.hr.http_status = 0;
    364             dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    365             break;
    366           }
    367           for (unsigned int i = 0; i<pch->num_deposits; i++)
    368           {
    369             struct Deposit *deposit = &pch->deposits[i];
    370 
    371             if (0 !=
    372                 GNUNET_memcmp (&coin_pub,
    373                                &deposit->coin_pub))
    374               continue;
    375             if (0 !=
    376                 GNUNET_memcmp (&deposit->h_denom_pub,
    377                                &h_denom_pub))
    378             {
    379               found = true;
    380               break;
    381             }
    382             if (0 !=
    383                 GNUNET_memcmp (&deposit->ahac,
    384                                &phac))
    385             {
    386               found = true;
    387               break;
    388             }
    389             if (0 ==
    390                 GNUNET_memcmp (&coin_sig,
    391                                &deposit->coin_sig))
    392             {
    393               GNUNET_break_op (0);
    394               continue;
    395             }
    396             found = true;
    397             break;
    398           }
    399           if (! found)
    400           {
    401             /* conflict is for a different coin! */
    402             GNUNET_break_op (0);
    403             dr.hr.http_status = 0;
    404             dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    405             break;
    406           }
    407         }
    408       /* fall-through */
    409       case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
    410         if (GNUNET_OK !=
    411             TALER_EXCHANGE_check_purse_econtract_conflict_ (
    412               &pch->econtract.econtract_sig,
    413               &pch->purse_pub,
    414               j))
    415         {
    416           GNUNET_break_op (0);
    417           dr.hr.http_status = 0;
    418           dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    419           break;
    420         }
    421         break;
    422       default:
    423         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    424                     "Unexpected error code %d for conflicting deposit\n",
    425                     dr.hr.ec);
    426         GNUNET_break_op (0);
    427         dr.hr.http_status = 0;
    428         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    429       }
    430     }
    431     break;
    432   case MHD_HTTP_GONE:
    433     /* could happen if denomination was revoked */
    434     /* Note: one might want to check /keys for revocation
    435        signature here, alas tricky in case our /keys
    436        is outdated => left to clients */
    437     dr.hr.ec = TALER_JSON_get_error_code (j);
    438     dr.hr.hint = TALER_JSON_get_error_hint (j);
    439     break;
    440   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    441     dr.hr.ec = TALER_JSON_get_error_code (j);
    442     dr.hr.hint = TALER_JSON_get_error_hint (j);
    443     /* Server had an internal issue; we should retry, but this API
    444        leaves this to the application */
    445     break;
    446   default:
    447     /* unexpected response code */
    448     dr.hr.ec = TALER_JSON_get_error_code (j);
    449     dr.hr.hint = TALER_JSON_get_error_hint (j);
    450     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    451                 "Unexpected response code %u/%d for exchange deposit\n",
    452                 (unsigned int) response_code,
    453                 dr.hr.ec);
    454     GNUNET_break_op (0);
    455     break;
    456   }
    457   if (NULL != pch->cb)
    458   {
    459     pch->cb (pch->cb_cls,
    460              &dr);
    461     pch->cb = NULL;
    462   }
    463   TALER_EXCHANGE_post_purses_create_cancel (pch);
    464 }
    465 
    466 
    467 struct TALER_EXCHANGE_PostPursesCreateHandle *
    468 TALER_EXCHANGE_post_purses_create_create (
    469   struct GNUNET_CURL_Context *ctx,
    470   const char *url,
    471   struct TALER_EXCHANGE_Keys *keys,
    472   const struct TALER_PurseContractPrivateKeyP *purse_priv,
    473   const struct TALER_PurseMergePrivateKeyP *merge_priv,
    474   const struct TALER_ContractDiffiePrivateP *contract_priv,
    475   const json_t *contract_terms,
    476   unsigned int num_deposits,
    477   const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits])
    478 {
    479   struct TALER_EXCHANGE_PostPursesCreateHandle *pch;
    480 
    481   pch = GNUNET_new (struct TALER_EXCHANGE_PostPursesCreateHandle);
    482   pch->ctx = ctx;
    483   pch->base_url = GNUNET_strdup (url);
    484   pch->purse_priv = *purse_priv;
    485   pch->merge_priv = *merge_priv;
    486   pch->contract_priv = *contract_priv;
    487   pch->contract_terms = json_incref ((json_t *) contract_terms);
    488   {
    489     struct GNUNET_JSON_Specification spec[] = {
    490       GNUNET_JSON_spec_timestamp ("pay_deadline",
    491                                   &pch->purse_expiration),
    492       TALER_JSON_spec_amount_any ("amount",
    493                                   &pch->purse_value_after_fees),
    494       GNUNET_JSON_spec_mark_optional (
    495         GNUNET_JSON_spec_uint32 ("minimum_age",
    496                                  &pch->min_age),
    497         NULL),
    498       GNUNET_JSON_spec_end ()
    499     };
    500 
    501     if (GNUNET_OK !=
    502         GNUNET_JSON_parse (contract_terms,
    503                            spec,
    504                            NULL, NULL))
    505     {
    506       GNUNET_break (0);
    507       GNUNET_free (pch->base_url);
    508       GNUNET_free (pch);
    509       return NULL;
    510     }
    511   }
    512   if (GNUNET_OK !=
    513       TALER_JSON_contract_hash (contract_terms,
    514                                 &pch->h_contract_terms))
    515   {
    516     GNUNET_break (0);
    517     GNUNET_free (pch->base_url);
    518     GNUNET_free (pch);
    519     return NULL;
    520   }
    521   GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
    522                                       &pch->purse_pub.eddsa_pub);
    523   GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
    524                                       &pch->merge_pub.eddsa_pub);
    525   pch->num_deposits = num_deposits;
    526   pch->deposits = GNUNET_new_array (num_deposits,
    527                                     struct Deposit);
    528   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    529               "Signing with URL `%s'\n",
    530               url);
    531   for (unsigned int i = 0; i<num_deposits; i++)
    532   {
    533     const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
    534     const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
    535     struct Deposit *d = &pch->deposits[i];
    536 
    537     if (NULL != acp)
    538     {
    539       TALER_age_commitment_hash (&acp->commitment,
    540                                  &d->ahac);
    541       d->have_age = true;
    542       if (GNUNET_OK !=
    543           TALER_age_commitment_attest (acp,
    544                                        pch->min_age,
    545                                        &d->attest))
    546       {
    547         GNUNET_break (0);
    548         GNUNET_array_grow (pch->deposits,
    549                            pch->num_deposits,
    550                            0);
    551         GNUNET_free (pch->base_url);
    552         GNUNET_free (pch);
    553         return NULL;
    554       }
    555     }
    556     d->contribution = deposit->amount;
    557     d->h_denom_pub = deposit->h_denom_pub;
    558     TALER_denom_sig_copy (&d->denom_sig,
    559                           &deposit->denom_sig);
    560     GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
    561                                         &d->coin_pub.eddsa_pub);
    562     TALER_wallet_purse_deposit_sign (
    563       url,
    564       &pch->purse_pub,
    565       &deposit->amount,
    566       &d->h_denom_pub,
    567       &d->ahac,
    568       &deposit->coin_priv,
    569       &d->coin_sig);
    570   }
    571   pch->keys = TALER_EXCHANGE_keys_incref (keys);
    572   return pch;
    573 }
    574 
    575 
    576 enum GNUNET_GenericReturnValue
    577 TALER_EXCHANGE_post_purses_create_set_options_ (
    578   struct TALER_EXCHANGE_PostPursesCreateHandle *ppch,
    579   unsigned int num_options,
    580   const struct TALER_EXCHANGE_PostPursesCreateOptionValue options[])
    581 {
    582   for (unsigned int i = 0; i < num_options; i++)
    583   {
    584     const struct TALER_EXCHANGE_PostPursesCreateOptionValue *opt = &options[i];
    585 
    586     switch (opt->option)
    587     {
    588     case TALER_EXCHANGE_POST_PURSES_CREATE_OPTION_END:
    589       return GNUNET_OK;
    590     case TALER_EXCHANGE_POST_PURSES_CREATE_OPTION_UPLOAD_CONTRACT:
    591       ppch->options.upload_contract = true;
    592       break;
    593     }
    594   }
    595   return GNUNET_OK;
    596 }
    597 
    598 
    599 enum TALER_ErrorCode
    600 TALER_EXCHANGE_post_purses_create_start (
    601   struct TALER_EXCHANGE_PostPursesCreateHandle *pch,
    602   TALER_EXCHANGE_PostPursesCreateCallback cb,
    603   TALER_EXCHANGE_POST_PURSES_CREATE_RESULT_CLOSURE *cb_cls)
    604 {
    605   char arg_str[sizeof (pch->purse_pub) * 2 + 32];
    606   CURL *eh;
    607   json_t *deposit_arr;
    608   json_t *body;
    609 
    610   pch->cb = cb;
    611   pch->cb_cls = cb_cls;
    612   deposit_arr = json_array ();
    613   GNUNET_assert (NULL != deposit_arr);
    614 
    615   for (unsigned int i = 0; i<pch->num_deposits; i++)
    616   {
    617     struct Deposit *d = &pch->deposits[i];
    618     struct TALER_AgeCommitmentHashP *aghp = NULL;
    619     struct TALER_AgeAttestationP *attestp = NULL;
    620     json_t *jdeposit;
    621 
    622     if (d->have_age)
    623     {
    624       aghp = &d->ahac;
    625       attestp = &d->attest;
    626     }
    627     jdeposit = GNUNET_JSON_PACK (
    628       GNUNET_JSON_pack_allow_null (
    629         GNUNET_JSON_pack_data_auto ("h_age_commitment",
    630                                     aghp)),
    631       GNUNET_JSON_pack_allow_null (
    632         GNUNET_JSON_pack_data_auto ("age_attestation",
    633                                     attestp)),
    634       TALER_JSON_pack_amount ("amount",
    635                               &d->contribution),
    636       GNUNET_JSON_pack_data_auto ("denom_pub_hash",
    637                                   &d->h_denom_pub),
    638       TALER_JSON_pack_denom_sig ("ub_sig",
    639                                  &d->denom_sig),
    640       GNUNET_JSON_pack_data_auto ("coin_sig",
    641                                   &d->coin_sig),
    642       GNUNET_JSON_pack_data_auto ("coin_pub",
    643                                   &d->coin_pub));
    644     GNUNET_assert (0 ==
    645                    json_array_append_new (deposit_arr,
    646                                           jdeposit));
    647 
    648   }
    649 
    650   if (pch->options.upload_contract)
    651   {
    652     TALER_CRYPTO_contract_encrypt_for_merge (
    653       &pch->purse_pub,
    654       &pch->contract_priv,
    655       &pch->merge_priv,
    656       pch->contract_terms,
    657       &pch->econtract.econtract,
    658       &pch->econtract.econtract_size);
    659     GNUNET_CRYPTO_ecdhe_key_get_public (
    660       &pch->contract_priv.ecdhe_priv,
    661       &pch->econtract.contract_pub.ecdhe_pub);
    662     TALER_wallet_econtract_upload_sign (
    663       pch->econtract.econtract,
    664       pch->econtract.econtract_size,
    665       &pch->econtract.contract_pub,
    666       &pch->purse_priv,
    667       &pch->econtract.econtract_sig);
    668   }
    669   TALER_wallet_purse_create_sign (pch->purse_expiration,
    670                                   &pch->h_contract_terms,
    671                                   &pch->merge_pub,
    672                                   pch->min_age,
    673                                   &pch->purse_value_after_fees,
    674                                   &pch->purse_priv,
    675                                   &pch->purse_sig);
    676 
    677   body = GNUNET_JSON_PACK (
    678     TALER_JSON_pack_amount ("amount",
    679                             &pch->purse_value_after_fees),
    680     GNUNET_JSON_pack_uint64 ("min_age",
    681                              pch->min_age),
    682     GNUNET_JSON_pack_allow_null (
    683       TALER_JSON_pack_econtract ("econtract",
    684                                  pch->options.upload_contract
    685                                  ? &pch->econtract
    686                                  : NULL)),
    687     GNUNET_JSON_pack_data_auto ("purse_sig",
    688                                 &pch->purse_sig),
    689     GNUNET_JSON_pack_data_auto ("merge_pub",
    690                                 &pch->merge_pub),
    691     GNUNET_JSON_pack_data_auto ("h_contract_terms",
    692                                 &pch->h_contract_terms),
    693     GNUNET_JSON_pack_timestamp ("purse_expiration",
    694                                 pch->purse_expiration),
    695     GNUNET_JSON_pack_array_steal ("deposits",
    696                                   deposit_arr));
    697   if (NULL == body)
    698   {
    699     GNUNET_break (0);
    700     return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
    701   }
    702 
    703   {
    704     char pub_str[sizeof (pch->purse_pub) * 2];
    705     char *end;
    706 
    707     end = GNUNET_STRINGS_data_to_string (
    708       &pch->purse_pub,
    709       sizeof (pch->purse_pub),
    710       pub_str,
    711       sizeof (pub_str));
    712     *end = '\0';
    713     GNUNET_snprintf (arg_str,
    714                      sizeof (arg_str),
    715                      "purses/%s/create",
    716                      pub_str);
    717   }
    718   pch->url = TALER_url_join (pch->base_url,
    719                              arg_str,
    720                              NULL);
    721   if (NULL == pch->url)
    722   {
    723     GNUNET_break (0);
    724     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    725   }
    726   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    727               "URL for purse create with deposit: `%s'\n",
    728               pch->url);
    729   eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
    730   if ( (NULL == eh) ||
    731        (GNUNET_OK !=
    732         TALER_curl_easy_post (&pch->post_ctx,
    733                               eh,
    734                               body)) )
    735   {
    736     GNUNET_break (0);
    737     if (NULL != eh)
    738       curl_easy_cleanup (eh);
    739     json_decref (body);
    740     return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
    741   }
    742   json_decref (body);
    743   pch->job = GNUNET_CURL_job_add2 (pch->ctx,
    744                                    eh,
    745                                    pch->post_ctx.headers,
    746                                    &handle_purse_create_deposit_finished,
    747                                    pch);
    748   if (NULL == pch->job)
    749     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    750   return TALER_EC_NONE;
    751 }
    752 
    753 
    754 void
    755 TALER_EXCHANGE_post_purses_create_cancel (
    756   struct TALER_EXCHANGE_PostPursesCreateHandle *pch)
    757 {
    758   if (NULL != pch->job)
    759   {
    760     GNUNET_CURL_job_cancel (pch->job);
    761     pch->job = NULL;
    762   }
    763   json_decref (pch->contract_terms);
    764   GNUNET_free (pch->econtract.econtract);
    765   GNUNET_free (pch->base_url);
    766   GNUNET_free (pch->url);
    767 
    768   for (unsigned int i = 0; i<pch->num_deposits; i++)
    769   {
    770     struct Deposit *d = &pch->deposits[i];
    771 
    772     TALER_denom_sig_free (&d->denom_sig);
    773   }
    774   GNUNET_array_grow (pch->deposits,
    775                      pch->num_deposits,
    776                      0);
    777   TALER_EXCHANGE_keys_decref (pch->keys);
    778   TALER_curl_easy_post_finished (&pch->post_ctx);
    779   GNUNET_free (pch);
    780 }
    781 
    782 
    783 /* end of exchange_api_post-purses-PURSE_PUB-create.c */