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


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