exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_reveal-melt.c (29386B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023,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 Affero 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-httpd_reveal-melt.c
     18  * @brief Handle /reveal-melt requests
     19  * @author Özgür Kesim
     20  */
     21 #include "taler/platform.h"
     22 #include <gnunet/gnunet_common.h>
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <jansson.h>
     25 #include <microhttpd.h>
     26 #include "taler-exchange-httpd_metrics.h"
     27 #include "taler/taler_error_codes.h"
     28 #include "taler/taler_exchangedb_plugin.h"
     29 #include "taler/taler_mhd_lib.h"
     30 #include "taler-exchange-httpd_mhd.h"
     31 #include "taler-exchange-httpd_reveal-melt.h"
     32 #include "taler-exchange-httpd_responses.h"
     33 #include "taler-exchange-httpd_keys.h"
     34 
     35 #define KAPPA_MINUS_1  (TALER_CNC_KAPPA - 1)
     36 
     37 
     38 /**
     39  * State for an /reveal-melt operation.
     40  */
     41 struct MeltRevealContext
     42 {
     43 
     44   /**
     45    * Commitment for the melt operation, previously called by the
     46    * client.
     47    */
     48   struct TALER_RefreshCommitmentP rc;
     49 
     50   /**
     51    * The data from the original melt.  Will be retrieved from
     52    * the DB via @a rc.
     53    */
     54   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh;
     55 
     56   /**
     57    * True, if @e signatures were not provided in the request.
     58    */
     59   bool no_signatures;
     60 
     61   /**
     62    * @since v27
     63    * @deprecated after vDOLDPLUS
     64    *
     65    * TALER_CNC_KAPPA-1 disclosed signatures for public refresh nonces.
     66    */
     67   struct TALER_PrivateRefreshNonceSignatureP signatures[KAPPA_MINUS_1];
     68 
     69   /**
     70    * True, if @e transfer_secret_seeds were not provided in the request.
     71    */
     72   bool no_transfer_secret_seeds;
     73 
     74   /**
     75    * @since vDOLDPLUS
     76    *
     77    * The transfer secret seeds for the revealed batches of coins.
     78    */
     79   struct TALER_PrivateRefreshBatchSeedP transfer_secret_seeds[KAPPA_MINUS_1];
     80 
     81   /**
     82    * True, if no @e age_commitment was provided
     83    */
     84   bool no_age_commitment;
     85 
     86   /**
     87    * If @e no_age_commitment is false, the age commitment of
     88    * the old coin.  Needed to ensure that the age commitment
     89    * is applied correctly to the fresh coins.
     90    */
     91   struct TALER_AgeCommitment age_commitment;
     92 };
     93 
     94 
     95 /**
     96  * Check if the request belongs to an existing refresh request.
     97  * If so, sets the refresh object with the request data.
     98  * Otherwise, it queues an appropriate MHD response.
     99  *
    100  * @param connection The HTTP connection to the client
    101  * @param rc Original commitment value sent with the melt request
    102  * @param[out] refresh Data from the original refresh request
    103  * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
    104  * @return #GNUNET_OK if the refresh request has been found,
    105  *   #GNUNET_SYSERR if we did not find the request in the DB
    106  */
    107 static enum GNUNET_GenericReturnValue
    108 find_original_refresh (
    109   struct MHD_Connection *connection,
    110   const struct TALER_RefreshCommitmentP *rc,
    111   struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh,
    112   MHD_RESULT *result)
    113 {
    114   enum GNUNET_DB_QueryStatus qs;
    115 
    116   for (unsigned int retry = 0; retry < 3; retry++)
    117   {
    118     qs = TEH_plugin->get_refresh (TEH_plugin->cls,
    119                                   rc,
    120                                   refresh);
    121     switch (qs)
    122     {
    123     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    124       return GNUNET_OK; /* Only happy case */
    125     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    126       *result = TALER_MHD_reply_with_error (connection,
    127                                             MHD_HTTP_NOT_FOUND,
    128                                             TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
    129                                             NULL);
    130       return GNUNET_SYSERR;
    131     case GNUNET_DB_STATUS_HARD_ERROR:
    132       *result = TALER_MHD_reply_with_ec (connection,
    133                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
    134                                          "get_refresh");
    135       return GNUNET_SYSERR;
    136     case GNUNET_DB_STATUS_SOFT_ERROR:
    137       break; /* try again */
    138     default:
    139       GNUNET_break (0);
    140       *result = TALER_MHD_reply_with_ec (connection,
    141                                          TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    142                                          NULL);
    143       return GNUNET_SYSERR;
    144     }
    145   }
    146   /* after unsuccessful retries*/
    147   *result = TALER_MHD_reply_with_ec (connection,
    148                                      TALER_EC_GENERIC_DB_FETCH_FAILED,
    149                                      "get_refresh");
    150   return GNUNET_SYSERR;
    151 }
    152 
    153 
    154 /**
    155  * Verify that the age commitment is sound, that is, if the
    156  * previous /melt provided a hash, ensure we have the corresponding
    157  * age commitment.  Or not, if it wasn't provided.
    158  *
    159  * @param connection The MHD connection to handle
    160  * @param actx The context of the operation, only partially built at this time
    161  * @param[out] mhd_ret The result if a reply is queued for MHD
    162  * @return #GNUNET_OK on success, otherwise a reply is queued for MHD and @a mhd_ret is set
    163  */
    164 static enum GNUNET_GenericReturnValue
    165 compare_age_commitment (
    166   struct MHD_Connection *connection,
    167   struct MeltRevealContext *actx,
    168   MHD_RESULT *mhd_ret)
    169 {
    170   if (actx->no_age_commitment !=
    171       actx->refresh.coin.no_age_commitment)
    172   {
    173     *mhd_ret = TALER_MHD_reply_with_ec (connection,
    174                                         TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
    175                                         NULL);
    176     return GNUNET_SYSERR;
    177   }
    178   if (! actx->no_age_commitment)
    179   {
    180     struct TALER_AgeCommitmentHashP ach;
    181 
    182     actx->age_commitment.mask = TEH_age_restriction_config.mask;
    183     TALER_age_commitment_hash (
    184       &actx->age_commitment,
    185       &ach);
    186     if (0 != GNUNET_memcmp (
    187           &actx->refresh.coin.h_age_commitment,
    188           &ach))
    189     {
    190       GNUNET_break_op (0);
    191       *mhd_ret = TALER_MHD_reply_with_ec (connection,
    192                                           TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
    193                                           NULL);
    194       return GNUNET_SYSERR;
    195     }
    196   }
    197   return GNUNET_OK;
    198 }
    199 
    200 
    201 /**
    202  * @brief Derives an planchet from a given input and returns
    203  * blinded planchets detail
    204  *
    205  * @param connection Connection to the client
    206  * @param denom_key The denomination key
    207  * @param secret The secret to a planchet
    208  * @param r_pub The public R-values from the exchange in case of a CS denomination; might be NULL
    209  * @param nonce The derived nonce needed for CS denomination
    210  * @param old_age_commitment The age commitment of the old coin, might be NULL
    211  * @param[out] detail planchet detail to write  to write
    212  * @param[out] result On error, a HTTP-response will be queued and result set accordingly
    213  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise, with an error message
    214  * written to the client and @e result set.
    215  */
    216 static enum GNUNET_GenericReturnValue
    217 calculate_blinded_detail (
    218   struct MHD_Connection *connection,
    219   struct TEH_DenominationKey *denom_key,
    220   const struct TALER_PlanchetMasterSecretP *secret,
    221   const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub,
    222   union GNUNET_CRYPTO_BlindSessionNonce *nonce,
    223   const struct TALER_AgeCommitment *old_age_commitment,
    224   struct TALER_PlanchetDetail *detail,
    225   MHD_RESULT *result)
    226 {
    227   enum GNUNET_GenericReturnValue ret;
    228   struct TALER_AgeCommitmentHashP ach;
    229   bool no_age_commitment = (NULL == old_age_commitment);
    230 
    231   /* calculate age commitment hash */
    232   if (! no_age_commitment)
    233   {
    234     struct TALER_AgeCommitment nac;
    235 
    236     TALER_age_commitment_derive_from_secret (old_age_commitment,
    237                                              secret,
    238                                              &nac);
    239     TALER_age_commitment_hash (&nac,
    240                                &ach);
    241     TALER_age_commitment_free (&nac);
    242   }
    243 
    244   /* Next: calculate planchet */
    245   {
    246     struct TALER_CoinPubHashP c_hash;
    247     struct TALER_CoinSpendPrivateKeyP coin_priv;
    248     union GNUNET_CRYPTO_BlindingSecretP bks;
    249     struct GNUNET_CRYPTO_BlindingInputValues bi = {
    250       .cipher = denom_key->denom_pub.bsign_pub_key->cipher
    251     };
    252     struct TALER_ExchangeBlindingValues blinding_values = {
    253       .blinding_inputs = &bi
    254     };
    255 
    256     switch (bi.cipher)
    257     {
    258     case GNUNET_CRYPTO_BSA_CS:
    259       GNUNET_assert (NULL != r_pub);
    260       GNUNET_assert (NULL != nonce);
    261       bi.details.cs_values = *r_pub;
    262       break;
    263     case GNUNET_CRYPTO_BSA_RSA:
    264       break;
    265     default:
    266       GNUNET_assert (0);
    267     }
    268 
    269     TALER_planchet_blinding_secret_create (secret,
    270                                            &blinding_values,
    271                                            &bks);
    272     TALER_planchet_setup_coin_priv (secret,
    273                                     &blinding_values,
    274                                     &coin_priv);
    275     ret = TALER_planchet_prepare (&denom_key->denom_pub,
    276                                   &blinding_values,
    277                                   &bks,
    278                                   nonce,
    279                                   &coin_priv,
    280                                   no_age_commitment
    281                                   ? NULL
    282                                   : &ach,
    283                                   &c_hash,
    284                                   detail);
    285     if (GNUNET_OK != ret)
    286     {
    287       GNUNET_break (0);
    288       *result = TALER_MHD_REPLY_JSON_PACK (connection,
    289                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
    290                                            GNUNET_JSON_pack_string (
    291                                              "details",
    292                                              "failed to prepare planchet from base key"));
    293       return ret;
    294     }
    295   }
    296   return ret;
    297 }
    298 
    299 
    300 /**
    301  * @brief Checks the validity of the disclosed signatures as follows:
    302  * - Verifies the validity of the disclosed signatures with the old coin's public key
    303  * - Derives the seeds for disclosed fresh coins
    304  * - Derives the fresh coins from the seeds
    305  * - Derives new age commitment
    306  * - Calculates the blinded coin planchet hashes
    307  * - Calculates the refresh commitment from above data
    308  * - Compares the calculated commitment with existing one
    309  *
    310  * The derivation of a fresh coin from the old coin is defined in
    311  * https://docs.taler.net/design-documents/062-pq-refresh.html
    312  *
    313  * The derivation of age-commitment from a coin's age-commitment
    314  * https://docs.taler.net/design-documents/024-age-restriction.html#melt
    315  *
    316  * @param con HTTP-connection to the client
    317  * @param rf Original refresh object from the previous /melt request
    318  * @param old_age_commitment The age commitment of the original coin
    319  * @param signatures The secrets of the disclosed coins, KAPPA_MINUS_1*num_coins many, maybe NULL
    320  * @param rev_batch_seeds The seeds for the transfer secrets of the disclosed coins, KAPPA_MINUS_1 many, maybe NULL
    321  * @param[out] result On error, a HTTP-response will be queued and result set accordingly
    322  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
    323  */
    324 static enum GNUNET_GenericReturnValue
    325 verify_commitment (
    326   struct MHD_Connection *con,
    327   const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf,
    328   const struct TALER_AgeCommitment *old_age_commitment,
    329   const struct TALER_PrivateRefreshNonceSignatureP (*signatures)[KAPPA_MINUS_1],
    330   const struct TALER_PrivateRefreshBatchSeedP (*rev_batch_seeds)[KAPPA_MINUS_1],
    331   MHD_RESULT *result)
    332 {
    333   enum GNUNET_GenericReturnValue ret;
    334   struct TEH_KeyStateHandle *keys;
    335   struct TEH_DenominationKey *denom_keys[rf->num_coins];
    336   struct TALER_DenominationHashP  *denoms_h[rf->num_coins];
    337   struct TALER_Amount total_amount;
    338   struct TALER_Amount total_fee;
    339   bool is_cs[rf->num_coins];
    340   size_t cs_count = 0;
    341 
    342   GNUNET_assert (rf->noreveal_index < TALER_CNC_KAPPA);
    343   GNUNET_assert (GNUNET_OK ==
    344                  TALER_amount_set_zero (TEH_currency,
    345                                         &total_amount));
    346   GNUNET_assert (GNUNET_OK ==
    347                  TALER_amount_set_zero (TEH_currency,
    348                                         &total_fee));
    349   memset (denom_keys,
    350           0,
    351           sizeof(denom_keys));
    352   memset (is_cs,
    353           0,
    354           sizeof(is_cs));
    355 
    356   /**
    357    * Consistency check:
    358    * If the refresh was for v27, signatures must not be NULL,
    359    * otherwise the revealed batch seeds must not be NULL.
    360    */
    361   if (rf->is_v27_refresh)
    362   {
    363     if (NULL == signatures)
    364     {
    365       *result = TALER_MHD_reply_with_error (con,
    366                                             MHD_HTTP_BAD_REQUEST,
    367                                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    368                                             "signatures missing");
    369       return GNUNET_SYSERR;
    370     }
    371   }
    372   else     /* vDOLDPLUS */
    373   {
    374     if (NULL == rev_batch_seeds)
    375     {
    376       *result = TALER_MHD_reply_with_error (con,
    377                                             MHD_HTTP_BAD_REQUEST,
    378                                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    379                                             "batch_seeds missing");
    380       return GNUNET_SYSERR;
    381     }
    382   }
    383   /**
    384    * We need the current keys in memory for the meta-data of the denominations
    385    */
    386   keys = TEH_keys_get_state ();
    387   if (NULL == keys)
    388   {
    389     *result = TALER_MHD_reply_with_ec (con,
    390                                        TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
    391                                        NULL);
    392     return GNUNET_SYSERR;
    393   }
    394 
    395   /**
    396    * Find the denomination keys from the original request to /melt
    397    * and keep track of those of type CS.
    398    */
    399   for (size_t i = 0; i < rf->num_coins; i++)
    400   {
    401     denom_keys[i] =
    402       TEH_keys_denomination_by_serial_from_state (
    403         keys,
    404         rf->denom_serials[i]);
    405     if (NULL == denom_keys[i])
    406     {
    407       GNUNET_break_op (0);
    408       *result = TALER_MHD_reply_with_ec (con,
    409                                          TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
    410                                          NULL);
    411       return GNUNET_SYSERR;
    412     }
    413 
    414     /* Accumulate amount and fees */
    415     GNUNET_assert (0 <= TALER_amount_add (&total_amount,
    416                                           &total_amount,
    417                                           &denom_keys[i]->meta.value));
    418     GNUNET_assert (0 <= TALER_amount_add (&total_fee,
    419                                           &total_fee,
    420                                           &denom_keys[i]->meta.fees.refresh));
    421 
    422     if (GNUNET_CRYPTO_BSA_CS ==
    423         denom_keys[i]->denom_pub.bsign_pub_key->cipher)
    424     {
    425       is_cs[i] = true;
    426       cs_count++;
    427     }
    428 
    429     /* Remember the hash of the public key of the denomination for later */
    430     denoms_h[i] = &denom_keys[i]->h_denom_pub;
    431   }
    432 
    433   /**
    434    * Sanity check:
    435    * The number CS denominations must match those from the /melt request
    436    */
    437   if (cs_count != rf->num_cs_r_values)
    438   {
    439     GNUNET_break (0);
    440     *result = TALER_MHD_reply_with_ec (con,
    441                                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    442                                        NULL);
    443     return GNUNET_SYSERR;
    444   }
    445   /**
    446    * First things first for v27 clients: Verify the signature of the old coin
    447    * over the refresh nonce.  This proves the ownership
    448    * for the fresh coin.
    449    */
    450   if (rf->is_v27_refresh)
    451   {
    452     size_t sig_idx = 0;
    453     struct TALER_KappaPublicRefreshNoncesP kappa_nonces;
    454 
    455     GNUNET_assert (NULL != signatures);
    456 
    457     /**
    458      * We expand the provided refresh_seed from the original call to /melt,
    459      * into kappa many batch seeds, from which we will later use all except the
    460      * noreveal_index one.
    461      */
    462     TALER_refresh_expand_kappa_nonces_v27 (
    463       &rf->refresh_seed,
    464       &kappa_nonces);
    465 
    466     for (uint8_t k=0; k < TALER_CNC_KAPPA; k++)
    467     {
    468       if (rf->noreveal_index == k)
    469         continue;
    470       if (GNUNET_OK !=
    471           TALER_wallet_refresh_nonce_verify (
    472             &rf->coin.coin_pub,
    473             &kappa_nonces.tuple[k],
    474             rf->num_coins,
    475             denoms_h,
    476             k,
    477             &(*signatures)[sig_idx++]))
    478       {
    479         GNUNET_break_op (0);
    480         *result = TALER_MHD_reply_with_ec (con,
    481                                            TALER_EC_EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID,
    482                                            NULL);
    483         return GNUNET_SYSERR;
    484       }
    485     }
    486   }
    487   /**
    488    * In the following scope, we start collecting blinded coin planchet hashes,
    489    * either those persisted from the original request to /melt, or we
    490    * derive and calculate them from the provided signatures, after having
    491    * verified that each of them was signed by the old coin's private key.
    492    *
    493    * After collecting the blinded coin planchet hashes, we can then get
    494    * the commitment for the calculated values and compare the result with
    495    * the commitment from the /melt request.
    496    */
    497   {
    498     struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h;
    499     union GNUNET_CRYPTO_BlindSessionNonce b_nonces[GNUNET_NZL (cs_count)];
    500     size_t cs_idx = 0; /* [0...cs_count) */
    501     uint8_t sig_idx = 0; /* [0..KAPPA_MINUS_1) */
    502     /* These two are only necessary for non-v27 clients, but this is the common case. */
    503     struct TALER_TransferPublicKeyP k_tpbs[TALER_CNC_KAPPA][rf->num_coins];
    504     struct TALER_KappaTransferPublicKeys kappa_transfer_pubs = {
    505       .num_transfer_pubs = rf->num_coins
    506     };
    507     /**
    508      * First, derive the blinding nonces for the CS denominations all at once.
    509      */
    510     if (0 < cs_count)
    511     {
    512       uint32_t cs_indices[cs_count];
    513       size_t idx = 0; /* [0...cs_count) */
    514 
    515       for (size_t i = 0; i < rf->num_coins; i++)
    516         if (is_cs[i])
    517           cs_indices[idx++] = i;
    518 
    519       TALER_cs_derive_only_cs_blind_nonces_from_seed (&rf->blinding_seed,
    520                                                       true, /* for melt */
    521                                                       cs_count,
    522                                                       cs_indices,
    523                                                       b_nonces);
    524     }
    525     /**
    526      * We handle the kappa batches of rf->num_coins depths first.
    527      */
    528     for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++)
    529     {
    530       if (k ==  rf->noreveal_index)
    531       {
    532         /**
    533          * We take the stored value for the hash of selected batch
    534          */
    535         kappa_planchets_h.tuple[k] = rf->selected_h;
    536       }
    537       else
    538       {
    539         /**
    540          * We have to generate all the planchets' details from
    541          * the disclosed input material and generate the
    542          * hashes of them.
    543          */
    544         struct TALER_PlanchetMasterSecretP planchet_secrets[rf->num_coins];
    545         struct TALER_PlanchetDetail details[rf->num_coins];
    546 
    547         memset (planchet_secrets,
    548                 0,
    549                 sizeof(planchet_secrets));
    550         memset (details,
    551                 0,
    552                 sizeof(details));
    553 
    554         GNUNET_assert (sig_idx < KAPPA_MINUS_1);
    555 
    556         /**
    557          * Expand from the k-th signature all num_coin planchet secrets,
    558          * except for the noreveal_index.
    559          */
    560         if (rf->is_v27_refresh)
    561         {
    562           TALER_refresh_signature_to_secrets_v27 (
    563             &(*signatures)[sig_idx++],
    564             rf->num_coins,
    565             planchet_secrets);
    566         }
    567         else        /* vDOLDPLUS */
    568         {
    569           TALER_refresh_expand_batch_seed_to_transfer_data (
    570             &(*rev_batch_seeds)[sig_idx++],
    571             &rf->coin.coin_pub,
    572             rf->num_coins,
    573             planchet_secrets,
    574             k_tpbs[k]);
    575 
    576           kappa_transfer_pubs.batch[k] = k_tpbs[k];
    577         }
    578         /**
    579          * Reset the index for the CS  denominations.
    580          */
    581         cs_idx = 0;
    582 
    583         for (size_t coin_idx = 0; coin_idx < rf->num_coins; coin_idx++)
    584         {
    585           struct GNUNET_CRYPTO_CSPublicRPairP *rp;
    586           union GNUNET_CRYPTO_BlindSessionNonce *np;
    587 
    588           if (is_cs[coin_idx])
    589           {
    590             GNUNET_assert (cs_idx < cs_count);
    591             np = &b_nonces[cs_idx];
    592             rp = &rf->cs_r_values[cs_idx];
    593             cs_idx++;
    594           }
    595           else
    596           {
    597             np = NULL;
    598             rp = NULL;
    599           }
    600           ret = calculate_blinded_detail (con,
    601                                           denom_keys[coin_idx],
    602                                           &planchet_secrets[coin_idx],
    603                                           rp,
    604                                           np,
    605                                           old_age_commitment,
    606                                           &details[coin_idx],
    607                                           result);
    608           if (GNUNET_OK != ret)
    609             return GNUNET_SYSERR;
    610         }
    611         /**
    612          * Now we can generate the hashes for the kappa-th batch of coins
    613          */
    614         TALER_wallet_blinded_planchet_details_hash (
    615           rf->num_coins,
    616           details,
    617           &kappa_planchets_h.tuple[k]);
    618 
    619         for (size_t i =0; i<rf->num_coins; i++)
    620           TALER_planchet_detail_free (&details[i]);
    621       }
    622     }
    623     /**
    624      * Finally, calculate the refresh commitment and compare it with the original.
    625      */
    626     {
    627       struct TALER_RefreshCommitmentP rc;
    628 
    629       if (rf->is_v27_refresh)
    630       {
    631         TALER_refresh_get_commitment_v27 (&rc,
    632                                           &rf->refresh_seed,
    633                                           rf->no_blinding_seed
    634                                             ? NULL
    635                                             : &rf->blinding_seed,
    636                                           &kappa_planchets_h,
    637                                           &rf->coin.coin_pub,
    638                                           &rf->amount_with_fee);
    639       }
    640       else
    641       {
    642         TALER_refresh_get_commitment (&rc,
    643                                       &rf->refresh_seed,
    644                                       rf->no_blinding_seed
    645                                             ? NULL
    646                                             : &rf->blinding_seed,
    647                                       &kappa_transfer_pubs,
    648                                       &kappa_planchets_h,
    649                                       &rf->coin.coin_pub,
    650                                       &rf->amount_with_fee);
    651       }
    652 
    653       if (0 != GNUNET_CRYPTO_hash_cmp (
    654             &rf->rc.session_hash,
    655             &rc.session_hash))
    656       {
    657         GNUNET_break_op (0);
    658         *result = TALER_MHD_reply_with_ec (con,
    659                                            TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH,
    660                                            "rc");
    661         return GNUNET_SYSERR;
    662       }
    663     }
    664   }
    665   return GNUNET_OK;
    666 }
    667 
    668 
    669 /**
    670  * @brief Commit the successful reveal to the database
    671  *
    672  * @param con HTTP-connection to the client
    673  * @param rc Original refresh commitment from the previous /melt request
    674  * @param[out] result On error, a HTTP-response will be queued and result set accordingly
    675  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
    676  */
    677 static enum GNUNET_GenericReturnValue
    678 commit_reveal (
    679   struct MHD_Connection *con,
    680   const struct TALER_RefreshCommitmentP *rc,
    681   MHD_RESULT *result)
    682 {
    683   enum GNUNET_DB_QueryStatus qs;
    684 
    685   for (unsigned int retry = 0; retry < 3; retry++)
    686   {
    687     qs = TEH_plugin->mark_refresh_reveal_success (
    688       TEH_plugin->cls,
    689       rc);
    690     switch (qs)
    691     {
    692     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    693       return GNUNET_OK; /* Only happy case */
    694     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    695       *result = TALER_MHD_reply_with_error (con,
    696                                             MHD_HTTP_NOT_FOUND,
    697                                             TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
    698                                             NULL);
    699       return GNUNET_SYSERR;
    700     case GNUNET_DB_STATUS_HARD_ERROR:
    701       *result = TALER_MHD_reply_with_ec (con,
    702                                          TALER_EC_GENERIC_DB_STORE_FAILED,
    703                                          "mark_refresh_reveal_success");
    704       return GNUNET_SYSERR;
    705     case GNUNET_DB_STATUS_SOFT_ERROR:
    706       break; /* try again */
    707     default:
    708       GNUNET_break (0);
    709       *result = TALER_MHD_reply_with_ec (con,
    710                                          TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    711                                          NULL);
    712       return GNUNET_SYSERR;
    713     }
    714   }
    715   /* after unsuccessful retries*/
    716   *result = TALER_MHD_reply_with_ec (con,
    717                                      TALER_EC_GENERIC_DB_STORE_FAILED,
    718                                      "mark_refresh_reveal_success");
    719   return GNUNET_SYSERR;
    720 
    721 }
    722 
    723 
    724 /**
    725  * @brief Send a response for "/reveal-melt"
    726  *
    727  * @param connection The http connection to the client to send the response to
    728  * @param refresh The data from the previous call to /melt with signatures
    729  * @return a MHD result code
    730  */
    731 static MHD_RESULT
    732 reply_melt_reveal_success (
    733   struct MHD_Connection *connection,
    734   const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh)
    735 {
    736   json_t *list = json_array ();
    737   GNUNET_assert (NULL != list);
    738 
    739   for (unsigned int i = 0; i < refresh->num_coins; i++)
    740   {
    741     json_t *obj = GNUNET_JSON_PACK (
    742       TALER_JSON_pack_blinded_denom_sig (NULL,
    743                                          &refresh->denom_sigs[i]));
    744     GNUNET_assert (0 ==
    745                    json_array_append_new (list,
    746                                           obj));
    747   }
    748 
    749   return TALER_MHD_REPLY_JSON_PACK (
    750     connection,
    751     MHD_HTTP_OK,
    752     GNUNET_JSON_pack_array_steal ("ev_sigs",
    753                                   list));
    754 }
    755 
    756 
    757 MHD_RESULT
    758 TEH_handler_reveal_melt (
    759   struct TEH_RequestContext *rc,
    760   const json_t *root,
    761   const char *const args[2])
    762 {
    763   MHD_RESULT result = MHD_NO;
    764   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    765   struct MeltRevealContext actx = {0};
    766   struct GNUNET_JSON_Specification sig_tuple[] = {
    767     GNUNET_JSON_spec_fixed_auto (NULL,
    768                                  &actx.signatures[0]),
    769     GNUNET_JSON_spec_fixed_auto (NULL,
    770                                  &actx.signatures[1]),
    771     GNUNET_JSON_spec_end ()
    772   };
    773   struct GNUNET_JSON_Specification seeds_tuple[] = {
    774     GNUNET_JSON_spec_fixed_auto (NULL,
    775                                  &actx.transfer_secret_seeds[0]),
    776     GNUNET_JSON_spec_fixed_auto (NULL,
    777                                  &actx.transfer_secret_seeds[1]),
    778     GNUNET_JSON_spec_end ()
    779   };
    780   struct GNUNET_JSON_Specification spec[] = {
    781     GNUNET_JSON_spec_fixed_auto ("rc",
    782                                  &actx.rc),
    783     GNUNET_JSON_spec_mark_optional (
    784       TALER_JSON_spec_tuple_of ("signatures",
    785                                 sig_tuple),
    786       &actx.no_signatures),
    787     GNUNET_JSON_spec_mark_optional (
    788       TALER_JSON_spec_tuple_of ("batch_seeds",
    789                                 seeds_tuple),
    790       &actx.no_transfer_secret_seeds),
    791     GNUNET_JSON_spec_mark_optional (
    792       TALER_JSON_spec_age_commitment ("age_commitment",
    793                                       &actx.age_commitment),
    794       &actx.no_age_commitment),
    795     GNUNET_JSON_spec_end ()
    796   };
    797 
    798   /**
    799    * Note that above, we have hard-wired
    800    * the size of TALER_CNC_KAPPA.
    801    * Let's make sure we keep this in sync.
    802    */
    803   _Static_assert (KAPPA_MINUS_1 == 2,
    804                   "TALER_CNC_KAPPA isn't 3!?!?");
    805 
    806   /* Parse JSON body*/
    807   ret = TALER_MHD_parse_json_data (rc->connection,
    808                                    root,
    809                                    spec);
    810   if (GNUNET_OK != ret)
    811   {
    812     GNUNET_break_op (0);
    813     return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
    814   }
    815 
    816   (void) args;
    817 
    818   do {
    819     /* Find original commitment */
    820     if (GNUNET_OK !=
    821         find_original_refresh (
    822           rc->connection,
    823           &actx.rc,
    824           &actx.refresh,
    825           &result))
    826       break;
    827 
    828     /* Compare age commitment with the hash from the /melt request, if present */
    829     if (GNUNET_OK !=
    830         compare_age_commitment (
    831           rc->connection,
    832           &actx,
    833           &result))
    834       break;
    835 
    836     /* verify the commitment  */
    837     if (GNUNET_OK !=
    838         verify_commitment (
    839           rc->connection,
    840           &actx.refresh,
    841           actx.no_age_commitment
    842                 ? NULL
    843                 : &actx.age_commitment,
    844           actx.no_signatures
    845                 ? NULL
    846                 : &actx.signatures,
    847           actx.no_transfer_secret_seeds
    848                 ? NULL
    849                 : &actx.transfer_secret_seeds,
    850           &result))
    851       break;
    852 
    853     if (GNUNET_OK !=
    854         commit_reveal (rc->connection,
    855                        &actx.rc,
    856                        &result))
    857       break;
    858 
    859     /* Finally, return the signatures */
    860     result = reply_melt_reveal_success (rc->connection,
    861                                         &actx.refresh);
    862 
    863   } while (0);
    864 
    865   GNUNET_JSON_parse_free (spec);
    866   if (NULL != actx.refresh.denom_sigs)
    867     for (unsigned int i = 0; i<actx.refresh.num_coins; i++)
    868       TALER_blinded_denom_sig_free (&actx.refresh.denom_sigs[i]);
    869   GNUNET_free (actx.refresh.denom_sigs);
    870   GNUNET_free (actx.refresh.denom_pub_hashes);
    871   GNUNET_free (actx.refresh.denom_serials);
    872   GNUNET_free (actx.refresh.cs_r_values);
    873   return result;
    874 }
    875 
    876 
    877 /* end of taler-exchange-httpd_reveal_melt.c */