diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_melt.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_melt.c | 706 |
1 files changed, 270 insertions, 436 deletions
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c index 3a0195cfd..ac3902e3f 100644 --- a/src/exchange/taler-exchange-httpd_melt.c +++ b/src/exchange/taler-exchange-httpd_melt.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -29,61 +29,8 @@ #include "taler-exchange-httpd_mhd.h" #include "taler-exchange-httpd_melt.h" #include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_keystate.h" - - -/** - * Send a response for a failed "melt" request. The - * transaction history of the given coin demonstrates that the - * @a residual value of the coin is below the @a requested - * contribution of the coin for the melt. Thus, the exchange - * refuses the melt operation. - * - * @param connection the connection to send the response to - * @param coin_pub public key of the coin - * @param coin_value original value of the coin - * @param tl transaction history for the coin - * @param requested how much this coin was supposed to contribute, including fee - * @param residual remaining value of the coin (after subtracting @a tl) - * @return a MHD result code - */ -static MHD_RESULT -reply_melt_insufficient_funds ( - struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *coin_value, - struct TALER_EXCHANGEDB_TransactionList *tl, - const struct TALER_Amount *requested, - const struct TALER_Amount *residual) -{ - json_t *history; - - history = TEH_RESPONSE_compile_transaction_history (coin_pub, - tl); - if (NULL == history) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS, - "Failed to compile transaction history"); - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_CONFLICT, - "{s:s, s:I, s:o, s:o, s:o, s:o, s:o}", - "hint", - "insufficient funds", - "code", - (json_int_t) - TALER_EC_MELT_INSUFFICIENT_FUNDS, - "coin_pub", - GNUNET_JSON_from_data_auto (coin_pub), - "original_value", - TALER_JSON_from_amount (coin_value), - "residual_value", - TALER_JSON_from_amount (residual), - "requested_value", - TALER_JSON_from_amount (requested), - "history", - history); -} +#include "taler-exchange-httpd_keys.h" +#include "taler_exchangedb_lib.h" /** @@ -101,30 +48,30 @@ reply_melt_success (struct MHD_Connection *connection, { struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; - struct TALER_RefreshMeltConfirmationPS body = { - .purpose.size = htonl (sizeof (body)), - .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT), - .rc = *rc, - .noreveal_index = htonl (noreveal_index) - }; - - if (GNUNET_OK != - TEH_KS_sign (&body, - &pub, - &sig)) + enum TALER_ErrorCode ec; + + if (TALER_EC_NONE != + (ec = TALER_exchange_online_melt_confirmation_sign ( + &TEH_keys_exchange_sign_, + rc, + noreveal_index, + &pub, + &sig))) { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); + GNUNET_break (0); + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); } - return TALER_MHD_reply_json_pack ( + return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, - "{s:i, s:o, s:o}", - "noreveal_index", (int) noreveal_index, - "exchange_sig", GNUNET_JSON_from_data_auto (&sig), - "exchange_pub", GNUNET_JSON_from_data_auto (&pub)); + GNUNET_JSON_pack_uint64 ("noreveal_index", + noreveal_index), + GNUNET_JSON_pack_data_auto ("exchange_sig", + &sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &pub)); } @@ -141,6 +88,11 @@ struct MeltContext struct TALER_EXCHANGEDB_Refresh refresh_session; /** + * UUID of the coin in the known_coins table. + */ + uint64_t known_coin_id; + + /** * Information about the @e coin's value. */ struct TALER_Amount coin_value; @@ -151,125 +103,28 @@ struct MeltContext struct TALER_Amount coin_refresh_fee; /** - * Set to #GNUNET_YES if this coin's denomination was revoked and the operation + * Refresh master secret, if any of the fresh denominations use CS. + */ + struct TALER_RefreshMasterSecretP rms; + + /** + * Set to true if this coin's denomination was revoked and the operation * is thus only allowed for zombie coins where the transaction * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP. */ - int zombie_required; - -}; - - -/** - * Check that the coin has sufficient funds left for the selected - * melt operation. - * - * @param connection the connection to send errors to - * @param session the database connection - * @param[in,out] rmc melt context - * @param[out] mhd_ret status code to return to MHD on hard error - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -refresh_check_melt (struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - struct MeltContext *rmc, - MHD_RESULT *mhd_ret) -{ - struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_Amount spent; - enum GNUNET_DB_QueryStatus qs; + bool zombie_required; - /* Start with cost of this melt transaction */ - spent = rmc->refresh_session.amount_with_fee; - - /* get historic transaction costs of this coin, including recoups as - we might be a zombie coin */ - qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - session, - &rmc->refresh_session.coin.coin_pub, - GNUNET_YES, - &tl); - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MELT_DB_FETCH_ERROR, - "failed to fetch old coin history"); - return qs; - } - if (rmc->zombie_required) - { - /* The denomination key is only usable for a melt if this is a true - zombie coin, i.e. it was refreshed and the resulting fresh coin was - then recouped. Check that this is truly the case. */ - for (struct TALER_EXCHANGEDB_TransactionList *tp = tl; - NULL != tp; - tp = tp->next) - { - if (TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP == tp->type) - { - rmc->zombie_required = GNUNET_NO; /* clear flag: was satisfied! */ - break; - } - } - if (GNUNET_YES == rmc->zombie_required) - { - /* zombie status not satisfied */ - GNUNET_break_op (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_MELT_COIN_EXPIRED_NO_ZOMBIE, - "denomination expired"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } - if (GNUNET_OK != - TALER_EXCHANGEDB_calculate_transaction_list_totals (tl, - &spent, - &spent)) - { - GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MELT_COIN_HISTORY_COMPUTATION_FAILED, - "failed to compute coin transaction history"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* Refuse to refresh when the coin's value is insufficient - for the cost of all transactions. */ - if (0 > TALER_amount_cmp (&rmc->coin_value, - &spent)) - { - struct TALER_Amount coin_residual; - - GNUNET_assert (0 <= - TALER_amount_subtract (&coin_residual, - &spent, - &rmc->refresh_session.amount_with_fee)); - *mhd_ret = reply_melt_insufficient_funds ( - connection, - &rmc->refresh_session.coin.coin_pub, - &rmc->coin_value, - tl, - &rmc->refresh_session.amount_with_fee, - &coin_residual); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return GNUNET_DB_STATUS_HARD_ERROR; - } + /** + * We already checked and noticed that the coin is known. Hence we + * can skip the "ensure_coin_known" step of the transaction. + */ + bool coin_is_dirty; - /* we're good, coin has sufficient funds to be melted */ - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} + /** + * True if @e rms is missing. + */ + bool no_rms; +}; /** @@ -287,7 +142,6 @@ refresh_check_melt (struct MHD_Connection *connection, * * @param cls our `struct MeltContext` * @param connection MHD request which triggered the transaction - * @param session database session to use * @param[out] mhd_ret set to MHD response status for @a connection, * if transaction failed (!) * @return transaction status @@ -295,121 +149,121 @@ refresh_check_melt (struct MHD_Connection *connection, static enum GNUNET_DB_QueryStatus melt_transaction (void *cls, struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, MHD_RESULT *mhd_ret) { struct MeltContext *rmc = cls; enum GNUNET_DB_QueryStatus qs; - uint32_t noreveal_index; - - /* Check if we already created a matching refresh_session */ - qs = TEH_plugin->get_melt_index (TEH_plugin->cls, - session, - &rmc->refresh_session.rc, - &noreveal_index); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - TALER_LOG_DEBUG ("Coin was previously melted, returning old reply\n"); - *mhd_ret = reply_melt_success (connection, - &rmc->refresh_session.rc, - noreveal_index); - /* Note: we return "hard error" to ensure the wrapper - does not retry the transaction, and to also not generate - a "fresh" response (as we would on "success") */ - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MELT_DB_FETCH_ERROR, - "failed to fetch melt index"); - return qs; - } - - /* check coin has enough funds remaining on it to cover melt cost */ - qs = refresh_check_melt (connection, - session, - rmc, - mhd_ret); - if (0 > qs) - return qs; /* if we failed, tell caller */ + bool balance_ok; /* pick challenge and persist it */ rmc->refresh_session.noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, TALER_CNC_KAPPA); - if (0 >= - (qs = TEH_plugin->insert_melt (TEH_plugin->cls, - session, - &rmc->refresh_session))) + + if (0 > + (qs = TEH_plugin->do_melt (TEH_plugin->cls, + rmc->no_rms + ? NULL + : &rmc->rms, + &rmc->refresh_session, + rmc->known_coin_id, + &rmc->zombie_required, + &balance_ok))) { if (GNUNET_DB_STATUS_SOFT_ERROR != qs) { *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MELT_DB_STORE_SESSION_ERROR, - "failed to persist melt data"); + TALER_EC_GENERIC_DB_STORE_FAILED, + "melt"); return GNUNET_DB_STATUS_HARD_ERROR; } return qs; } + GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); + if (rmc->zombie_required) + { + GNUNET_break_op (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (! balance_ok) + { + GNUNET_break_op (0); + *mhd_ret + = TEH_RESPONSE_reply_coin_insufficient_funds ( + connection, + TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, + &rmc->refresh_session.coin.denom_pub_hash, + &rmc->refresh_session.coin.coin_pub); + return GNUNET_DB_STATUS_HARD_ERROR; + } + /* All good, commit, final response will be generated by caller */ + TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT]++; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } /** * Handle a "melt" request after the first parsing has - * happened. We now need to validate the coins being melted and the - * session signature and then hand things of to execute the melt - * operation. This function parses the JSON arrays and then passes - * processing on to #melt_transaction(). + * happened. Performs the database transactions. * * @param connection the MHD connection to handle * @param[in,out] rmc details about the melt request * @return MHD result code */ static MHD_RESULT -handle_melt (struct MHD_Connection *connection, - struct MeltContext *rmc) +database_melt (struct MHD_Connection *connection, + struct MeltContext *rmc) { - /* verify signature of coin for melt operation */ + if (GNUNET_SYSERR == + TEH_plugin->preflight (TEH_plugin->cls)) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + "preflight failure"); + } + + /* first, make sure coin is known */ + if (! rmc->coin_is_dirty) { - struct TALER_RefreshMeltCoinAffirmationPS body = { - .purpose.size = htonl (sizeof (body)), - .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT), - .rc = rmc->refresh_session.rc, - .coin_pub = rmc->refresh_session.coin.coin_pub - }; - - TALER_amount_hton (&body.amount_with_fee, - &rmc->refresh_session.amount_with_fee); - TALER_amount_hton (&body.melt_fee, - &rmc->coin_refresh_fee); + MHD_RESULT mhd_ret = MHD_NO; + enum GNUNET_DB_QueryStatus qs; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify ( - TALER_SIGNATURE_WALLET_COIN_MELT, - &body, - &rmc->refresh_session.coin_sig.eddsa_signature, - &rmc->refresh_session.coin.coin_pub.eddsa_pub)) + for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++) { - GNUNET_break_op (0); + qs = TEH_make_coin_known (&rmc->refresh_session.coin, + connection, + &rmc->known_coin_id, + &mhd_ret); + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; + } + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + GNUNET_break (0); return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_MELT_COIN_SIGNATURE_INVALID, - "confirm_sig"); + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "make_coin_known"); } + if (qs < 0) + return mhd_ret; } - /* run database transaction */ + /* run main database transaction */ { MHD_RESULT mhd_ret; if (GNUNET_OK != TEH_DB_run_transaction (connection, "run melt", + TEH_MT_REQUEST_MELT, &mhd_ret, &melt_transaction, rmc)) @@ -432,158 +286,47 @@ handle_melt (struct MHD_Connection *connection, * @return MHD status code */ static MHD_RESULT -check_for_denomination_key (struct MHD_Connection *connection, - struct MeltContext *rmc) +check_melt_valid (struct MHD_Connection *connection, + struct MeltContext *rmc) { - struct TEH_KS_StateHandle *key_state; - int coin_is_dirty = GNUNET_NO; + /* Baseline: check if deposits/refreshes are generally + simply still allowed for this denomination */ + struct TEH_DenominationKey *dk; + MHD_RESULT mret; + + dk = TEH_keys_denomination_by_hash ( + &rmc->refresh_session.coin.denom_pub_hash, + connection, + &mret); + if (NULL == dk) + return mret; - key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); - if (NULL == key_state) + if (GNUNET_TIME_absolute_is_past (dk->meta.expire_legal.abs_time)) { - TALER_LOG_ERROR ("Lacking keys to operate\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); + /* Way too late now, even zombies have expired */ + return TEH_RESPONSE_reply_expired_denom_pub_hash ( + connection, + &rmc->refresh_session.coin.denom_pub_hash, + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, + "MELT"); } + if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) { - /* Baseline: check if deposits/refreshs are generally - simply still allowed for this denomination */ - struct TALER_EXCHANGEDB_DenominationKey *dki; - unsigned int hc; - enum TALER_ErrorCode ec; - - dki = TEH_KS_denomination_key_lookup_by_hash ( - key_state, + /* This denomination is not yet valid */ + return TEH_RESPONSE_reply_expired_denom_pub_hash ( + connection, &rmc->refresh_session.coin.denom_pub_hash, - TEH_KS_DKU_DEPOSIT, - &ec, - &hc); - /* Consider case that denomination was revoked but - this coin was already seen and thus refresh is OK. */ - if (NULL == dki) - { - dki = TEH_KS_denomination_key_lookup_by_hash ( - key_state, - &rmc->refresh_session.coin.denom_pub_hash, - TEH_KS_DKU_RECOUP, - &ec, - &hc); - if (NULL != dki) - { - struct GNUNET_HashCode denom_hash; - enum GNUNET_DB_QueryStatus qs; - - /* Check that the coin is dirty (we have seen it before), as we will - not just allow melting of a *fresh* coin where the denomination was - revoked (those must be recouped) */ - qs = TEH_plugin->get_coin_denomination ( - TEH_plugin->cls, - NULL, - &rmc->refresh_session.coin.coin_pub, - &denom_hash); - if (0 > qs) - { - TEH_KS_release (key_state); - /* There is no good reason for a serialization failure here: */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MELT_DB_FETCH_ERROR, - "failed to find information about old coin"); - } - /* sanity check */ - GNUNET_break (0 == - GNUNET_memcmp (&denom_hash, - &rmc->refresh_session.coin.denom_pub_hash)); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - /* We never saw this coin before, so _this_ justification is not OK */ - dki = NULL; - } - else - { - /* Minor optimization: no need to run the - #TEH_DB_know_coin_transaction below */ - coin_is_dirty = GNUNET_YES; - } - } - } - - /* Consider the case that the denomination expired for deposits, but - recoup of a refreshed coin refilled the balance of the 'zombie' coin - and we should thus allow the refresh during the legal period. */ - if (NULL == dki) - { - dki = TEH_KS_denomination_key_lookup_by_hash (key_state, - &rmc->refresh_session.coin. - denom_pub_hash, - TEH_KS_DKU_ZOMBIE, - &ec, - &hc); - if (NULL != dki) - rmc->zombie_required = GNUNET_YES; /* check later that zombie is satisfied */ - } - if (NULL == dki) - { - TEH_KS_release (key_state); - return TALER_MHD_reply_with_error (connection, - hc, - ec, - "unknown denomination"); - } - - TALER_amount_ntoh (&rmc->coin_refresh_fee, - &dki->issue.properties.fee_refresh); - TALER_amount_ntoh (&rmc->coin_value, - &dki->issue.properties.value); - /* check client used sane currency */ - if (GNUNET_YES != - TALER_amount_cmp_currency (&rmc->refresh_session.amount_with_fee, - &rmc->coin_value) ) - { - GNUNET_break_op (0); - TEH_KS_release (key_state); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_MELT_CURRENCY_MISMATCH, - "value_with_fee"); - - } - /* check coin is actually properly signed */ - if (GNUNET_OK != - TALER_test_coin_valid (&rmc->refresh_session.coin, - &dki->denom_pub)) - { - GNUNET_break_op (0); - TEH_KS_release (key_state); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_MELT_DENOMINATION_SIGNATURE_INVALID, - "denom_sig"); - } + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, + "MELT"); } - TEH_KS_release (key_state); - /* run actual logic, now that the request was parsed */ - /* First, make sure coin is 'known' in database */ - if (GNUNET_NO == coin_is_dirty) - { - struct TEH_DB_KnowCoinContext kcc; - MHD_RESULT mhd_ret; + rmc->coin_refresh_fee = dk->meta.fees.refresh; + rmc->coin_value = dk->meta.value; - kcc.coin = &rmc->refresh_session.coin; - kcc.connection = connection; - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "know coin for melt", - &mhd_ret, - &TEH_DB_know_coin_transaction, - &kcc)) - return mhd_ret; - } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Melted coin's denomination is worth %s\n", + TALER_amount2s (&dk->meta.value)); /* sanity-check that "total melt amount > melt fee" */ if (0 < @@ -593,61 +336,152 @@ check_for_denomination_key (struct MHD_Connection *connection, GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_MELT_FEES_EXCEED_CONTRIBUTION, - "melt amount smaller than melting fee"); + TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION, + NULL); + } + switch (dk->denom_pub.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++; + break; + case GNUNET_CRYPTO_BSA_CS: + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++; + break; + default: + break; + } + if (GNUNET_OK != + TALER_test_coin_valid (&rmc->refresh_session.coin, + &dk->denom_pub)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, + NULL); } - return handle_melt (connection, - rmc); -} + /* verify signature of coin for melt operation */ + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_wallet_melt_verify (&rmc->refresh_session.amount_with_fee, + &rmc->coin_refresh_fee, + &rmc->refresh_session.rc, + &rmc->refresh_session.coin.denom_pub_hash, + &rmc->refresh_session.coin.h_age_commitment, + &rmc->refresh_session.coin.coin_pub, + &rmc->refresh_session.coin_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID, + NULL); + } + + if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time)) + { + /* We are past deposit expiration time, but maybe this is a zombie? */ + struct TALER_DenominationHashP denom_hash; + enum GNUNET_DB_QueryStatus qs; + + /* Check that the coin is dirty (we have seen it before), as we will + not just allow melting of a *fresh* coin where the denomination was + revoked (those must be recouped) */ + qs = TEH_plugin->get_coin_denomination ( + TEH_plugin->cls, + &rmc->refresh_session.coin.coin_pub, + &rmc->known_coin_id, + &denom_hash); + if (0 > qs) + { + /* There is no good reason for a serialization failure here: */ + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "coin denomination"); + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + /* We never saw this coin before, so _this_ justification is not OK */ + return TEH_RESPONSE_reply_expired_denom_pub_hash ( + connection, + &rmc->refresh_session.coin.denom_pub_hash, + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, + "MELT"); + } + /* Minor optimization: no need to run the + "ensure_coin_known" part of the transaction */ + rmc->coin_is_dirty = true; + /* sanity check */ + if (0 != + GNUNET_memcmp (&denom_hash, + &rmc->refresh_session.coin.denom_pub_hash)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_ec ( + connection, + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY, + TALER_B2S (&denom_hash)); + } + rmc->zombie_required = true; /* check later that zombie is satisfied */ + } + + return database_melt (connection, + rmc); +} -/** - * Handle a "/coins/$COIN_PUB/melt" request. Parses the request into the JSON - * components and then hands things of to #check_for_denomination_key() to - * validate the melted coins, the signature and execute the melt using - * handle_melt(). - * @param connection the MHD connection to handle - * @param coin_pub public key of the coin - * @param root uploaded JSON data - * @return MHD result code - */ MHD_RESULT TEH_handler_melt (struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub, const json_t *root) { struct MeltContext rmc; - enum GNUNET_GenericReturnValue ret; - MHD_RESULT res; struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_denomination_signature ("denom_sig", - &rmc.refresh_session.coin.denom_sig), + TALER_JSON_spec_denom_sig ("denom_sig", + &rmc.refresh_session.coin.denom_sig), GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", &rmc.refresh_session.coin.denom_pub_hash), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("age_commitment_hash", + &rmc.refresh_session.coin.h_age_commitment), + &rmc.refresh_session.coin.no_age_commitment), GNUNET_JSON_spec_fixed_auto ("confirm_sig", &rmc.refresh_session.coin_sig), TALER_JSON_spec_amount ("value_with_fee", + TEH_currency, &rmc.refresh_session.amount_with_fee), GNUNET_JSON_spec_fixed_auto ("rc", &rmc.refresh_session.rc), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("rms", + &rmc.rms), + &rmc.no_rms), GNUNET_JSON_spec_end () }; - memset (&rmc, - 0, - sizeof (rmc)); + memset (&rmc, 0, sizeof (rmc)); rmc.refresh_session.coin.coin_pub = *coin_pub; - ret = TALER_MHD_parse_json_data (connection, - root, - spec); - if (GNUNET_OK != ret) - return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; - - res = check_for_denomination_key (connection, - &rmc); - GNUNET_JSON_parse_free (spec); - return res; + + { + enum GNUNET_GenericReturnValue ret; + ret = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_OK != ret) + return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; + } + + { + MHD_RESULT res; + + res = check_melt_valid (connection, + &rmc); + GNUNET_JSON_parse_free (spec); + return res; + } } |