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-melt.c (19997B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 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-melt.c
     19  * @brief Implementation of the /melt request
     20  * @author Özgür Kesim
     21  */
     22 #include <jansson.h>
     23 #include <microhttpd.h> /* just for HTTP status codes */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_json_lib.h"
     28 #include "exchange_api_common.h"
     29 #include "exchange_api_handle.h"
     30 #include "taler/taler_signatures.h"
     31 #include "exchange_api_curl_defaults.h"
     32 #include "exchange_api_refresh_common.h"
     33 
     34 
     35 /**
     36  * @brief A /melt Handle
     37  */
     38 struct TALER_EXCHANGE_PostMeltHandle
     39 {
     40 
     41   /**
     42    * The keys of the this request handle will use
     43    */
     44   struct TALER_EXCHANGE_Keys *keys;
     45 
     46   /**
     47    * The url for this request.
     48    */
     49   char *url;
     50 
     51   /**
     52    * The exchange base url.
     53    */
     54   char *exchange_url;
     55 
     56   /**
     57    * Curl context.
     58    */
     59   struct GNUNET_CURL_Context *cctx;
     60 
     61   /**
     62    * Context for #TEH_curl_easy_post(). Keeps the data that must
     63    * persist for Curl to make the upload.
     64    */
     65   struct TALER_CURL_PostContext ctx;
     66 
     67   /**
     68    * Handle for the request.
     69    */
     70   struct GNUNET_CURL_Job *job;
     71 
     72   /**
     73    * Function to call with refresh melt failure results.
     74    */
     75   TALER_EXCHANGE_PostMeltCallback melt_cb;
     76 
     77   /**
     78    * Closure for @e result_cb and @e melt_failure_cb.
     79    */
     80   void *melt_cb_cls;
     81 
     82   /**
     83    * Actual information about the melt operation.
     84    */
     85   struct MeltData md;
     86 
     87   /**
     88    * The seed for the melt operation.
     89    */
     90   struct TALER_PublicRefreshMasterSeedP rms;
     91 
     92   /**
     93    * Details about the characteristics of the requested melt operation.
     94    */
     95   const struct TALER_EXCHANGE_MeltInput *rd;
     96 
     97   /**
     98    * True, if no blinding_seed is needed (no CS denominations involved)
     99    */
    100   bool no_blinding_seed;
    101 
    102   /**
    103    * If @e no_blinding_seed is false, the blinding seed for the intermediate
    104    * call to /blinding-prepare, in order to retrieve the R-values from the
    105    * exchange for the blind Clause-Schnorr signature.
    106    */
    107   struct TALER_BlindingMasterSeedP blinding_seed;
    108 
    109   /**
    110    * Array of `num_fresh_denom_pubs` per-coin values
    111    * returned from melt operation.
    112    */
    113   struct TALER_ExchangeBlindingValues *melt_blinding_values;
    114 
    115   /**
    116    * Handle for the preflight request, or NULL.
    117    */
    118   struct TALER_EXCHANGE_PostBlindingPrepareHandle *bpr;
    119 
    120   /**
    121    * Public key of the coin being melted.
    122    */
    123   struct TALER_CoinSpendPublicKeyP coin_pub;
    124 
    125   /**
    126    * Signature affirming the melt.
    127    */
    128   struct TALER_CoinSpendSignatureP coin_sig;
    129 
    130   /**
    131    * @brief Public information about the coin's denomination key
    132    */
    133   const struct TALER_EXCHANGE_DenomPublicKey *dki;
    134 
    135   /**
    136    * Gamma value chosen by the exchange during melt.
    137    */
    138   uint32_t noreveal_index;
    139 
    140 };
    141 
    142 
    143 /**
    144  * Verify that the signature on the "200 OK" response
    145  * from the exchange is valid.
    146  *
    147  * @param[in,out] mh melt handle
    148  * @param json json reply with the signature
    149  * @param[out] exchange_pub public key of the exchange used for the signature
    150  * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
    151  */
    152 static enum GNUNET_GenericReturnValue
    153 verify_melt_signature_ok (struct TALER_EXCHANGE_PostMeltHandle *mh,
    154                           const json_t *json,
    155                           struct TALER_ExchangePublicKeyP *exchange_pub)
    156 {
    157   struct TALER_ExchangeSignatureP exchange_sig;
    158   struct GNUNET_JSON_Specification spec[] = {
    159     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    160                                  &exchange_sig),
    161     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    162                                  exchange_pub),
    163     GNUNET_JSON_spec_uint32 ("noreveal_index",
    164                              &mh->noreveal_index),
    165     GNUNET_JSON_spec_end ()
    166   };
    167 
    168   if (GNUNET_OK !=
    169       GNUNET_JSON_parse (json,
    170                          spec,
    171                          NULL, NULL))
    172   {
    173     GNUNET_break_op (0);
    174     return GNUNET_SYSERR;
    175   }
    176   /* check that exchange signing key is permitted */
    177   if (GNUNET_OK !=
    178       TALER_EXCHANGE_test_signing_key (mh->keys,
    179                                        exchange_pub))
    180   {
    181     GNUNET_break_op (0);
    182     return GNUNET_SYSERR;
    183   }
    184 
    185   /* check that noreveal index is in permitted range */
    186   if (TALER_CNC_KAPPA <= mh->noreveal_index)
    187   {
    188     GNUNET_break_op (0);
    189     return GNUNET_SYSERR;
    190   }
    191 
    192   if (GNUNET_OK !=
    193       TALER_exchange_online_melt_confirmation_verify (
    194         &mh->md.rc,
    195         mh->noreveal_index,
    196         exchange_pub,
    197         &exchange_sig))
    198   {
    199     GNUNET_break_op (0);
    200     return GNUNET_SYSERR;
    201   }
    202   return GNUNET_OK;
    203 }
    204 
    205 
    206 /**
    207  * Function called when we're done processing the
    208  * HTTP /melt request.
    209  *
    210  * @param cls the `struct TALER_EXCHANGE_MeltHandle`
    211  * @param response_code HTTP response code, 0 on error
    212  * @param response parsed JSON result, NULL on error
    213  */
    214 static void
    215 handle_melt_finished (void *cls,
    216                       long response_code,
    217                       const void *response)
    218 {
    219   struct TALER_EXCHANGE_PostMeltHandle *mh = cls;
    220   const json_t *j = response;
    221   struct TALER_EXCHANGE_PostMeltResponse mr = {
    222     .hr.reply = j,
    223     .hr.http_status = (unsigned int) response_code
    224   };
    225 
    226   mh->job = NULL;
    227   switch (response_code)
    228   {
    229   case 0:
    230     mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    231     break;
    232   case MHD_HTTP_OK:
    233     if (GNUNET_OK !=
    234         verify_melt_signature_ok (mh,
    235                                   j,
    236                                   &mr.details.ok.sign_key))
    237     {
    238       GNUNET_break_op (0);
    239       mr.hr.http_status = 0;
    240       mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
    241       break;
    242     }
    243     mr.details.ok.noreveal_index = mh->noreveal_index;
    244     mr.details.ok.num_melt_blinding_values = mh->rd->num_fresh_denom_pubs;
    245     mr.details.ok.melt_blinding_values = mh->melt_blinding_values;
    246     mr.details.ok.blinding_seed = mh->no_blinding_seed
    247                                                ? NULL
    248                                                : &mh->blinding_seed;
    249     mh->melt_cb (mh->melt_cb_cls,
    250                  &mr);
    251     mh->melt_cb = NULL;
    252     break;
    253   case MHD_HTTP_BAD_REQUEST:
    254     /* This should never happen, either us or the exchange is buggy
    255        (or API version conflict); just pass JSON reply to the application */
    256     mr.hr.ec = TALER_JSON_get_error_code (j);
    257     mr.hr.hint = TALER_JSON_get_error_hint (j);
    258     break;
    259   case MHD_HTTP_CONFLICT:
    260     mr.hr.ec = TALER_JSON_get_error_code (j);
    261     mr.hr.hint = TALER_JSON_get_error_hint (j);
    262     break;
    263   case MHD_HTTP_FORBIDDEN:
    264     /* Nothing really to verify, exchange says one of the signatures is
    265        invalid; assuming we checked them, this should never happen, we
    266        should pass the JSON reply to the application */
    267     mr.hr.ec = TALER_JSON_get_error_code (j);
    268     mr.hr.hint = TALER_JSON_get_error_hint (j);
    269     break;
    270   case MHD_HTTP_NOT_FOUND:
    271     /* Nothing really to verify, this should never
    272        happen, we should pass the JSON reply to the application */
    273     mr.hr.ec = TALER_JSON_get_error_code (j);
    274     mr.hr.hint = TALER_JSON_get_error_hint (j);
    275     break;
    276   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    277     /* Server had an internal issue; we should retry, but this API
    278        leaves this to the application */
    279     mr.hr.ec = TALER_JSON_get_error_code (j);
    280     mr.hr.hint = TALER_JSON_get_error_hint (j);
    281     break;
    282   default:
    283     /* unexpected response code */
    284     mr.hr.ec = TALER_JSON_get_error_code (j);
    285     mr.hr.hint = TALER_JSON_get_error_hint (j);
    286     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    287                 "Unexpected response code %u/%d for exchange melt\n",
    288                 (unsigned int) response_code,
    289                 mr.hr.ec);
    290     GNUNET_break_op (0);
    291     break;
    292   }
    293   if (NULL != mh->melt_cb)
    294     mh->melt_cb (mh->melt_cb_cls,
    295                  &mr);
    296   TALER_EXCHANGE_post_melt_cancel (mh);
    297 }
    298 
    299 
    300 /**
    301  * Start the actual melt operation, now that we have
    302  * the exchange's input values.
    303  *
    304  * @param[in,out] mh melt operation to run
    305  * @return #GNUNET_OK if we could start the operation
    306  */
    307 static enum GNUNET_GenericReturnValue
    308 start_melt (struct TALER_EXCHANGE_PostMeltHandle *mh)
    309 {
    310   json_t *j_request_body;
    311   json_t *j_transfer_pubs;
    312   json_t *j_coin_evs;
    313   CURL *eh;
    314   struct TALER_DenominationHashP h_denom_pub;
    315 
    316   if (GNUNET_OK !=
    317       TALER_EXCHANGE_get_melt_data (&mh->rms,
    318                                     mh->rd,
    319                                     mh->no_blinding_seed
    320                                     ? NULL
    321                                     : &mh->blinding_seed,
    322                                     mh->melt_blinding_values,
    323                                     &mh->md))
    324   {
    325     GNUNET_break (0);
    326     return GNUNET_SYSERR;
    327   }
    328   TALER_denom_pub_hash (
    329     &mh->md.melted_coin.pub_key,
    330     &h_denom_pub);
    331   TALER_wallet_melt_sign (
    332     &mh->md.melted_coin.melt_amount_with_fee,
    333     &mh->md.melted_coin.fee_melt,
    334     &mh->md.rc,
    335     &h_denom_pub,
    336     mh->md.melted_coin.h_age_commitment,
    337     &mh->md.melted_coin.coin_priv,
    338     &mh->coin_sig);
    339   GNUNET_CRYPTO_eddsa_key_get_public (
    340     &mh->md.melted_coin.coin_priv.eddsa_priv,
    341     &mh->coin_pub.eddsa_pub);
    342   mh->dki = TALER_EXCHANGE_get_denomination_key (
    343     mh->keys,
    344     &mh->md.melted_coin.pub_key);
    345   j_request_body = GNUNET_JSON_PACK (
    346     GNUNET_JSON_pack_data_auto ("old_coin_pub",
    347                                 &mh->coin_pub),
    348     GNUNET_JSON_pack_data_auto ("old_denom_pub_h",
    349                                 &h_denom_pub),
    350     TALER_JSON_pack_denom_sig ("old_denom_sig",
    351                                &mh->md.melted_coin.sig),
    352     GNUNET_JSON_pack_data_auto ("confirm_sig",
    353                                 &mh->coin_sig),
    354     TALER_JSON_pack_amount ("value_with_fee",
    355                             &mh->md.melted_coin.melt_amount_with_fee),
    356     GNUNET_JSON_pack_allow_null (
    357       (NULL != mh->md.melted_coin.h_age_commitment)
    358       ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h",
    359                                     mh->md.melted_coin.h_age_commitment)
    360       : GNUNET_JSON_pack_string ("old_age_commitment_h",
    361                                  NULL)),
    362     GNUNET_JSON_pack_data_auto ("refresh_seed",
    363                                 &mh->md.refresh_seed),
    364     GNUNET_JSON_pack_allow_null (
    365       (mh->md.no_blinding_seed)
    366       ? GNUNET_JSON_pack_string ("blinding_seed",
    367                                  NULL)
    368       : GNUNET_JSON_pack_data_auto ("blinding_seed",
    369                                     &mh->md.blinding_seed)),
    370     TALER_JSON_pack_array_of_data_auto ("denoms_h",
    371                                         mh->md.num_fresh_coins,
    372                                         mh->md.denoms_h)
    373     );
    374   GNUNET_assert (NULL != j_request_body);
    375   GNUNET_assert (NULL !=
    376                  (j_transfer_pubs = json_array ()));
    377   GNUNET_assert (NULL !=
    378                  (j_coin_evs = json_array ()));
    379   /**
    380    * Fill the kappa array of coin envelopes and
    381    * the array of transfer pubs.
    382    */
    383   for (uint8_t k=0; k<TALER_CNC_KAPPA; k++)
    384   {
    385     json_t *j_envs;
    386     json_t *j_tbs = GNUNET_JSON_PACK (
    387       TALER_JSON_pack_array_of_data_auto ("_",
    388                                           mh->md.num_fresh_coins,
    389                                           mh->md.kappa_transfer_pubs[k])
    390       );
    391 
    392     GNUNET_assert (NULL != (j_envs = json_array ()));
    393     GNUNET_assert (NULL != j_tbs);
    394 
    395     for (size_t i = 0; i < mh->md.num_fresh_coins; i++)
    396     {
    397       json_t *j_coin = GNUNET_JSON_PACK (
    398         TALER_JSON_pack_blinded_planchet (NULL,
    399                                           &mh->md.kappa_blinded_planchets[k][i])
    400         );
    401       GNUNET_assert (NULL != j_coin);
    402       GNUNET_assert (0 ==
    403                      json_array_append_new (j_envs,
    404                                             j_coin));
    405     }
    406     GNUNET_assert (0 ==
    407                    json_array_append_new (j_coin_evs,
    408                                           j_envs));
    409     GNUNET_assert (0 ==
    410                    json_array_append (j_transfer_pubs,
    411                                       json_object_get (j_tbs,
    412                                                        "_")));
    413     json_decref (j_tbs);
    414   }
    415   GNUNET_assert (0 ==
    416                  json_object_set_new (j_request_body,
    417                                       "coin_evs",
    418                                       j_coin_evs));
    419   GNUNET_assert (0 ==
    420                  json_object_set_new (j_request_body,
    421                                       "transfer_pubs",
    422                                       j_transfer_pubs));
    423   /* and now we can at last begin the actual request handling */
    424   mh->url = TALER_url_join (mh->exchange_url,
    425                             "melt",
    426                             NULL);
    427   if (NULL == mh->url)
    428   {
    429     json_decref (j_request_body);
    430     return GNUNET_SYSERR;
    431   }
    432   eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
    433   if ( (NULL == eh) ||
    434        (GNUNET_OK !=
    435         TALER_curl_easy_post (&mh->ctx,
    436                               eh,
    437                               j_request_body)) )
    438   {
    439     GNUNET_break (0);
    440     if (NULL != eh)
    441       curl_easy_cleanup (eh);
    442     json_decref (j_request_body);
    443     return GNUNET_SYSERR;
    444   }
    445   json_decref (j_request_body);
    446   mh->job = GNUNET_CURL_job_add2 (mh->cctx,
    447                                   eh,
    448                                   mh->ctx.headers,
    449                                   &handle_melt_finished,
    450                                   mh);
    451   return GNUNET_OK;
    452 }
    453 
    454 
    455 /**
    456  * The melt request @a mh failed, return an error to
    457  * the application and cancel the operation.
    458  *
    459  * @param[in] mh melt request that failed
    460  * @param ec error code to fail with
    461  */
    462 static void
    463 fail_mh (struct TALER_EXCHANGE_PostMeltHandle *mh,
    464          enum TALER_ErrorCode ec)
    465 {
    466   struct TALER_EXCHANGE_PostMeltResponse mr = {
    467     .hr.ec = ec
    468   };
    469 
    470   mh->melt_cb (mh->melt_cb_cls,
    471                &mr);
    472   TALER_EXCHANGE_post_melt_cancel (mh);
    473 }
    474 
    475 
    476 /**
    477  * Callbacks of this type are used to serve the result of submitting a
    478  * /blinding-prepare request to a exchange.
    479  *
    480  * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *`
    481  * @param bpr response details
    482  */
    483 static void
    484 blinding_prepare_cb (void *cls,
    485                      const struct TALER_EXCHANGE_PostBlindingPrepareResponse *
    486                      bpr)
    487 {
    488   struct TALER_EXCHANGE_PostMeltHandle *mh = cls;
    489   unsigned int nks_off = 0;
    490 
    491   mh->bpr = NULL;
    492   if (MHD_HTTP_OK != bpr->hr.http_status)
    493   {
    494     struct TALER_EXCHANGE_PostMeltResponse mr = {
    495       .hr = bpr->hr
    496     };
    497 
    498     mr.hr.hint = "/blinding-prepare failed";
    499     mh->melt_cb (mh->melt_cb_cls,
    500                  &mr);
    501     TALER_EXCHANGE_post_melt_cancel (mh);
    502     return;
    503   }
    504   for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
    505   {
    506     const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
    507       &mh->rd->fresh_denom_pubs[i];
    508     struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i];
    509 
    510     switch (fresh_pk->key.bsign_pub_key->cipher)
    511     {
    512     case GNUNET_CRYPTO_BSA_INVALID:
    513       GNUNET_break (0);
    514       fail_mh (mh,
    515                TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
    516       return;
    517     case GNUNET_CRYPTO_BSA_RSA:
    518       break;
    519     case GNUNET_CRYPTO_BSA_CS:
    520       TALER_denom_ewv_copy (wv,
    521                             &bpr->details.ok.blinding_values[nks_off]);
    522       nks_off++;
    523       break;
    524     }
    525   }
    526   if (GNUNET_OK !=
    527       start_melt (mh))
    528   {
    529     GNUNET_break (0);
    530     fail_mh (mh,
    531              TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
    532     return;
    533   }
    534 }
    535 
    536 
    537 struct TALER_EXCHANGE_PostMeltHandle *
    538 TALER_EXCHANGE_post_melt_create (
    539   struct GNUNET_CURL_Context *ctx,
    540   const char *url,
    541   struct TALER_EXCHANGE_Keys *keys,
    542   const struct TALER_PublicRefreshMasterSeedP *rms,
    543   const struct TALER_EXCHANGE_MeltInput *rd)
    544 {
    545   struct TALER_EXCHANGE_PostMeltHandle *mh;
    546 
    547   if (0 == rd->num_fresh_denom_pubs)
    548   {
    549     GNUNET_break (0);
    550     return NULL;
    551   }
    552   mh = GNUNET_new (struct TALER_EXCHANGE_PostMeltHandle);
    553   mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */
    554   mh->cctx = ctx;
    555   mh->exchange_url = GNUNET_strdup (url);
    556   mh->rd = rd;
    557   mh->rms = *rms;
    558   mh->no_blinding_seed = true;
    559   mh->melt_blinding_values =
    560     GNUNET_new_array (rd->num_fresh_denom_pubs,
    561                       struct TALER_ExchangeBlindingValues);
    562   for (unsigned int i = 0; i < rd->num_fresh_denom_pubs; i++)
    563   {
    564     const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
    565       &rd->fresh_denom_pubs[i];
    566 
    567     switch (fresh_pk->key.bsign_pub_key->cipher)
    568     {
    569     case GNUNET_CRYPTO_BSA_INVALID:
    570       GNUNET_break (0);
    571       GNUNET_free (mh->melt_blinding_values);
    572       GNUNET_free (mh->exchange_url);
    573       GNUNET_free (mh);
    574       return NULL;
    575     case GNUNET_CRYPTO_BSA_RSA:
    576       TALER_denom_ewv_copy (&mh->melt_blinding_values[i],
    577                             TALER_denom_ewv_rsa_singleton ());
    578       break;
    579     case GNUNET_CRYPTO_BSA_CS:
    580       mh->no_blinding_seed = false;
    581       break;
    582     }
    583   }
    584   mh->keys = TALER_EXCHANGE_keys_incref (keys);
    585   return mh;
    586 }
    587 
    588 
    589 enum TALER_ErrorCode
    590 TALER_EXCHANGE_post_melt_start (
    591   struct TALER_EXCHANGE_PostMeltHandle *mh,
    592   TALER_EXCHANGE_PostMeltCallback melt_cb,
    593   TALER_EXCHANGE_POST_MELT_RESULT_CLOSURE *melt_cb_cls)
    594 {
    595   mh->melt_cb = melt_cb;
    596   mh->melt_cb_cls = melt_cb_cls;
    597 
    598   if (! mh->no_blinding_seed)
    599   {
    600     struct TALER_EXCHANGE_NonceKey nks[
    601       GNUNET_NZL (mh->rd->num_fresh_denom_pubs)];
    602     unsigned int nks_off = 0;
    603 
    604     for (unsigned int i = 0; i < mh->rd->num_fresh_denom_pubs; i++)
    605     {
    606       const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
    607         &mh->rd->fresh_denom_pubs[i];
    608 
    609       if (GNUNET_CRYPTO_BSA_CS ==
    610           fresh_pk->key.bsign_pub_key->cipher)
    611       {
    612         nks[nks_off].pk = fresh_pk;
    613         nks[nks_off].cnc_num = i;
    614         nks_off++;
    615       }
    616     }
    617     TALER_cs_refresh_seed_to_blinding_seed (
    618       &mh->rms,
    619       &mh->md.melted_coin.coin_priv,
    620       &mh->blinding_seed);
    621     mh->bpr = TALER_EXCHANGE_post_blinding_prepare_for_melt_create (
    622       mh->cctx,
    623       mh->exchange_url,
    624       &mh->blinding_seed,
    625       nks_off,
    626       nks);
    627     if (NULL == mh->bpr)
    628     {
    629       GNUNET_break (0);
    630       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    631     }
    632     {
    633       enum TALER_ErrorCode ec;
    634 
    635       ec = TALER_EXCHANGE_post_blinding_prepare_start (mh->bpr,
    636                                                        &blinding_prepare_cb,
    637                                                        mh);
    638       if (TALER_EC_NONE != ec)
    639       {
    640         GNUNET_break (0);
    641         TALER_EXCHANGE_post_blinding_prepare_cancel (mh->bpr);
    642         mh->bpr = NULL;
    643         return ec;
    644       }
    645     }
    646     return TALER_EC_NONE;
    647   }
    648   if (GNUNET_OK !=
    649       start_melt (mh))
    650   {
    651     GNUNET_break (0);
    652     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    653   }
    654   return TALER_EC_NONE;
    655 }
    656 
    657 
    658 void
    659 TALER_EXCHANGE_post_melt_cancel (struct TALER_EXCHANGE_PostMeltHandle *mh)
    660 {
    661   for (unsigned int i = 0; i < mh->rd->num_fresh_denom_pubs; i++)
    662     TALER_denom_ewv_free (&mh->melt_blinding_values[i]);
    663   if (NULL != mh->job)
    664   {
    665     GNUNET_CURL_job_cancel (mh->job);
    666     mh->job = NULL;
    667   }
    668   if (NULL != mh->bpr)
    669   {
    670     TALER_EXCHANGE_post_blinding_prepare_cancel (mh->bpr);
    671     mh->bpr = NULL;
    672   }
    673   TALER_EXCHANGE_free_melt_data (&mh->md); /* does not free 'md' itself */
    674   GNUNET_free (mh->melt_blinding_values);
    675   GNUNET_free (mh->url);
    676   GNUNET_free (mh->exchange_url);
    677   TALER_curl_easy_post_finished (&mh->ctx);
    678   TALER_EXCHANGE_keys_decref (mh->keys);
    679   GNUNET_free (mh);
    680 }
    681 
    682 
    683 /* end of exchange_api_melt.c */