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


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