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


      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  * Find the deposited coin @a coin_pub and how we used it.
    215  *
    216  * @param cls a `struct TALER_EXCHANGE_PostPursesCreateHandle *`
    217  * @param coin_pub public key of the coin named in the conflict reply
    218  * @param[out] h_denom_pub set to the hash of the denomination we used
    219  * @param[out] h_age_commitment set to the age commitment hash we used, or NULL
    220  * @return #GNUNET_OK if found, #GNUNET_NO if the coin is not ours
    221  */
    222 static enum GNUNET_GenericReturnValue
    223 purse_create_coin_lookup (void *cls,
    224                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
    225                           const struct TALER_DenominationHashP **h_denom_pub,
    226                           const struct TALER_AgeCommitmentHashP **
    227                           h_age_commitment)
    228 {
    229   struct TALER_EXCHANGE_PostPursesCreateHandle *pch = cls;
    230 
    231   for (unsigned int i = 0; i<pch->num_deposits; i++)
    232   {
    233     const struct Deposit *deposit = &pch->deposits[i];
    234 
    235     if (0 != GNUNET_memcmp (coin_pub,
    236                             &deposit->coin_pub))
    237       continue;
    238     *h_denom_pub = &deposit->h_denom_pub;
    239     *h_age_commitment = deposit->have_age
    240       ? &deposit->ahac
    241       : NULL;
    242     return GNUNET_OK;
    243   }
    244   return GNUNET_NO;
    245 }
    246 
    247 
    248 /**
    249  * Function called when we're done processing the
    250  * HTTP /purses/$PID/create request.
    251  *
    252  * @param cls the `struct TALER_EXCHANGE_PostPursesCreateHandle`
    253  * @param response_code HTTP response code, 0 on error
    254  * @param response parsed JSON result, NULL on error
    255  */
    256 static void
    257 handle_purse_create_deposit_finished (void *cls,
    258                                       long response_code,
    259                                       const void *response)
    260 {
    261   struct TALER_EXCHANGE_PostPursesCreateHandle *pch = cls;
    262   const json_t *j = response;
    263   struct TALER_EXCHANGE_PostPursesCreateResponse dr = {
    264     .hr.reply = j,
    265     .hr.http_status = (unsigned int) response_code
    266   };
    267   const struct TALER_EXCHANGE_Keys *keys = pch->keys;
    268 
    269   pch->job = NULL;
    270   switch (response_code)
    271   {
    272   case 0:
    273     dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    274     break;
    275   case MHD_HTTP_OK:
    276     {
    277       struct GNUNET_JSON_Specification spec[] = {
    278         GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    279                                      &dr.details.ok.exchange_sig),
    280         GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    281                                      &dr.details.ok.exchange_pub),
    282         GNUNET_JSON_spec_timestamp ("exchange_timestamp",
    283                                     &dr.details.ok.exchange_timestamp),
    284         TALER_JSON_spec_amount ("total_deposited",
    285                                 pch->purse_value_after_fees.currency,
    286                                 &dr.details.ok.total_deposited),
    287         GNUNET_JSON_spec_end ()
    288       };
    289 
    290       if (GNUNET_OK !=
    291           GNUNET_JSON_parse (j,
    292                              spec,
    293                              NULL, NULL))
    294       {
    295         GNUNET_break_op (0);
    296         dr.hr.http_status = 0;
    297         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    298         break;
    299       }
    300       if (GNUNET_OK !=
    301           TALER_EXCHANGE_test_signing_key (keys,
    302                                            &dr.details.ok.exchange_pub))
    303       {
    304         GNUNET_break_op (0);
    305         dr.hr.http_status = 0;
    306         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
    307         break;
    308       }
    309       if (GNUNET_OK !=
    310           TALER_exchange_online_purse_created_verify (
    311             dr.details.ok.exchange_timestamp,
    312             pch->purse_expiration,
    313             &pch->purse_value_after_fees,
    314             &dr.details.ok.total_deposited,
    315             &pch->purse_pub,
    316             &pch->h_contract_terms,
    317             &dr.details.ok.exchange_pub,
    318             &dr.details.ok.exchange_sig))
    319       {
    320         GNUNET_break_op (0);
    321         dr.hr.http_status = 0;
    322         dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
    323         break;
    324       }
    325     }
    326     break;
    327   case MHD_HTTP_BAD_REQUEST:
    328     /* This should never happen, either us or the exchange is buggy
    329        (or API version conflict); just pass JSON reply to the application */
    330     dr.hr.ec = TALER_JSON_get_error_code (j);
    331     dr.hr.hint = TALER_JSON_get_error_hint (j);
    332     break;
    333   case MHD_HTTP_FORBIDDEN:
    334     dr.hr.ec = TALER_JSON_get_error_code (j);
    335     dr.hr.hint = TALER_JSON_get_error_hint (j);
    336     /* Nothing really to verify, exchange says one of the signatures is
    337        invalid; as we checked them, this should never happen, we
    338        should pass the JSON reply to the application */
    339     break;
    340   case MHD_HTTP_NOT_FOUND:
    341     dr.hr.ec = TALER_JSON_get_error_code (j);
    342     dr.hr.hint = TALER_JSON_get_error_hint (j);
    343     /* Nothing really to verify, this should never
    344        happen, we should pass the JSON reply to the application */
    345     break;
    346   case MHD_HTTP_CONFLICT:
    347     {
    348       dr.hr.ec = TALER_JSON_get_error_code (j);
    349       switch (dr.hr.ec)
    350       {
    351       case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA:
    352         if (GNUNET_OK !=
    353             TALER_EXCHANGE_check_purse_create_conflict_ (
    354               &pch->purse_sig,
    355               &pch->purse_pub,
    356               j))
    357         {
    358           GNUNET_break_op (0);
    359           dr.hr.http_status = 0;
    360           dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    361           break;
    362         }
    363         break;
    364       case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
    365         /* Nothing to check anymore here, proof needs to be
    366            checked in the GET /coins/$COIN_PUB handler */
    367         break;
    368       case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
    369       case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
    370         if (GNUNET_OK !=
    371             TALER_EXCHANGE_check_coin_conflict_ (pch->keys,
    372                                                  dr.hr.ec,
    373                                                  j,
    374                                                  &purse_create_coin_lookup,
    375                                                  pch,
    376                                                  &dr.details.conflict))
    377         {
    378           GNUNET_break_op (0);
    379           dr.hr.http_status = 0;
    380           dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    381         }
    382         break;
    383       case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
    384         {
    385           struct TALER_CoinSpendPublicKeyP coin_pub;
    386           struct TALER_CoinSpendSignatureP coin_sig;
    387           struct TALER_DenominationHashP h_denom_pub;
    388           struct TALER_AgeCommitmentHashP phac;
    389           bool found = false;
    390 
    391           if (GNUNET_OK !=
    392               TALER_EXCHANGE_check_purse_coin_conflict_ (
    393                 &pch->purse_pub,
    394                 pch->base_url,
    395                 j,
    396                 &h_denom_pub,
    397                 &phac,
    398                 &coin_pub,
    399                 &coin_sig))
    400           {
    401             GNUNET_break_op (0);
    402             dr.hr.http_status = 0;
    403             dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    404             break;
    405           }
    406           for (unsigned int i = 0; i<pch->num_deposits; i++)
    407           {
    408             struct Deposit *deposit = &pch->deposits[i];
    409 
    410             if (0 !=
    411                 GNUNET_memcmp (&coin_pub,
    412                                &deposit->coin_pub))
    413               continue;
    414             if (0 !=
    415                 GNUNET_memcmp (&deposit->h_denom_pub,
    416                                &h_denom_pub))
    417             {
    418               found = true;
    419               break;
    420             }
    421             if (0 !=
    422                 GNUNET_memcmp (&deposit->ahac,
    423                                &phac))
    424             {
    425               found = true;
    426               break;
    427             }
    428             if (0 ==
    429                 GNUNET_memcmp (&coin_sig,
    430                                &deposit->coin_sig))
    431             {
    432               GNUNET_break_op (0);
    433               continue;
    434             }
    435             found = true;
    436             break;
    437           }
    438           if (! found)
    439           {
    440             /* conflict is for a different coin! */
    441             GNUNET_break_op (0);
    442             dr.hr.http_status = 0;
    443             dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    444             break;
    445           }
    446         }
    447         break;
    448       case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
    449         if (GNUNET_OK !=
    450             TALER_EXCHANGE_check_purse_econtract_conflict_ (
    451               &pch->econtract.econtract_sig,
    452               &pch->purse_pub,
    453               j))
    454         {
    455           GNUNET_break_op (0);
    456           dr.hr.http_status = 0;
    457           dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    458           break;
    459         }
    460         break;
    461       default:
    462         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    463                     "Unexpected error code %d for conflicting deposit\n",
    464                     dr.hr.ec);
    465         GNUNET_break_op (0);
    466         dr.hr.http_status = 0;
    467         dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    468       } /* switch on (ec) */
    469     }
    470     break;
    471   case MHD_HTTP_GONE:
    472     /* could happen if denomination was revoked */
    473     /* Note: one might want to check /keys for revocation
    474        signature here, alas tricky in case our /keys
    475        is outdated => left to clients */
    476     dr.hr.ec = TALER_JSON_get_error_code (j);
    477     dr.hr.hint = TALER_JSON_get_error_hint (j);
    478     break;
    479   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    480     dr.hr.ec = TALER_JSON_get_error_code (j);
    481     dr.hr.hint = TALER_JSON_get_error_hint (j);
    482     /* Server had an internal issue; we should retry, but this API
    483        leaves this to the application */
    484     break;
    485   default:
    486     /* unexpected response code */
    487     dr.hr.ec = TALER_JSON_get_error_code (j);
    488     dr.hr.hint = TALER_JSON_get_error_hint (j);
    489     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    490                 "Unexpected response code %u/%d for exchange deposit\n",
    491                 (unsigned int) response_code,
    492                 dr.hr.ec);
    493     GNUNET_break_op (0);
    494     break;
    495   }
    496   if (NULL != pch->cb)
    497   {
    498     pch->cb (pch->cb_cls,
    499              &dr);
    500     pch->cb = NULL;
    501   }
    502   TALER_EXCHANGE_free_coin_conflict_ (&dr.hr,
    503                                       &dr.details.conflict);
    504   TALER_EXCHANGE_post_purses_create_cancel (pch);
    505 }
    506 
    507 
    508 struct TALER_EXCHANGE_PostPursesCreateHandle *
    509 TALER_EXCHANGE_post_purses_create_create (
    510   struct GNUNET_CURL_Context *ctx,
    511   const char *url,
    512   struct TALER_EXCHANGE_Keys *keys,
    513   const struct TALER_PurseContractPrivateKeyP *purse_priv,
    514   const struct TALER_PurseMergePrivateKeyP *merge_priv,
    515   const struct TALER_ContractDiffiePrivateP *contract_priv,
    516   const json_t *contract_terms,
    517   unsigned int num_deposits,
    518   const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits])
    519 {
    520   struct TALER_EXCHANGE_PostPursesCreateHandle *pch;
    521 
    522   pch = GNUNET_new (struct TALER_EXCHANGE_PostPursesCreateHandle);
    523   pch->ctx = ctx;
    524   pch->base_url = GNUNET_strdup (url);
    525   pch->purse_priv = *purse_priv;
    526   pch->merge_priv = *merge_priv;
    527   pch->contract_priv = *contract_priv;
    528   pch->contract_terms = json_incref ((json_t *) contract_terms);
    529   {
    530     struct GNUNET_JSON_Specification spec[] = {
    531       GNUNET_JSON_spec_timestamp ("pay_deadline",
    532                                   &pch->purse_expiration),
    533       TALER_JSON_spec_amount_any ("amount",
    534                                   &pch->purse_value_after_fees),
    535       GNUNET_JSON_spec_mark_optional (
    536         GNUNET_JSON_spec_uint32 ("minimum_age",
    537                                  &pch->min_age),
    538         NULL),
    539       GNUNET_JSON_spec_end ()
    540     };
    541 
    542     if (GNUNET_OK !=
    543         GNUNET_JSON_parse (contract_terms,
    544                            spec,
    545                            NULL, NULL))
    546     {
    547       GNUNET_break (0);
    548       GNUNET_free (pch->base_url);
    549       GNUNET_free (pch);
    550       return NULL;
    551     }
    552   }
    553   if (GNUNET_OK !=
    554       TALER_JSON_contract_hash (contract_terms,
    555                                 &pch->h_contract_terms))
    556   {
    557     GNUNET_break (0);
    558     GNUNET_free (pch->base_url);
    559     GNUNET_free (pch);
    560     return NULL;
    561   }
    562   GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
    563                                       &pch->purse_pub.eddsa_pub);
    564   GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
    565                                       &pch->merge_pub.eddsa_pub);
    566   pch->num_deposits = num_deposits;
    567   pch->deposits = GNUNET_new_array (num_deposits,
    568                                     struct Deposit);
    569   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    570               "Signing with URL `%s'\n",
    571               url);
    572   for (unsigned int i = 0; i<num_deposits; i++)
    573   {
    574     const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
    575     const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
    576     struct Deposit *d = &pch->deposits[i];
    577 
    578     if (NULL != acp)
    579     {
    580       TALER_age_commitment_hash (&acp->commitment,
    581                                  &d->ahac);
    582       d->have_age = true;
    583       if (GNUNET_OK !=
    584           TALER_age_commitment_attest (acp,
    585                                        pch->min_age,
    586                                        &d->attest))
    587       {
    588         GNUNET_break (0);
    589         GNUNET_array_grow (pch->deposits,
    590                            pch->num_deposits,
    591                            0);
    592         GNUNET_free (pch->base_url);
    593         GNUNET_free (pch);
    594         return NULL;
    595       }
    596     }
    597     d->contribution = deposit->amount;
    598     d->h_denom_pub = deposit->h_denom_pub;
    599     TALER_denom_sig_copy (&d->denom_sig,
    600                           &deposit->denom_sig);
    601     GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
    602                                         &d->coin_pub.eddsa_pub);
    603     TALER_wallet_purse_deposit_sign (
    604       url,
    605       &pch->purse_pub,
    606       &deposit->amount,
    607       &d->h_denom_pub,
    608       &d->ahac,
    609       &deposit->coin_priv,
    610       &d->coin_sig);
    611   }
    612   pch->keys = TALER_EXCHANGE_keys_incref (keys);
    613   return pch;
    614 }
    615 
    616 
    617 enum GNUNET_GenericReturnValue
    618 TALER_EXCHANGE_post_purses_create_set_options_ (
    619   struct TALER_EXCHANGE_PostPursesCreateHandle *ppch,
    620   unsigned int num_options,
    621   const struct TALER_EXCHANGE_PostPursesCreateOptionValue options[])
    622 {
    623   for (unsigned int i = 0; i < num_options; i++)
    624   {
    625     const struct TALER_EXCHANGE_PostPursesCreateOptionValue *opt = &options[i];
    626 
    627     switch (opt->option)
    628     {
    629     case TALER_EXCHANGE_POST_PURSES_CREATE_OPTION_END:
    630       return GNUNET_OK;
    631     case TALER_EXCHANGE_POST_PURSES_CREATE_OPTION_UPLOAD_CONTRACT:
    632       ppch->options.upload_contract = true;
    633       break;
    634     }
    635   }
    636   return GNUNET_OK;
    637 }
    638 
    639 
    640 enum TALER_ErrorCode
    641 TALER_EXCHANGE_post_purses_create_start (
    642   struct TALER_EXCHANGE_PostPursesCreateHandle *pch,
    643   TALER_EXCHANGE_PostPursesCreateCallback cb,
    644   TALER_EXCHANGE_POST_PURSES_CREATE_RESULT_CLOSURE *cb_cls)
    645 {
    646   char arg_str[sizeof (pch->purse_pub) * 2 + 32];
    647   CURL *eh;
    648   json_t *deposit_arr;
    649   json_t *body;
    650 
    651   pch->cb = cb;
    652   pch->cb_cls = cb_cls;
    653   deposit_arr = json_array ();
    654   GNUNET_assert (NULL != deposit_arr);
    655 
    656   for (unsigned int i = 0; i<pch->num_deposits; i++)
    657   {
    658     struct Deposit *d = &pch->deposits[i];
    659     struct TALER_AgeCommitmentHashP *aghp = NULL;
    660     struct TALER_AgeAttestationP *attestp = NULL;
    661     json_t *jdeposit;
    662 
    663     if (d->have_age)
    664     {
    665       aghp = &d->ahac;
    666       attestp = &d->attest;
    667     }
    668     jdeposit = GNUNET_JSON_PACK (
    669       GNUNET_JSON_pack_allow_null (
    670         GNUNET_JSON_pack_data_auto ("h_age_commitment",
    671                                     aghp)),
    672       GNUNET_JSON_pack_allow_null (
    673         GNUNET_JSON_pack_data_auto ("age_attestation",
    674                                     attestp)),
    675       TALER_JSON_pack_amount ("amount",
    676                               &d->contribution),
    677       GNUNET_JSON_pack_data_auto ("denom_pub_hash",
    678                                   &d->h_denom_pub),
    679       TALER_JSON_pack_denom_sig ("ub_sig",
    680                                  &d->denom_sig),
    681       GNUNET_JSON_pack_data_auto ("coin_sig",
    682                                   &d->coin_sig),
    683       GNUNET_JSON_pack_data_auto ("coin_pub",
    684                                   &d->coin_pub));
    685     GNUNET_assert (0 ==
    686                    json_array_append_new (deposit_arr,
    687                                           jdeposit));
    688 
    689   }
    690 
    691   if (pch->options.upload_contract)
    692   {
    693     TALER_CRYPTO_contract_encrypt_for_merge (
    694       &pch->purse_pub,
    695       &pch->contract_priv,
    696       &pch->merge_priv,
    697       pch->contract_terms,
    698       &pch->econtract.econtract,
    699       &pch->econtract.econtract_size);
    700     GNUNET_CRYPTO_ecdhe_key_get_public (
    701       &pch->contract_priv.ecdhe_priv,
    702       &pch->econtract.contract_pub.ecdhe_pub);
    703     TALER_wallet_econtract_upload_sign (
    704       pch->econtract.econtract,
    705       pch->econtract.econtract_size,
    706       &pch->econtract.contract_pub,
    707       &pch->purse_priv,
    708       &pch->econtract.econtract_sig);
    709   }
    710   TALER_wallet_purse_create_sign (pch->purse_expiration,
    711                                   &pch->h_contract_terms,
    712                                   &pch->merge_pub,
    713                                   pch->min_age,
    714                                   &pch->purse_value_after_fees,
    715                                   &pch->purse_priv,
    716                                   &pch->purse_sig);
    717 
    718   body = GNUNET_JSON_PACK (
    719     TALER_JSON_pack_amount ("amount",
    720                             &pch->purse_value_after_fees),
    721     GNUNET_JSON_pack_uint64 ("min_age",
    722                              pch->min_age),
    723     GNUNET_JSON_pack_allow_null (
    724       TALER_JSON_pack_econtract ("econtract",
    725                                  pch->options.upload_contract
    726                                  ? &pch->econtract
    727                                  : NULL)),
    728     GNUNET_JSON_pack_data_auto ("purse_sig",
    729                                 &pch->purse_sig),
    730     GNUNET_JSON_pack_data_auto ("merge_pub",
    731                                 &pch->merge_pub),
    732     GNUNET_JSON_pack_data_auto ("h_contract_terms",
    733                                 &pch->h_contract_terms),
    734     GNUNET_JSON_pack_timestamp ("purse_expiration",
    735                                 pch->purse_expiration),
    736     GNUNET_JSON_pack_array_steal ("deposits",
    737                                   deposit_arr));
    738   if (NULL == body)
    739   {
    740     GNUNET_break (0);
    741     return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
    742   }
    743 
    744   {
    745     char pub_str[sizeof (pch->purse_pub) * 2];
    746     char *end;
    747 
    748     end = GNUNET_STRINGS_data_to_string (
    749       &pch->purse_pub,
    750       sizeof (pch->purse_pub),
    751       pub_str,
    752       sizeof (pub_str));
    753     *end = '\0';
    754     GNUNET_snprintf (arg_str,
    755                      sizeof (arg_str),
    756                      "purses/%s/create",
    757                      pub_str);
    758   }
    759   pch->url = TALER_url_join (pch->base_url,
    760                              arg_str,
    761                              NULL);
    762   if (NULL == pch->url)
    763   {
    764     GNUNET_break (0);
    765     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    766   }
    767   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    768               "URL for purse create with deposit: `%s'\n",
    769               pch->url);
    770   eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
    771   if ( (NULL == eh) ||
    772        (GNUNET_OK !=
    773         TALER_curl_easy_post (&pch->post_ctx,
    774                               eh,
    775                               body)) )
    776   {
    777     GNUNET_break (0);
    778     if (NULL != eh)
    779       curl_easy_cleanup (eh);
    780     json_decref (body);
    781     return TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
    782   }
    783   json_decref (body);
    784   pch->job = GNUNET_CURL_job_add2 (pch->ctx,
    785                                    eh,
    786                                    pch->post_ctx.headers,
    787                                    &handle_purse_create_deposit_finished,
    788                                    pch);
    789   if (NULL == pch->job)
    790     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    791   return TALER_EC_NONE;
    792 }
    793 
    794 
    795 void
    796 TALER_EXCHANGE_post_purses_create_cancel (
    797   struct TALER_EXCHANGE_PostPursesCreateHandle *pch)
    798 {
    799   if (NULL != pch->job)
    800   {
    801     GNUNET_CURL_job_cancel (pch->job);
    802     pch->job = NULL;
    803   }
    804   json_decref (pch->contract_terms);
    805   GNUNET_free (pch->econtract.econtract);
    806   GNUNET_free (pch->base_url);
    807   GNUNET_free (pch->url);
    808 
    809   for (unsigned int i = 0; i<pch->num_deposits; i++)
    810   {
    811     struct Deposit *d = &pch->deposits[i];
    812 
    813     TALER_denom_sig_free (&d->denom_sig);
    814   }
    815   GNUNET_array_grow (pch->deposits,
    816                      pch->num_deposits,
    817                      0);
    818   TALER_EXCHANGE_keys_decref (pch->keys);
    819   TALER_curl_easy_post_finished (&pch->post_ctx);
    820   GNUNET_free (pch);
    821 }
    822 
    823 
    824 /* end of exchange_api_post-purses-PURSE_PUB-create.c */