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


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