From f8e62141f27c296b326fc810f18206d16d41b468 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 19 Jun 2017 21:04:49 +0200 Subject: split httpd_refresh.c into refresh_melt, refresh_link and refresh_reveal --- src/exchange/Makefile.am | 4 +- src/exchange/taler-exchange-httpd.c | 6 +- src/exchange/taler-exchange-httpd_db.c | 945 ------------------ src/exchange/taler-exchange-httpd_db.h | 94 +- src/exchange/taler-exchange-httpd_refresh.c | 755 -------------- src/exchange/taler-exchange-httpd_refresh.h | 94 -- src/exchange/taler-exchange-httpd_refresh_link.c | 286 ++++++ src/exchange/taler-exchange-httpd_refresh_link.h | 49 + src/exchange/taler-exchange-httpd_refresh_melt.c | 1043 ++++++++++++++++++++ src/exchange/taler-exchange-httpd_refresh_melt.h | 52 + src/exchange/taler-exchange-httpd_refresh_reveal.c | 833 ++++++++++++++++ src/exchange/taler-exchange-httpd_refresh_reveal.h | 54 + src/exchange/taler-exchange-httpd_responses.c | 245 ----- src/exchange/taler-exchange-httpd_responses.h | 105 -- 14 files changed, 2325 insertions(+), 2240 deletions(-) delete mode 100644 src/exchange/taler-exchange-httpd_refresh.c delete mode 100644 src/exchange/taler-exchange-httpd_refresh.h create mode 100644 src/exchange/taler-exchange-httpd_refresh_link.c create mode 100644 src/exchange/taler-exchange-httpd_refresh_link.h create mode 100644 src/exchange/taler-exchange-httpd_refresh_melt.c create mode 100644 src/exchange/taler-exchange-httpd_refresh_melt.h create mode 100644 src/exchange/taler-exchange-httpd_refresh_reveal.c create mode 100644 src/exchange/taler-exchange-httpd_refresh_reveal.h (limited to 'src') diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 1d8372435..9ff98d12b 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -50,7 +50,9 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \ taler-exchange-httpd_parsing.c taler-exchange-httpd_parsing.h \ taler-exchange-httpd_payback.c taler-exchange-httpd_payback.h \ - taler-exchange-httpd_refresh.c taler-exchange-httpd_refresh.h \ + taler-exchange-httpd_refresh_link.c taler-exchange-httpd_refresh_link.h \ + taler-exchange-httpd_refresh_melt.c taler-exchange-httpd_refresh_melt.h \ + taler-exchange-httpd_refresh_reveal.c taler-exchange-httpd_refresh_reveal.h \ taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \ taler-exchange-httpd_reserve_status.c taler-exchange-httpd_reserve_status.h \ taler-exchange-httpd_reserve_withdraw.c taler-exchange-httpd_reserve_withdraw.h \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 43c8a8039..1a4d2863d 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -34,11 +34,13 @@ #include "taler-exchange-httpd_reserve_status.h" #include "taler-exchange-httpd_reserve_withdraw.h" #include "taler-exchange-httpd_payback.h" -#include "taler-exchange-httpd_wire.h" -#include "taler-exchange-httpd_refresh.h" +#include "taler-exchange-httpd_refresh_link.h" +#include "taler-exchange-httpd_refresh_melt.h" +#include "taler-exchange-httpd_refresh_reveal.h" #include "taler-exchange-httpd_track_transfer.h" #include "taler-exchange-httpd_track_transaction.h" #include "taler-exchange-httpd_keystate.h" +#include "taler-exchange-httpd_wire.h" #if HAVE_DEVELOPER #include "taler-exchange-httpd_test.h" #endif diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 9871b7f34..e7be6af09 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -32,85 +32,6 @@ */ #define MAX_TRANSACTION_COMMIT_RETRIES 3 -/** - * Code to begin a transaction, must be inline as we define a block - * that ends with #COMMIT_TRANSACTION() within which we perform a number - * of retries. Note that this code may call "return" internally, so - * it must be called within a function where any cleanup will be done - * by the caller. Furthermore, the function's return value must - * match that of a #TEH_RESPONSE_reply_internal_db_error() status code. - * - * @param session session handle - * @param connection connection handle - */ -#define START_TRANSACTION(session,connection) \ -{ /* start new scope, will be ended by COMMIT_TRANSACTION() */\ - unsigned int transaction_retries = 0; \ - enum GNUNET_DB_QueryStatus transaction_commit_result; \ -transaction_start_label: /* we will use goto for retries */ \ - if (GNUNET_OK != \ - TEH_plugin->start (TEH_plugin->cls, \ - session)) \ - { \ - GNUNET_break (0); \ - return TEH_RESPONSE_reply_internal_db_error (connection, \ - TALER_EC_DB_START_FAILED); \ - } - -/** - * Code to conclude a transaction, dual to #START_TRANSACTION(). Note - * that this code may call "return" internally, so it must be called - * within a function where any cleanup will be done by the caller. - * Furthermore, the function's return value must match that of a - * #TEH_RESPONSE_reply_internal_db_error() status code. - * - * @param session session handle - * @param connection connection handle - */ -#define COMMIT_TRANSACTION(session,connection) \ - transaction_commit_result = \ - TEH_plugin->commit (TEH_plugin->cls, \ - session); \ - if (GNUNET_DB_STATUS_HARD_ERROR == transaction_commit_result) \ - { \ - TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ - return TEH_RESPONSE_reply_commit_error (connection, \ - TALER_EC_DB_COMMIT_FAILED_HARD); \ - } \ - if (GNUNET_DB_STATUS_SOFT_ERROR == transaction_commit_result) \ - { \ - TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ - if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ - goto transaction_start_label; \ - TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ - transaction_retries, \ - __FUNCTION__); \ - return TEH_RESPONSE_reply_commit_error (connection, \ - TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ - } \ -} /* end of scope opened by BEGIN_TRANSACTION */ - - -/** - * Code to include to retry a transaction, must only be used in between - * #START_TRANSACTION and #COMMIT_TRANSACTION. - * - * @param session session handle - * @param connection connection handle - */ -#define RETRY_TRANSACTION(session,connection) \ - do { \ - TEH_plugin->rollback (TEH_plugin->cls, \ - session); \ - if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ - goto transaction_start_label; \ - TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ - transaction_retries, \ - __FUNCTION__); \ - return TEH_RESPONSE_reply_commit_error (connection, \ - TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ - } while (0) - /** * Run a database transaction for @a connection. @@ -279,870 +200,4 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis } -/** - * Parse coin melt requests from a JSON object and write them to - * the database. - * - * @param connection the connection to send errors to - * @param session the database connection - * @param key_state the exchange's key state - * @param session_hash hash identifying the refresh session - * @param coin_details details about the coin being melted - * @param[out] meltp on success, set to melt details - * @return #GNUNET_OK on success, - * #GNUNET_NO if an error message was generated, - * #GNUNET_SYSERR on internal errors (no response generated) - */ -static int -refresh_check_melt (struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - const struct TEH_KS_StateHandle *key_state, - const struct GNUNET_HashCode *session_hash, - const struct TEH_DB_MeltDetails *coin_details, - struct TALER_EXCHANGEDB_RefreshMelt *meltp) -{ - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk; - struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; - struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_Amount coin_value; - struct TALER_Amount coin_residual; - struct TALER_Amount spent; - int res; - enum GNUNET_DB_QueryStatus qs; - - dk = TEH_KS_denomination_key_lookup (key_state, - &coin_details->coin_info.denom_pub, - TEH_KS_DKU_DEPOSIT); - if (NULL == dk) - return (MHD_YES == - TEH_RESPONSE_reply_internal_error (connection, - TALER_EC_REFRESH_MELT_DB_DENOMINATION_KEY_NOT_FOUND, - "denomination key no longer available while executing transaction")) - ? GNUNET_NO : GNUNET_SYSERR; - dki = &dk->issue; - TALER_amount_ntoh (&coin_value, - &dki->properties.value); - /* fee for THIS transaction; the melt amount includes the fee! */ - spent = coin_details->melt_amount_with_fee; - /* add historic transaction costs of this coin */ - qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - session, - &coin_details->coin_info.coin_pub, - &tl); - (void) qs; /* FIXME #5010 */ - if (GNUNET_OK != - TEH_DB_calculate_transaction_list_totals (tl, - &spent, - &spent)) - { - GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return (MHD_YES == - TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED)) - ? GNUNET_NO : GNUNET_SYSERR; - } - /* Refuse to refresh when the coin's value is insufficient - for the cost of all transactions. */ - if (TALER_amount_cmp (&coin_value, - &spent) < 0) - { - GNUNET_assert (GNUNET_SYSERR != - TALER_amount_subtract (&coin_residual, - &spent, - &coin_details->melt_amount_with_fee)); - res = (MHD_YES == - TEH_RESPONSE_reply_refresh_melt_insufficient_funds (connection, - &coin_details->coin_info.coin_pub, - coin_value, - tl, - coin_details->melt_amount_with_fee, - coin_residual)) - ? GNUNET_NO : GNUNET_SYSERR; - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return res; - } - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - - meltp->coin = coin_details->coin_info; - meltp->coin_sig = coin_details->melt_sig; - meltp->session_hash = *session_hash; - meltp->amount_with_fee = coin_details->melt_amount_with_fee; - meltp->melt_fee = coin_details->melt_fee; - return GNUNET_OK; -} - - -/** - * Execute a "/refresh/melt". We have been given a list of valid - * coins and a request to melt them into the given - * @a refresh_session_pub. Check that the coins all have the - * required value left and if so, store that they have been - * melted and confirm the melting operation to the client. - * - * @param connection the MHD connection to handle - * @param session_hash hash code of the session the coins are melted into - * @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array - * @param denom_pubs public keys of the coins we want to withdraw in the end - * @param coin_melt_detail signature and (residual) value of the respective coin should be melted - * @param commit_coin 2d array of coin commitments (what the exchange is to sign - * once the "/refres/reveal" of cut and choose is done), - * x-dimension must be #TALER_CNC_KAPPA - * @param transfer_pubs array of transfer public keys (what the exchange is - * to return via "/refresh/link" to enable linkage in the - * future) of length #TALER_CNC_KAPPA - * @return MHD result code - */ -int -TEH_DB_execute_refresh_melt (struct MHD_Connection *connection, - const struct GNUNET_HashCode *session_hash, - unsigned int num_new_denoms, - const struct TALER_DenominationPublicKey *denom_pubs, - const struct TEH_DB_MeltDetails *coin_melt_detail, - struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin, - const struct TALER_TransferPublicKeyP *transfer_pubs) -{ - struct TEH_KS_StateHandle *key_state; - struct TALER_EXCHANGEDB_RefreshSession refresh_session; - struct TALER_EXCHANGEDB_Session *session; - int res; - - if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) - { - GNUNET_break (0); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_DB_SETUP_FAILED); - } - START_TRANSACTION (session, connection); - res = TEH_plugin->get_refresh_session (TEH_plugin->cls, - session, - session_hash, - &refresh_session); - if (GNUNET_YES == res) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - res = TEH_RESPONSE_reply_refresh_melt_success (connection, - session_hash, - refresh_session.noreveal_index); - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - } - if (GNUNET_SYSERR == res) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_MELT_DB_FETCH_ERROR); - } - - /* store 'global' session data */ - refresh_session.num_newcoins = num_new_denoms; - refresh_session.noreveal_index - = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, - TALER_CNC_KAPPA); - key_state = TEH_KS_acquire (); - if (GNUNET_OK != - (res = refresh_check_melt (connection, - session, - key_state, - session_hash, - coin_melt_detail, - &refresh_session.melt))) - { - TEH_KS_release (key_state); - TEH_plugin->rollback (TEH_plugin->cls, - session); - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - } - TEH_KS_release (key_state); - - if (GNUNET_OK != - (res = TEH_plugin->create_refresh_session (TEH_plugin->cls, - session, - session_hash, - &refresh_session))) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR); - } - - /* store requested new denominations */ - if (GNUNET_OK != - TEH_plugin->insert_refresh_order (TEH_plugin->cls, - session, - session_hash, - num_new_denoms, - denom_pubs)) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_MELT_DB_STORE_ORDER_ERROR); - } - - if (GNUNET_OK != - TEH_plugin->insert_refresh_commit_coins (TEH_plugin->cls, - session, - session_hash, - num_new_denoms, - commit_coin[refresh_session.noreveal_index])) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_MELT_DB_STORE_ORDER_ERROR); - } - if (GNUNET_OK != - TEH_plugin->insert_refresh_transfer_public_key (TEH_plugin->cls, - session, - session_hash, - &transfer_pubs[refresh_session.noreveal_index])) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_MELT_DB_STORE_TRANSFER_ERROR); - } - - COMMIT_TRANSACTION (session, connection); - return TEH_RESPONSE_reply_refresh_melt_success (connection, - session_hash, - refresh_session.noreveal_index); -} - - -/** - * Check if the given @a transfer_privs correspond to an honest - * commitment for the given session. - * Checks that the transfer private keys match their commitments. - * Then derives the shared secret for each #TALER_CNC_KAPPA, and check that they match. - * - * @param connection the MHD connection to handle - * @param session database connection to use - * @param session_hash hash of session to query - * @param off commitment offset to check - * @param transfer_priv private transfer key - * @param melt information about the melted coin - * @param num_newcoins number of newcoins being generated - * @param denom_pubs array of @a num_newcoins keys for the new coins - * @param hash_context hash context to update by hashing in the data - * from this offset - * @return #GNUNET_OK if the committment was honest, - * #GNUNET_NO if there was a problem and we generated an error message - * #GNUNET_SYSERR if we could not even generate an error message - */ -static int -check_commitment (struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - const struct GNUNET_HashCode *session_hash, - unsigned int off, - const struct TALER_TransferPrivateKeyP *transfer_priv, - const struct TALER_EXCHANGEDB_RefreshMelt *melt, - unsigned int num_newcoins, - const struct TALER_DenominationPublicKey *denom_pubs, - struct GNUNET_HashContext *hash_context) -{ - struct TALER_TransferSecretP transfer_secret; - unsigned int j; - - TALER_link_reveal_transfer_secret (transfer_priv, - &melt->coin.coin_pub, - &transfer_secret); - - /* Check that the commitments for all new coins were correct */ - for (j = 0; j < num_newcoins; j++) - { - struct TALER_FreshCoinP fc; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_HashCode h_msg; - char *buf; - size_t buf_len; - - TALER_setup_fresh_coin (&transfer_secret, - j, - &fc); - GNUNET_CRYPTO_eddsa_key_get_public (&fc.coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); - GNUNET_CRYPTO_hash (&coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP), - &h_msg); - if (GNUNET_YES != - GNUNET_CRYPTO_rsa_blind (&h_msg, - &fc.blinding_key.bks, - denom_pubs[j].rsa_public_key, - &buf, - &buf_len)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Blind failed (bad denomination key!?)\n"); - return (MHD_YES == - TEH_RESPONSE_reply_internal_error (connection, - TALER_EC_REFRESH_REVEAL_BLINDING_ERROR, - "Blinding error")) - ? GNUNET_NO : GNUNET_SYSERR; - } - GNUNET_CRYPTO_hash_context_read (hash_context, - buf, - buf_len); - GNUNET_free (buf); - } - return GNUNET_OK; -} - - -/** - * Exchange a coin as part of a refresh operation. Obtains the - * envelope from the database and performs the signing operation. - * - * @param connection the MHD connection to handle - * @param session database connection to use - * @param session_hash hash of session to query - * @param key_state key state to lookup denomination pubs - * @param denom_pub denomination key for the coin to create - * @param commit_coin the coin that was committed - * @param coin_off number of the coin - * @return NULL on error, otherwise signature over the coin - */ -static struct TALER_DenominationSignature -refresh_exchange_coin (struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - const struct GNUNET_HashCode *session_hash, - struct TEH_KS_StateHandle *key_state, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin, - unsigned int coin_off) -{ - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; - struct TALER_DenominationSignature ev_sig; - - dki = TEH_KS_denomination_key_lookup (key_state, - denom_pub, - TEH_KS_DKU_WITHDRAW); - if (NULL == dki) - { - GNUNET_break (0); - ev_sig.rsa_signature = NULL; - return ev_sig; - } - if (GNUNET_OK == - TEH_plugin->get_refresh_out (TEH_plugin->cls, - session, - session_hash, - coin_off, - &ev_sig)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning cached reply for /refresh/reveal signature\n"); - return ev_sig; - } - - ev_sig.rsa_signature - = GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key, - commit_coin->coin_ev, - commit_coin->coin_ev_size); - if (NULL == ev_sig.rsa_signature) - { - GNUNET_break (0); - return ev_sig; - } - if (GNUNET_SYSERR == - TEH_plugin->insert_refresh_out (TEH_plugin->cls, - session, - session_hash, - coin_off, - &ev_sig)) - { - GNUNET_break (0); - GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature); - ev_sig.rsa_signature = NULL; - } - - return ev_sig; -} - - -/** - * The client request was well-formed, now execute the DB transaction - * of a "/refresh/reveal" operation. We use the @a ev_sigs and - * @a commit_coins to clean up resources after this function returns - * as we might experience retries of the database transaction. - * - * @param connection the MHD connection to handle - * @param session database session - * @param session_hash hash identifying the refresh session - * @param refresh_session information about the refresh operation we are doing - * @param denom_pubs array of "num_newcoins" denomination keys for the new coins - * @param[out] ev_sigs where to store generated signatures for the new coins, - * array of length "num_newcoins", memory released by the - * caller - * @param[out] commit_coins array of length "num_newcoins" to be used for - * information about the new coins from the commitment. - * @return MHD result code - */ -static int -execute_refresh_reveal_transaction (struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - const struct GNUNET_HashCode *session_hash, - const struct TALER_EXCHANGEDB_RefreshSession *refresh_session, - const struct TALER_DenominationPublicKey *denom_pubs, - struct TALER_DenominationSignature *ev_sigs, - struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins) -{ - unsigned int j; - struct TEH_KS_StateHandle *key_state; - int ret; - - START_TRANSACTION (session, connection); - key_state = TEH_KS_acquire (); - for (j=0;jnum_newcoins;j++) - { - if (NULL == ev_sigs[j].rsa_signature) /* could be non-NULL during retries */ - ev_sigs[j] = refresh_exchange_coin (connection, - session, - session_hash, - key_state, - &denom_pubs[j], - &commit_coins[j], - j); - if (NULL == ev_sigs[j].rsa_signature) - { - TEH_plugin->rollback (TEH_plugin->cls, - session); - ret = TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_SIGNING_ERROR); - goto cleanup; - } - } - COMMIT_TRANSACTION (session, connection); - ret = TEH_RESPONSE_reply_refresh_reveal_success (connection, - refresh_session->num_newcoins, - ev_sigs); - cleanup: - TEH_KS_release (key_state); - return ret; -} - - -/** - * Execute a "/refresh/reveal". The client is revealing to us the - * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the - * revealed transfer keys would allow linkage to the blinded coins, - * and if so, return the signed coins for corresponding to the set of - * coins that was not chosen. - * - * @param connection the MHD connection to handle - * @param session_hash hash identifying the refresh session - * @param transfer_privs array with the revealed transfer keys, - * length must be #TALER_CNC_KAPPA - 1 - * @return MHD result code - */ -int -TEH_DB_execute_refresh_reveal (struct MHD_Connection *connection, - const struct GNUNET_HashCode *session_hash, - struct TALER_TransferPrivateKeyP *transfer_privs) -{ - int res; - struct TALER_EXCHANGEDB_Session *session; - struct TALER_EXCHANGEDB_RefreshSession refresh_session; - struct TALER_DenominationPublicKey *denom_pubs; - struct TALER_DenominationSignature *ev_sigs; - struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins; - unsigned int i; - unsigned int j; - unsigned int off; - struct GNUNET_HashContext *hash_context; - struct GNUNET_HashCode sh_check; - int ret; - struct TALER_TransferPublicKeyP gamma_tp; - - if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) - { - GNUNET_break (0); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_DB_SETUP_FAILED); - } - - res = TEH_plugin->get_refresh_session (TEH_plugin->cls, - session, - session_hash, - &refresh_session); - if (GNUNET_NO == res) - return TEH_RESPONSE_reply_arg_invalid (connection, - TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, - "session_hash"); - if ( (GNUNET_SYSERR == res) || - (refresh_session.noreveal_index >= TALER_CNC_KAPPA) ) - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR); - denom_pubs = GNUNET_new_array (refresh_session.num_newcoins, - struct TALER_DenominationPublicKey); - if (GNUNET_OK != - TEH_plugin->get_refresh_order (TEH_plugin->cls, - session, - session_hash, - refresh_session.num_newcoins, - denom_pubs)) - { - GNUNET_break (0); - GNUNET_free (denom_pubs); - GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); - GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); - return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR)) - ? GNUNET_NO : GNUNET_SYSERR; - } - - hash_context = GNUNET_CRYPTO_hash_context_start (); - /* first, iterate over transfer public keys for hash_context */ - off = 0; - for (i=0;iget_refresh_transfer_public_key (TEH_plugin->cls, - session, - session_hash, - &gamma_tp)) - { - GNUNET_break (0); - GNUNET_free (denom_pubs); - GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); - GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); - GNUNET_CRYPTO_hash_context_abort (hash_context); - return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_DB_FETCH_TRANSFER_ERROR)) - ? GNUNET_NO : GNUNET_SYSERR; - } - GNUNET_CRYPTO_hash_context_read (hash_context, - &gamma_tp, - sizeof (struct TALER_TransferPublicKeyP)); - } - else - { - /* compute tp from private key */ - struct TALER_TransferPublicKeyP tp; - - GNUNET_CRYPTO_ecdhe_key_get_public (&transfer_privs[i - off].ecdhe_priv, - &tp.ecdhe_pub); - GNUNET_CRYPTO_hash_context_read (hash_context, - &tp, - sizeof (struct TALER_TransferPublicKeyP)); - } - } - - /* next, add all of the hashes from the denomination keys to the - hash_context */ - { - struct TALER_DenominationPublicKey denom_pubs[refresh_session.num_newcoins]; - - if (GNUNET_OK != - TEH_plugin->get_refresh_order (TEH_plugin->cls, - session, - session_hash, - refresh_session.num_newcoins, - denom_pubs)) - { - GNUNET_break (0); - GNUNET_free (denom_pubs); - GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); - GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); - GNUNET_CRYPTO_hash_context_abort (hash_context); - return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR)) - ? GNUNET_NO : GNUNET_SYSERR; - } - for (i=0;iget_refresh_commit_coins (TEH_plugin->cls, - session, - session_hash, - refresh_session.num_newcoins, - commit_coins)) - { - GNUNET_break (0); - GNUNET_free (denom_pubs); - GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); - GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); - GNUNET_CRYPTO_hash_context_abort (hash_context); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_DB_FETCH_COMMIT_ERROR); - } - /* add envelopes to hash_context */ - for (j=0;jstatus) - return; - ldl = TEH_plugin->get_link_data_list (TEH_plugin->cls, - ctx->session, - session_hash); - if (NULL == ldl) - { - ctx->status = GNUNET_NO; - if (MHD_NO == - TEH_RESPONSE_reply_json_pack (ctx->connection, - MHD_HTTP_NOT_FOUND, - "{s:s}", - "error", - "link data not found (link)")) - ctx->status = GNUNET_SYSERR; - return; - } - GNUNET_array_grow (ctx->sessions, - ctx->num_sessions, - ctx->num_sessions + 1); - lsi = &ctx->sessions[ctx->num_sessions - 1]; - lsi->transfer_pub = *transfer_pub; - lsi->ldl = ldl; -} - - -/** - * Execute a "/refresh/link". Returns the linkage information that - * will allow the owner of a coin to follow the refresh trail to - * the refreshed coin. - * - * @param connection the MHD connection to handle - * @param coin_pub public key of the coin to link - * @return MHD result code - */ -int -TEH_DB_execute_refresh_link (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub) -{ - struct HTD_Context ctx; - int res; - unsigned int i; - - if (NULL == (ctx.session = TEH_plugin->get_session (TEH_plugin->cls))) - { - GNUNET_break (0); - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_DB_SETUP_FAILED); - } - ctx.connection = connection; - ctx.num_sessions = 0; - ctx.sessions = NULL; - ctx.status = GNUNET_OK; - res = TEH_plugin->get_transfer (TEH_plugin->cls, - ctx.session, - coin_pub, - &handle_transfer_data, - &ctx); - if (GNUNET_SYSERR == ctx.status) - { - res = MHD_NO; - goto cleanup; - } - if (GNUNET_NO == ctx.status) - { - res = MHD_YES; - goto cleanup; - } - GNUNET_assert (GNUNET_OK == ctx.status); - if (0 == ctx.num_sessions) - return TEH_RESPONSE_reply_arg_unknown (connection, - TALER_EC_REFRESH_LINK_COIN_UNKNOWN, - "coin_pub"); - res = TEH_RESPONSE_reply_refresh_link_success (connection, - ctx.num_sessions, - ctx.sessions); - cleanup: - for (i=0;ifree_link_data_list (TEH_plugin->cls, - ctx.sessions[i].ldl); - GNUNET_free_non_null (ctx.sessions); - return res; -} - - /* end of taler-exchange-httpd_db.c */ diff --git a/src/exchange/taler-exchange-httpd_db.h b/src/exchange/taler-exchange-httpd_db.h index 85a1604cf..a61f93291 100644 --- a/src/exchange/taler-exchange-httpd_db.h +++ b/src/exchange/taler-exchange-httpd_db.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. + Copyright (C) 2014-2017 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -83,97 +83,5 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis struct TALER_Amount *ret); -/** - * @brief Details about a melt operation of an individual coin. - */ -struct TEH_DB_MeltDetails -{ - - /** - * Information about the coin being melted. - */ - struct TALER_CoinPublicInfo coin_info; - - /** - * Signature allowing the melt (using - * a `struct TALER_EXCHANGEDB_RefreshMeltConfirmSignRequestBody`) to sign over. - */ - struct TALER_CoinSpendSignatureP melt_sig; - - /** - * How much of the coin's value did the client allow to be melted? - * This amount includes the fees, so the final amount contributed - * to the melt is this value minus the fee for melting the coin. - */ - struct TALER_Amount melt_amount_with_fee; - - /** - * What fee is earned by the exchange? Set delayed during - * #verify_coin_public_info(). - */ - struct TALER_Amount melt_fee; -}; - - -/** - * Execute a "/refresh/melt". We have been given a list of valid - * coins and a request to melt them into the given - * @a refresh_session_pub. Check that the coins all have the - * required value left and if so, store that they have been - * melted and confirm the melting operation to the client. - * - * @param connection the MHD connection to handle - * @param session_hash hash code of the session the coins are melted into - * @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array - * @param denom_pubs array of public denomination keys for the refresh (?) - * @param coin_melt_detail signatures and (residual) value of and information about the respective coin to be melted - * @param commit_coin 2d array of coin commitments (what the exchange is to sign - * once the "/refres/reveal" of cut and choose is done) - * @param transfer_pubs array of transfer public keys (what the exchange is - * to return via "/refresh/link" to enable linkage in the - * future) of length #TALER_CNC_KAPPA - * @return MHD result code - */ -int -TEH_DB_execute_refresh_melt (struct MHD_Connection *connection, - const struct GNUNET_HashCode *session_hash, - unsigned int num_new_denoms, - const struct TALER_DenominationPublicKey *denom_pubs, - const struct TEH_DB_MeltDetails *coin_melt_detail, - struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin, - const struct TALER_TransferPublicKeyP *transfer_pubs); - - -/** - * Execute a "/refresh/reveal". The client is revealing to us the - * transfer keys for #TALER_CNC_KAPPA-1 sets of coins. Verify that the - * revealed transfer keys would allow linkage to the blinded coins, - * and if so, return the signed coins for corresponding to the set of - * coins that was not chosen. - * - * @param connection the MHD connection to handle - * @param session_hash hash over the refresh session - * @param transfer_privs array of length #TALER_CNC_KAPPA-1 with the revealed transfer keys - * @return MHD result code - */ -int -TEH_DB_execute_refresh_reveal (struct MHD_Connection *connection, - const struct GNUNET_HashCode *session_hash, - struct TALER_TransferPrivateKeyP *transfer_privs); - - -/** - * Execute a "/refresh/link". Returns the linkage information that - * will allow the owner of a coin to follow the refresh trail to the - * refreshed coin. - * - * @param connection the MHD connection to handle - * @param coin_pub public key of the coin to link - * @return MHD result code - */ -int -TEH_DB_execute_refresh_link (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub); - #endif /* TALER_EXCHANGE_HTTPD_DB_H */ diff --git a/src/exchange/taler-exchange-httpd_refresh.c b/src/exchange/taler-exchange-httpd_refresh.c deleted file mode 100644 index 3a8875f44..000000000 --- a/src/exchange/taler-exchange-httpd_refresh.c +++ /dev/null @@ -1,755 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015, 2016 Inria & GNUnet e.V. - - 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 - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see -*/ -/** - * @file taler-exchange-httpd_refresh.c - * @brief Handle /refresh/ requests - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#include "platform.h" -#include -#include -#include -#include "taler-exchange-httpd_parsing.h" -#include "taler-exchange-httpd_mhd.h" -#include "taler-exchange-httpd_refresh.h" -#include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_keystate.h" - - -/** - * Handle a "/refresh/melt" request after the main JSON 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. - * - * @param connection the MHD connection to handle - * @param num_new_denoms number of coins to be created, size of y-dimension of @a commit_link array - * @param denom_pubs array of @a num_new_denoms keys - * @param coin_melt_details melting details - * @param session_hash hash over the data that the client commits to - * @param commit_coin 2d array of coin commitments (what the exchange is to sign - * once the "/refres/reveal" of cut and choose is done) - * @param transfer_pubs array of transfer public keys (which the exchange is - * to return via "/refresh/link" to enable linkage in the - * future) of length #TALER_CNC_KAPPA - * @return MHD result code - */ -static int -handle_refresh_melt_binary (struct MHD_Connection *connection, - unsigned int num_new_denoms, - const struct TALER_DenominationPublicKey *denom_pubs, - const struct TEH_DB_MeltDetails *coin_melt_details, - const struct GNUNET_HashCode *session_hash, - struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin, - const struct TALER_TransferPublicKeyP *transfer_pubs) -{ - unsigned int i; - struct TEH_KS_StateHandle *key_state; - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk; - struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; - struct TALER_Amount cost; - struct TALER_Amount total_cost; - struct TALER_Amount value; - struct TALER_Amount fee_withdraw; - struct TALER_Amount fee_melt; - struct TALER_Amount total_melt; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "/refresh/melt request for session %s\n", - GNUNET_h2s (session_hash)); - - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (TEH_exchange_currency_string, - &total_cost)); - key_state = TEH_KS_acquire (); - for (i=0;iissue; - TALER_amount_ntoh (&value, - &dki->properties.value); - TALER_amount_ntoh (&fee_withdraw, - &dki->properties.fee_withdraw); - if ( (GNUNET_OK != - TALER_amount_add (&cost, - &value, - &fee_withdraw)) || - (GNUNET_OK != - TALER_amount_add (&total_cost, - &cost, - &total_cost)) ) - { - GNUNET_break_op (0); - TEH_KS_release (key_state); - return TEH_RESPONSE_reply_internal_error (connection, - TALER_EC_REFRESH_MELT_COST_CALCULATION_OVERFLOW, - "cost calculation failure"); - } - } - - dk = TEH_KS_denomination_key_lookup (key_state, - &coin_melt_details->coin_info.denom_pub, - TEH_KS_DKU_DEPOSIT); - if (NULL == dk) - { - GNUNET_break (0); - return TEH_RESPONSE_reply_arg_unknown (connection, - TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND, - "denom_pub"); - } - dki = &dk->issue; - TALER_amount_ntoh (&fee_melt, - &dki->properties.fee_refresh); - if (GNUNET_OK != - TALER_amount_subtract (&total_melt, - &coin_melt_details->melt_amount_with_fee, - &fee_melt)) - { - GNUNET_break_op (0); - TEH_KS_release (key_state); - return TEH_RESPONSE_reply_external_error (connection, - TALER_EC_REFRESH_MELT_FEES_EXCEED_CONTRIBUTION, - "Melt contribution below melting fee"); - } - TEH_KS_release (key_state); - if (0 != - TALER_amount_cmp (&total_cost, - &total_melt)) - { - GNUNET_break_op (0); - /* We require total value of coins being melted and - total value of coins being generated to match! */ - return TEH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_BAD_REQUEST, - "{s:s, s:I}", - "error", "value mismatch", - "code", (json_int_t) TALER_EC_REFRESH_MELT_FEES_MISSMATCH); - } - return TEH_DB_execute_refresh_melt (connection, - session_hash, - num_new_denoms, - denom_pubs, - coin_melt_details, - commit_coin, - transfer_pubs); -} - - -/** - * Extract public coin information from a JSON object. - * - * @param connection the connection to send error responses to - * @param coin_info the JSON object to extract the coin info from - * @param[out] r_melt_detail set to details about the coin's melting permission (if valid) - * @return #GNUNET_YES if coin public info in JSON was valid - * #GNUNET_NO JSON was invalid, response was generated - * #GNUNET_SYSERR on internal error - */ -static int -get_coin_public_info (struct MHD_Connection *connection, - const json_t *coin_info, - struct TEH_DB_MeltDetails *r_melt_detail) -{ - int ret; - struct TALER_CoinSpendSignatureP melt_sig; - struct TALER_DenominationSignature sig; - struct TALER_DenominationPublicKey pk; - struct TALER_Amount amount; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", &r_melt_detail->coin_info.coin_pub), - TALER_JSON_spec_denomination_signature ("denom_sig", &sig), - TALER_JSON_spec_denomination_public_key ("denom_pub", &pk), - GNUNET_JSON_spec_fixed_auto ("confirm_sig", &melt_sig), - TALER_JSON_spec_amount ("value_with_fee", &amount), - GNUNET_JSON_spec_end () - }; - - ret = TEH_PARSE_json_data (connection, - coin_info, - spec); - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - return ret; - } - /* check exchange signature on the coin */ - r_melt_detail->coin_info.denom_sig = sig; - r_melt_detail->coin_info.denom_pub = pk; - if (GNUNET_OK != - TALER_test_coin_valid (&r_melt_detail->coin_info)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - r_melt_detail->coin_info.denom_sig.rsa_signature = NULL; - r_melt_detail->coin_info.denom_pub.rsa_public_key = NULL; - return (MHD_YES == - TEH_RESPONSE_reply_signature_invalid (connection, - TALER_EC_REFRESH_MELT_DENOMINATION_SIGNATURE_INVALID, - "denom_sig")) - ? GNUNET_NO : GNUNET_SYSERR; - } - r_melt_detail->melt_sig = melt_sig; - r_melt_detail->melt_amount_with_fee = amount; - return GNUNET_OK; -} - - -/** - * Verify that the signature shows that this coin is to be melted into - * the given @a session_hash melting session, and that this is a valid - * coin (we know the denomination key and the signature on it is - * valid). Essentially, this does all of the per-coin checks that can - * be done before the transaction starts. - * - * @param connection the connection to send error responses to - * @param session_hash hash over refresh session the coin is melted into - * @param[in,out] melt_detail details about the coin's melting permission, - * the `melt_fee` is updated - * @return #GNUNET_YES if coin public info in JSON was valid - * #GNUNET_NO JSON was invalid, response was generated - * #GNUNET_SYSERR on internal error - */ -static int -verify_coin_public_info (struct MHD_Connection *connection, - const struct GNUNET_HashCode *session_hash, - struct TEH_DB_MeltDetails *melt_detail) -{ - struct TALER_RefreshMeltCoinAffirmationPS body; - struct TEH_KS_StateHandle *key_state; - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; - struct TALER_Amount fee_refresh; - - /* FIXME: we lookup the dki twice during /refresh/melt. - This should be avoided. */ - key_state = TEH_KS_acquire (); - dki = TEH_KS_denomination_key_lookup (key_state, - &melt_detail->coin_info.denom_pub, - TEH_KS_DKU_DEPOSIT); - if (NULL == dki) - { - TEH_KS_release (key_state); - TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); - return TEH_RESPONSE_reply_arg_unknown (connection, - TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND, - "denom_pub"); - } - TALER_amount_ntoh (&fee_refresh, - &dki->issue.properties.fee_refresh); - melt_detail->melt_fee = fee_refresh; - body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); - body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); - body.session_hash = *session_hash; - TALER_amount_hton (&body.amount_with_fee, - &melt_detail->melt_amount_with_fee); - TALER_amount_hton (&body.melt_fee, - &fee_refresh); - body.coin_pub = melt_detail->coin_info.coin_pub; - if (TALER_amount_cmp (&fee_refresh, - &melt_detail->melt_amount_with_fee) > 0) - { - GNUNET_break_op (0); - TEH_KS_release (key_state); - return (MHD_YES == - TEH_RESPONSE_reply_external_error (connection, - TALER_EC_REFRESH_MELT_AMOUNT_INSUFFICIENT, - "melt amount smaller than melting fee")) - ? GNUNET_NO : GNUNET_SYSERR; - } - - TEH_KS_release (key_state); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, - &body.purpose, - &melt_detail->melt_sig.eddsa_signature, - &melt_detail->coin_info.coin_pub.eddsa_pub)) - { - GNUNET_break_op (0); - if (MHD_YES != - TEH_RESPONSE_reply_signature_invalid (connection, - TALER_EC_REFRESH_MELT_COIN_SIGNATURE_INVALID, - "confirm_sig")) - return GNUNET_SYSERR; - return GNUNET_NO; - } - return GNUNET_OK; -} - - -/** - * Release memory from the @a commit_coin array. - * - * @param commit_coin array to release - * @param kappa size of 1st dimension - * @param num_new_coins size of 2nd dimension - */ -static void -free_commit_coins (struct TALER_EXCHANGEDB_RefreshCommitCoin **commit_coin, - unsigned int kappa, - unsigned int num_new_coins) -{ - unsigned int i; - unsigned int j; - - for (i=0;icoin_ev, - &rcc->coin_ev_size), - GNUNET_JSON_spec_end () - }; - - res = TEH_PARSE_json_array (connection, - coin_evs, - coin_spec, - i, j, -1); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - goto cleanup; - } - - GNUNET_CRYPTO_hash_context_read (hash_context, - rcc->coin_ev, - rcc->coin_ev_size); - } - } - - GNUNET_CRYPTO_hash_context_finish (hash_context, - &session_hash); - hash_context = NULL; - /* verify signature on coins to melt */ - res = verify_coin_public_info (connection, - &session_hash, - &coin_melt_details); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - res = (GNUNET_NO == res) ? MHD_YES : MHD_NO; - goto cleanup; - } - - /* execute commit */ - res = handle_refresh_melt_binary (connection, - num_newcoins, - denom_pubs, - &coin_melt_details, - &session_hash, - commit_coin, - transfer_pub); - cleanup: - free_commit_coins (commit_coin, - TALER_CNC_KAPPA, - num_newcoins); - cleanup_melt_details: - if (NULL != coin_melt_details.coin_info.denom_pub.rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (coin_melt_details.coin_info.denom_pub.rsa_public_key); - if (NULL != coin_melt_details.coin_info.denom_sig.rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (coin_melt_details.coin_info.denom_sig.rsa_signature); - cleanup_denoms: - if (NULL != denom_pubs) - { - for (j=0;j -*/ -/** - * @file taler-exchange-httpd_refresh.h - * @brief Handle /refresh/ requests - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#ifndef TALER_EXCHANGE_HTTPD_REFRESH_H -#define TALER_EXCHANGE_HTTPD_REFRESH_H - -#include -#include -#include "taler-exchange-httpd.h" - - -/** - * Handle a "/refresh/melt" request. Parses the request into the JSON - * components and then hands things of to #handle_refresh_melt_json() - * to validate the melted coins, the signature and execute the melt - * using TEH_DB_execute_refresh_melt(). - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @return MHD result code - */ -int -TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size); - - -/** - * Handle a "/refresh/reveal" request. This time, the client reveals - * the private transfer keys except for the cut-and-choose value - * returned from "/refresh/commit". This function parses the revealed - * keys and secrets and ultimately passes everything to - * #TEH_DB_execute_refresh_reveal() which will verify that the - * revealed information is valid then returns the signed refreshed - * coins. - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @return MHD result code - */ -int -TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size); - - -/** - * Handle a "/refresh/link" request - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @return MHD result code - */ -int -TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size); - - -#endif diff --git a/src/exchange/taler-exchange-httpd_refresh_link.c b/src/exchange/taler-exchange-httpd_refresh_link.c new file mode 100644 index 000000000..c85431f04 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_refresh_link.c @@ -0,0 +1,286 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 Inria & GNUnet e.V. + + 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_refresh_link.c + * @brief Handle /refresh/link requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler-exchange-httpd_parsing.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_refresh_link.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + + +/** + * @brief Information for each session a coin was melted into. + */ +struct TEH_RESPONSE_LinkSessionInfo +{ + /** + * Transfer public key of the coin. + */ + struct TALER_TransferPublicKeyP transfer_pub; + + /** + * Linked data of coins being created in the session. + */ + struct TALER_EXCHANGEDB_LinkDataList *ldl; + +}; + + +/** + * Closure for #handle_transfer_data(). + */ +struct HTD_Context +{ + + /** + * Session link data we collect. + */ + struct TEH_RESPONSE_LinkSessionInfo *sessions; + + /** + * Database session. Nothing to do with @a sessions. + */ + struct TALER_EXCHANGEDB_Session *session; + + /** + * MHD connection, for queueing replies. + */ + struct MHD_Connection *connection; + + /** + * Number of sessions the coin was melted into. + */ + unsigned int num_sessions; + + /** + * How are we expected to proceed. #GNUNET_SYSERR if we + * failed to return an error (should return #MHD_NO). + * #GNUNET_NO if we succeeded in queueing an MHD error + * (should return #MHD_YES from #TEH_execute_refresh_link), + * #GNUNET_OK if we should call #reply_refresh_link_success(). + */ + int status; +}; + + +/** + * Send a response for "/refresh/link". + * + * @param connection the connection to send the response to + * @param num_sessions number of sessions the coin was used in + * @param sessions array of @a num_session entries with + * information for each session + * @return a MHD result code + */ +static int +reply_refresh_link_success (struct MHD_Connection *connection, + unsigned int num_sessions, + const struct TEH_RESPONSE_LinkSessionInfo *sessions) +{ + json_t *root; + json_t *mlist; + int res; + unsigned int i; + + mlist = json_array (); + for (i=0;inext) + { + json_t *obj; + + obj = json_object (); + json_object_set_new (obj, + "denom_pub", + GNUNET_JSON_from_rsa_public_key (pos->denom_pub.rsa_public_key)); + json_object_set_new (obj, + "ev_sig", + GNUNET_JSON_from_rsa_signature (pos->ev_sig.rsa_signature)); + GNUNET_assert (0 == + json_array_append_new (list, + obj)); + } + root = json_object (); + json_object_set_new (root, + "new_coins", + list); + json_object_set_new (root, + "transfer_pub", + GNUNET_JSON_from_data_auto (&sessions[i].transfer_pub)); + GNUNET_assert (0 == + json_array_append_new (mlist, + root)); + } + res = TEH_RESPONSE_reply_json (connection, + mlist, + MHD_HTTP_OK); + json_decref (mlist); + return res; +} + + +/** + * Function called with the session hashes and transfer secret + * information for a given coin. Gets the linkage data and + * builds the reply for the client. + * + * + * @param cls closure, a `struct HTD_Context` + * @param session_hash a session the coin was melted in + * @param transfer_pub public transfer key for the session + */ +static void +handle_transfer_data (void *cls, + const struct GNUNET_HashCode *session_hash, + const struct TALER_TransferPublicKeyP *transfer_pub) +{ + struct HTD_Context *ctx = cls; + struct TALER_EXCHANGEDB_LinkDataList *ldl; + struct TEH_RESPONSE_LinkSessionInfo *lsi; + + if (GNUNET_OK != ctx->status) + return; + ldl = TEH_plugin->get_link_data_list (TEH_plugin->cls, + ctx->session, + session_hash); + if (NULL == ldl) + { + ctx->status = GNUNET_NO; + if (MHD_NO == + TEH_RESPONSE_reply_json_pack (ctx->connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "link data not found (link)")) + ctx->status = GNUNET_SYSERR; + return; + } + GNUNET_array_grow (ctx->sessions, + ctx->num_sessions, + ctx->num_sessions + 1); + lsi = &ctx->sessions[ctx->num_sessions - 1]; + lsi->transfer_pub = *transfer_pub; + lsi->ldl = ldl; +} + + +/** + * Execute a "/refresh/link". Returns the linkage information that + * will allow the owner of a coin to follow the refresh trail to + * the refreshed coin. + * + * @param connection the MHD connection to handle + * @param coin_pub public key of the coin to link + * @return MHD result code + */ +static int +execute_refresh_link (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub) +{ + struct HTD_Context ctx; + int res; + unsigned int i; + + if (NULL == (ctx.session = TEH_plugin->get_session (TEH_plugin->cls))) + { + GNUNET_break (0); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_DB_SETUP_FAILED); + } + ctx.connection = connection; + ctx.num_sessions = 0; + ctx.sessions = NULL; + ctx.status = GNUNET_OK; + res = TEH_plugin->get_transfer (TEH_plugin->cls, + ctx.session, + coin_pub, + &handle_transfer_data, + &ctx); + if (GNUNET_SYSERR == ctx.status) + { + res = MHD_NO; + goto cleanup; + } + if (GNUNET_NO == ctx.status) + { + res = MHD_YES; + goto cleanup; + } + GNUNET_assert (GNUNET_OK == ctx.status); + if (0 == ctx.num_sessions) + return TEH_RESPONSE_reply_arg_unknown (connection, + TALER_EC_REFRESH_LINK_COIN_UNKNOWN, + "coin_pub"); + res = reply_refresh_link_success (connection, + ctx.num_sessions, + ctx.sessions); + cleanup: + for (i=0;ifree_link_data_list (TEH_plugin->cls, + ctx.sessions[i].ldl); + GNUNET_free_non_null (ctx.sessions); + return res; +} + + +/** + * Handle a "/refresh/link" request. Note that for "/refresh/link" + * we do use a simple HTTP GET, and a HTTP POST! + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TALER_CoinSpendPublicKeyP coin_pub; + int res; + + res = TEH_PARSE_mhd_request_arg_data (connection, + "coin_pub", + &coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)); + if (GNUNET_SYSERR == res) + return MHD_NO; + if (GNUNET_OK != res) + return MHD_YES; + return execute_refresh_link (connection, + &coin_pub); +} + + +/* end of taler-exchange-httpd_refresh_link.c */ diff --git a/src/exchange/taler-exchange-httpd_refresh_link.h b/src/exchange/taler-exchange-httpd_refresh_link.h new file mode 100644 index 000000000..037b0d309 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_refresh_link.h @@ -0,0 +1,49 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 GNUnet e.V. + + 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_refresh_link.h + * @brief Handle /refresh/link requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_REFRESH_LINK_H +#define TALER_EXCHANGE_HTTPD_REFRESH_LINK_H + +#include +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a "/refresh/link" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c new file mode 100644 index 000000000..1c4e5e864 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_refresh_melt.c @@ -0,0 +1,1043 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 Inria & GNUnet e.V. + + 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_refresh_melt.c + * @brief Handle /refresh/melt requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler-exchange-httpd_parsing.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_refresh_melt.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + + +/** + * How often should we retry a transaction before giving up + * (for transactions resulting in serialization/dead locks only). + */ +#define MAX_TRANSACTION_COMMIT_RETRIES 3 + +/** + * Code to begin a transaction, must be inline as we define a block + * that ends with #COMMIT_TRANSACTION() within which we perform a number + * of retries. Note that this code may call "return" internally, so + * it must be called within a function where any cleanup will be done + * by the caller. Furthermore, the function's return value must + * match that of a #TEH_RESPONSE_reply_internal_db_error() status code. + * + * @param session session handle + * @param connection connection handle + */ +#define START_TRANSACTION(session,connection) \ +{ /* start new scope, will be ended by COMMIT_TRANSACTION() */\ + unsigned int transaction_retries = 0; \ + enum GNUNET_DB_QueryStatus transaction_commit_result; \ +transaction_start_label: /* we will use goto for retries */ \ + if (GNUNET_OK != \ + TEH_plugin->start (TEH_plugin->cls, \ + session)) \ + { \ + GNUNET_break (0); \ + return TEH_RESPONSE_reply_internal_db_error (connection, \ + TALER_EC_DB_START_FAILED); \ + } + +/** + * Code to conclude a transaction, dual to #START_TRANSACTION(). Note + * that this code may call "return" internally, so it must be called + * within a function where any cleanup will be done by the caller. + * Furthermore, the function's return value must match that of a + * #TEH_RESPONSE_reply_internal_db_error() status code. + * + * @param session session handle + * @param connection connection handle + */ +#define COMMIT_TRANSACTION(session,connection) \ + transaction_commit_result = \ + TEH_plugin->commit (TEH_plugin->cls, \ + session); \ + if (GNUNET_DB_STATUS_HARD_ERROR == transaction_commit_result) \ + { \ + TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_HARD); \ + } \ + if (GNUNET_DB_STATUS_SOFT_ERROR == transaction_commit_result) \ + { \ + TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ + if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ + goto transaction_start_label; \ + TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ + transaction_retries, \ + __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ + } \ +} /* end of scope opened by BEGIN_TRANSACTION */ + + +/** + * Code to include to retry a transaction, must only be used in between + * #START_TRANSACTION and #COMMIT_TRANSACTION. + * + * @param session session handle + * @param connection connection handle + */ +#define RETRY_TRANSACTION(session,connection) \ + do { \ + TEH_plugin->rollback (TEH_plugin->cls, \ + session); \ + if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ + goto transaction_start_label; \ + TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ + transaction_retries, \ + __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ + } while (0) + + + + +/** + * @brief Details about a melt operation of an individual coin. + */ +struct TEH_DB_MeltDetails +{ + + /** + * Information about the coin being melted. + */ + struct TALER_CoinPublicInfo coin_info; + + /** + * Signature allowing the melt (using + * a `struct TALER_EXCHANGEDB_RefreshMeltConfirmSignRequestBody`) to sign over. + */ + struct TALER_CoinSpendSignatureP melt_sig; + + /** + * How much of the coin's value did the client allow to be melted? + * This amount includes the fees, so the final amount contributed + * to the melt is this value minus the fee for melting the coin. + */ + struct TALER_Amount melt_amount_with_fee; + + /** + * What fee is earned by the exchange? Set delayed during + * #verify_coin_public_info(). + */ + struct TALER_Amount melt_fee; +}; + + +/** + * Send a response for a failed "/refresh/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 int +reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_Amount coin_value, + struct TALER_EXCHANGEDB_TransactionList *tl, + struct TALER_Amount requested, + struct TALER_Amount residual) +{ + json_t *history; + + history = TEH_RESPONSE_compile_transaction_history (tl); + if (NULL == history) + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS); + return TEH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_FORBIDDEN, + "{s:s, s:I, s:o, s:o, s:o, s:o, s:o}", + "error", + "insufficient funds", + "code", + (json_int_t) TALER_EC_REFRESH_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); +} + + +/** + * Send a response to a "/refresh/melt" request. + * + * @param connection the connection to send the response to + * @param session_hash hash of the refresh session + * @param noreveal_index which index will the client not have to reveal + * @return a MHD status code + */ +static int +reply_refresh_melt_success (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + uint16_t noreveal_index) +{ + struct TALER_RefreshMeltConfirmationPS body; + struct TALER_ExchangePublicKeyP pub; + struct TALER_ExchangeSignatureP sig; + json_t *sig_json; + + body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS)); + body.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); + body.session_hash = *session_hash; + body.noreveal_index = htons (noreveal_index); + body.reserved = htons (0); + TEH_KS_sign (&body.purpose, + &pub, + &sig); + sig_json = GNUNET_JSON_from_data_auto (&sig); + GNUNET_assert (NULL != sig_json); + return TEH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:i, s:o, s:o}", + "noreveal_index", (int) noreveal_index, + "exchange_sig", sig_json, + "exchange_pub", GNUNET_JSON_from_data_auto (&pub)); +} + + +/** + * Parse coin melt requests from a JSON object and write them to + * the database. + * + * @param connection the connection to send errors to + * @param session the database connection + * @param key_state the exchange's key state + * @param session_hash hash identifying the refresh session + * @param coin_details details about the coin being melted + * @param[out] meltp on success, set to melt details + * @return #GNUNET_OK on success, + * #GNUNET_NO if an error message was generated, + * #GNUNET_SYSERR on internal errors (no response generated) + */ +static int +refresh_check_melt (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct TEH_KS_StateHandle *key_state, + const struct GNUNET_HashCode *session_hash, + const struct TEH_DB_MeltDetails *coin_details, + struct TALER_EXCHANGEDB_RefreshMelt *meltp) +{ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk; + struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; + struct TALER_EXCHANGEDB_TransactionList *tl; + struct TALER_Amount coin_value; + struct TALER_Amount coin_residual; + struct TALER_Amount spent; + int res; + enum GNUNET_DB_QueryStatus qs; + + dk = TEH_KS_denomination_key_lookup (key_state, + &coin_details->coin_info.denom_pub, + TEH_KS_DKU_DEPOSIT); + if (NULL == dk) + return (MHD_YES == + TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_REFRESH_MELT_DB_DENOMINATION_KEY_NOT_FOUND, + "denomination key no longer available while executing transaction")) + ? GNUNET_NO : GNUNET_SYSERR; + dki = &dk->issue; + TALER_amount_ntoh (&coin_value, + &dki->properties.value); + /* fee for THIS transaction; the melt amount includes the fee! */ + spent = coin_details->melt_amount_with_fee; + /* add historic transaction costs of this coin */ + qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, + session, + &coin_details->coin_info.coin_pub, + &tl); + (void) qs; /* FIXME #5010 */ + if (GNUNET_OK != + TEH_DB_calculate_transaction_list_totals (tl, + &spent, + &spent)) + { + GNUNET_break (0); + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + return (MHD_YES == + TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED)) + ? GNUNET_NO : GNUNET_SYSERR; + } + /* Refuse to refresh when the coin's value is insufficient + for the cost of all transactions. */ + if (TALER_amount_cmp (&coin_value, + &spent) < 0) + { + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_subtract (&coin_residual, + &spent, + &coin_details->melt_amount_with_fee)); + res = (MHD_YES == + reply_refresh_melt_insufficient_funds (connection, + &coin_details->coin_info.coin_pub, + coin_value, + tl, + coin_details->melt_amount_with_fee, + coin_residual)) + ? GNUNET_NO : GNUNET_SYSERR; + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + return res; + } + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + + meltp->coin = coin_details->coin_info; + meltp->coin_sig = coin_details->melt_sig; + meltp->session_hash = *session_hash; + meltp->amount_with_fee = coin_details->melt_amount_with_fee; + meltp->melt_fee = coin_details->melt_fee; + return GNUNET_OK; +} + + +/** + * Execute a "/refresh/melt". We have been given a list of valid + * coins and a request to melt them into the given + * @a refresh_session_pub. Check that the coins all have the + * required value left and if so, store that they have been + * melted and confirm the melting operation to the client. + * + * @param connection the MHD connection to handle + * @param session_hash hash code of the session the coins are melted into + * @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array + * @param denom_pubs public keys of the coins we want to withdraw in the end + * @param coin_melt_detail signature and (residual) value of the respective coin should be melted + * @param commit_coin 2d array of coin commitments (what the exchange is to sign + * once the "/refres/reveal" of cut and choose is done), + * x-dimension must be #TALER_CNC_KAPPA + * @param transfer_pubs array of transfer public keys (what the exchange is + * to return via "/refresh/link" to enable linkage in the + * future) of length #TALER_CNC_KAPPA + * @return MHD result code + */ +static int +execute_refresh_melt (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + unsigned int num_new_denoms, + const struct TALER_DenominationPublicKey *denom_pubs, + const struct TEH_DB_MeltDetails *coin_melt_detail, + struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin, + const struct TALER_TransferPublicKeyP *transfer_pubs) +{ + struct TEH_KS_StateHandle *key_state; + struct TALER_EXCHANGEDB_RefreshSession refresh_session; + struct TALER_EXCHANGEDB_Session *session; + int res; + + if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) + { + GNUNET_break (0); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_DB_SETUP_FAILED); + } + START_TRANSACTION (session, connection); + res = TEH_plugin->get_refresh_session (TEH_plugin->cls, + session, + session_hash, + &refresh_session); + if (GNUNET_YES == res) + { + TEH_plugin->rollback (TEH_plugin->cls, + session); + res = reply_refresh_melt_success (connection, + session_hash, + refresh_session.noreveal_index); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + if (GNUNET_SYSERR == res) + { + TEH_plugin->rollback (TEH_plugin->cls, + session); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_MELT_DB_FETCH_ERROR); + } + + /* store 'global' session data */ + refresh_session.num_newcoins = num_new_denoms; + refresh_session.noreveal_index + = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + TALER_CNC_KAPPA); + key_state = TEH_KS_acquire (); + if (GNUNET_OK != + (res = refresh_check_melt (connection, + session, + key_state, + session_hash, + coin_melt_detail, + &refresh_session.melt))) + { + TEH_KS_release (key_state); + TEH_plugin->rollback (TEH_plugin->cls, + session); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + TEH_KS_release (key_state); + + if (GNUNET_OK != + (res = TEH_plugin->create_refresh_session (TEH_plugin->cls, + session, + session_hash, + &refresh_session))) + { + TEH_plugin->rollback (TEH_plugin->cls, + session); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR); + } + + /* store requested new denominations */ + if (GNUNET_OK != + TEH_plugin->insert_refresh_order (TEH_plugin->cls, + session, + session_hash, + num_new_denoms, + denom_pubs)) + { + TEH_plugin->rollback (TEH_plugin->cls, + session); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_MELT_DB_STORE_ORDER_ERROR); + } + + if (GNUNET_OK != + TEH_plugin->insert_refresh_commit_coins (TEH_plugin->cls, + session, + session_hash, + num_new_denoms, + commit_coin[refresh_session.noreveal_index])) + { + TEH_plugin->rollback (TEH_plugin->cls, + session); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_MELT_DB_STORE_ORDER_ERROR); + } + if (GNUNET_OK != + TEH_plugin->insert_refresh_transfer_public_key (TEH_plugin->cls, + session, + session_hash, + &transfer_pubs[refresh_session.noreveal_index])) + { + TEH_plugin->rollback (TEH_plugin->cls, + session); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_MELT_DB_STORE_TRANSFER_ERROR); + } + + COMMIT_TRANSACTION (session, connection); + return reply_refresh_melt_success (connection, + session_hash, + refresh_session.noreveal_index); +} + + +/** + * Handle a "/refresh/melt" request after the main JSON 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. + * + * @param connection the MHD connection to handle + * @param num_new_denoms number of coins to be created, size of y-dimension of @a commit_link array + * @param denom_pubs array of @a num_new_denoms keys + * @param coin_melt_details melting details + * @param session_hash hash over the data that the client commits to + * @param commit_coin 2d array of coin commitments (what the exchange is to sign + * once the "/refres/reveal" of cut and choose is done) + * @param transfer_pubs array of transfer public keys (which the exchange is + * to return via "/refresh/link" to enable linkage in the + * future) of length #TALER_CNC_KAPPA + * @return MHD result code + */ +static int +handle_refresh_melt_binary (struct MHD_Connection *connection, + unsigned int num_new_denoms, + const struct TALER_DenominationPublicKey *denom_pubs, + const struct TEH_DB_MeltDetails *coin_melt_details, + const struct GNUNET_HashCode *session_hash, + struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin, + const struct TALER_TransferPublicKeyP *transfer_pubs) +{ + unsigned int i; + struct TEH_KS_StateHandle *key_state; + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk; + struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; + struct TALER_Amount cost; + struct TALER_Amount total_cost; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_melt; + struct TALER_Amount total_melt; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "/refresh/melt request for session %s\n", + GNUNET_h2s (session_hash)); + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TEH_exchange_currency_string, + &total_cost)); + key_state = TEH_KS_acquire (); + for (i=0;iissue; + TALER_amount_ntoh (&value, + &dki->properties.value); + TALER_amount_ntoh (&fee_withdraw, + &dki->properties.fee_withdraw); + if ( (GNUNET_OK != + TALER_amount_add (&cost, + &value, + &fee_withdraw)) || + (GNUNET_OK != + TALER_amount_add (&total_cost, + &cost, + &total_cost)) ) + { + GNUNET_break_op (0); + TEH_KS_release (key_state); + return TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_REFRESH_MELT_COST_CALCULATION_OVERFLOW, + "cost calculation failure"); + } + } + + dk = TEH_KS_denomination_key_lookup (key_state, + &coin_melt_details->coin_info.denom_pub, + TEH_KS_DKU_DEPOSIT); + if (NULL == dk) + { + GNUNET_break (0); + return TEH_RESPONSE_reply_arg_unknown (connection, + TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND, + "denom_pub"); + } + dki = &dk->issue; + TALER_amount_ntoh (&fee_melt, + &dki->properties.fee_refresh); + if (GNUNET_OK != + TALER_amount_subtract (&total_melt, + &coin_melt_details->melt_amount_with_fee, + &fee_melt)) + { + GNUNET_break_op (0); + TEH_KS_release (key_state); + return TEH_RESPONSE_reply_external_error (connection, + TALER_EC_REFRESH_MELT_FEES_EXCEED_CONTRIBUTION, + "Melt contribution below melting fee"); + } + TEH_KS_release (key_state); + if (0 != + TALER_amount_cmp (&total_cost, + &total_melt)) + { + GNUNET_break_op (0); + /* We require total value of coins being melted and + total value of coins being generated to match! */ + return TEH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:I}", + "error", "value mismatch", + "code", (json_int_t) TALER_EC_REFRESH_MELT_FEES_MISSMATCH); + } + return execute_refresh_melt (connection, + session_hash, + num_new_denoms, + denom_pubs, + coin_melt_details, + commit_coin, + transfer_pubs); +} + + +/** + * Extract public coin information from a JSON object. + * + * @param connection the connection to send error responses to + * @param coin_info the JSON object to extract the coin info from + * @param[out] r_melt_detail set to details about the coin's melting permission (if valid) + * @return #GNUNET_YES if coin public info in JSON was valid + * #GNUNET_NO JSON was invalid, response was generated + * #GNUNET_SYSERR on internal error + */ +static int +get_coin_public_info (struct MHD_Connection *connection, + const json_t *coin_info, + struct TEH_DB_MeltDetails *r_melt_detail) +{ + int ret; + struct TALER_CoinSpendSignatureP melt_sig; + struct TALER_DenominationSignature sig; + struct TALER_DenominationPublicKey pk; + struct TALER_Amount amount; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", &r_melt_detail->coin_info.coin_pub), + TALER_JSON_spec_denomination_signature ("denom_sig", &sig), + TALER_JSON_spec_denomination_public_key ("denom_pub", &pk), + GNUNET_JSON_spec_fixed_auto ("confirm_sig", &melt_sig), + TALER_JSON_spec_amount ("value_with_fee", &amount), + GNUNET_JSON_spec_end () + }; + + ret = TEH_PARSE_json_data (connection, + coin_info, + spec); + if (GNUNET_OK != ret) + { + GNUNET_break_op (0); + return ret; + } + /* check exchange signature on the coin */ + r_melt_detail->coin_info.denom_sig = sig; + r_melt_detail->coin_info.denom_pub = pk; + if (GNUNET_OK != + TALER_test_coin_valid (&r_melt_detail->coin_info)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + r_melt_detail->coin_info.denom_sig.rsa_signature = NULL; + r_melt_detail->coin_info.denom_pub.rsa_public_key = NULL; + return (MHD_YES == + TEH_RESPONSE_reply_signature_invalid (connection, + TALER_EC_REFRESH_MELT_DENOMINATION_SIGNATURE_INVALID, + "denom_sig")) + ? GNUNET_NO : GNUNET_SYSERR; + } + r_melt_detail->melt_sig = melt_sig; + r_melt_detail->melt_amount_with_fee = amount; + return GNUNET_OK; +} + + +/** + * Verify that the signature shows that this coin is to be melted into + * the given @a session_hash melting session, and that this is a valid + * coin (we know the denomination key and the signature on it is + * valid). Essentially, this does all of the per-coin checks that can + * be done before the transaction starts. + * + * @param connection the connection to send error responses to + * @param session_hash hash over refresh session the coin is melted into + * @param[in,out] melt_detail details about the coin's melting permission, + * the `melt_fee` is updated + * @return #GNUNET_YES if coin public info in JSON was valid + * #GNUNET_NO JSON was invalid, response was generated + * #GNUNET_SYSERR on internal error + */ +static int +verify_coin_public_info (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + struct TEH_DB_MeltDetails *melt_detail) +{ + struct TALER_RefreshMeltCoinAffirmationPS body; + struct TEH_KS_StateHandle *key_state; + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + struct TALER_Amount fee_refresh; + + /* FIXME: we lookup the dki twice during /refresh/melt. + This should be avoided. */ + key_state = TEH_KS_acquire (); + dki = TEH_KS_denomination_key_lookup (key_state, + &melt_detail->coin_info.denom_pub, + TEH_KS_DKU_DEPOSIT); + if (NULL == dki) + { + TEH_KS_release (key_state); + TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); + return TEH_RESPONSE_reply_arg_unknown (connection, + TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND, + "denom_pub"); + } + TALER_amount_ntoh (&fee_refresh, + &dki->issue.properties.fee_refresh); + melt_detail->melt_fee = fee_refresh; + body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); + body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + body.session_hash = *session_hash; + TALER_amount_hton (&body.amount_with_fee, + &melt_detail->melt_amount_with_fee); + TALER_amount_hton (&body.melt_fee, + &fee_refresh); + body.coin_pub = melt_detail->coin_info.coin_pub; + if (TALER_amount_cmp (&fee_refresh, + &melt_detail->melt_amount_with_fee) > 0) + { + GNUNET_break_op (0); + TEH_KS_release (key_state); + return (MHD_YES == + TEH_RESPONSE_reply_external_error (connection, + TALER_EC_REFRESH_MELT_AMOUNT_INSUFFICIENT, + "melt amount smaller than melting fee")) + ? GNUNET_NO : GNUNET_SYSERR; + } + + TEH_KS_release (key_state); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, + &body.purpose, + &melt_detail->melt_sig.eddsa_signature, + &melt_detail->coin_info.coin_pub.eddsa_pub)) + { + GNUNET_break_op (0); + if (MHD_YES != + TEH_RESPONSE_reply_signature_invalid (connection, + TALER_EC_REFRESH_MELT_COIN_SIGNATURE_INVALID, + "confirm_sig")) + return GNUNET_SYSERR; + return GNUNET_NO; + } + return GNUNET_OK; +} + + +/** + * Release memory from the @a commit_coin array. + * + * @param commit_coin array to release + * @param kappa size of 1st dimension + * @param num_new_coins size of 2nd dimension + */ +static void +free_commit_coins (struct TALER_EXCHANGEDB_RefreshCommitCoin **commit_coin, + unsigned int kappa, + unsigned int num_new_coins) +{ + unsigned int i; + unsigned int j; + + for (i=0;icoin_ev, + &rcc->coin_ev_size), + GNUNET_JSON_spec_end () + }; + + res = TEH_PARSE_json_array (connection, + coin_evs, + coin_spec, + i, j, -1); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + goto cleanup; + } + + GNUNET_CRYPTO_hash_context_read (hash_context, + rcc->coin_ev, + rcc->coin_ev_size); + } + } + + GNUNET_CRYPTO_hash_context_finish (hash_context, + &session_hash); + hash_context = NULL; + /* verify signature on coins to melt */ + res = verify_coin_public_info (connection, + &session_hash, + &coin_melt_details); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + res = (GNUNET_NO == res) ? MHD_YES : MHD_NO; + goto cleanup; + } + + /* execute commit */ + res = handle_refresh_melt_binary (connection, + num_newcoins, + denom_pubs, + &coin_melt_details, + &session_hash, + commit_coin, + transfer_pub); + cleanup: + free_commit_coins (commit_coin, + TALER_CNC_KAPPA, + num_newcoins); + cleanup_melt_details: + if (NULL != coin_melt_details.coin_info.denom_pub.rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (coin_melt_details.coin_info.denom_pub.rsa_public_key); + if (NULL != coin_melt_details.coin_info.denom_sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (coin_melt_details.coin_info.denom_sig.rsa_signature); + cleanup_denoms: + if (NULL != denom_pubs) + { + for (j=0;j +*/ +/** + * @file taler-exchange-httpd_refresh_melt.h + * @brief Handle /refresh/melt requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_REFRESH_MELT_H +#define TALER_EXCHANGE_HTTPD_REFRESH_MELT_H + +#include +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a "/refresh/melt" request. Parses the request into the JSON + * components and then hands things of to #handle_refresh_melt_json() + * to validate the melted coins, the signature and execute the melt + * using TEH_DB_execute_refresh_melt(). + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.c b/src/exchange/taler-exchange-httpd_refresh_reveal.c new file mode 100644 index 000000000..05422a85a --- /dev/null +++ b/src/exchange/taler-exchange-httpd_refresh_reveal.c @@ -0,0 +1,833 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 Inria & GNUnet e.V. + + 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 + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_refresh_reveal.c + * @brief Handle /refresh/reveal requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler-exchange-httpd_parsing.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_refresh_reveal.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + + +/** + * How often should we retry a transaction before giving up + * (for transactions resulting in serialization/dead locks only). + */ +#define MAX_TRANSACTION_COMMIT_RETRIES 3 + +/** + * Code to begin a transaction, must be inline as we define a block + * that ends with #COMMIT_TRANSACTION() within which we perform a number + * of retries. Note that this code may call "return" internally, so + * it must be called within a function where any cleanup will be done + * by the caller. Furthermore, the function's return value must + * match that of a #TEH_RESPONSE_reply_internal_db_error() status code. + * + * @param session session handle + * @param connection connection handle + */ +#define START_TRANSACTION(session,connection) \ +{ /* start new scope, will be ended by COMMIT_TRANSACTION() */\ + unsigned int transaction_retries = 0; \ + enum GNUNET_DB_QueryStatus transaction_commit_result; \ +transaction_start_label: /* we will use goto for retries */ \ + if (GNUNET_OK != \ + TEH_plugin->start (TEH_plugin->cls, \ + session)) \ + { \ + GNUNET_break (0); \ + return TEH_RESPONSE_reply_internal_db_error (connection, \ + TALER_EC_DB_START_FAILED); \ + } + +/** + * Code to conclude a transaction, dual to #START_TRANSACTION(). Note + * that this code may call "return" internally, so it must be called + * within a function where any cleanup will be done by the caller. + * Furthermore, the function's return value must match that of a + * #TEH_RESPONSE_reply_internal_db_error() status code. + * + * @param session session handle + * @param connection connection handle + */ +#define COMMIT_TRANSACTION(session,connection) \ + transaction_commit_result = \ + TEH_plugin->commit (TEH_plugin->cls, \ + session); \ + if (GNUNET_DB_STATUS_HARD_ERROR == transaction_commit_result) \ + { \ + TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_HARD); \ + } \ + if (GNUNET_DB_STATUS_SOFT_ERROR == transaction_commit_result) \ + { \ + TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \ + if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ + goto transaction_start_label; \ + TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ + transaction_retries, \ + __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ + } \ +} /* end of scope opened by BEGIN_TRANSACTION */ + + +/** + * Code to include to retry a transaction, must only be used in between + * #START_TRANSACTION and #COMMIT_TRANSACTION. + * + * @param session session handle + * @param connection connection handle + */ +#define RETRY_TRANSACTION(session,connection) \ + do { \ + TEH_plugin->rollback (TEH_plugin->cls, \ + session); \ + if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \ + goto transaction_start_label; \ + TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \ + transaction_retries, \ + __FUNCTION__); \ + return TEH_RESPONSE_reply_commit_error (connection, \ + TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \ + } while (0) + + + + + +/** + * Send a response for "/refresh/reveal". + * + * @param connection the connection to send the response to + * @param num_newcoins number of new coins for which we reveal data + * @param sigs array of @a num_newcoins signatures revealed + * @return a MHD result code + */ +static int +reply_refresh_reveal_success (struct MHD_Connection *connection, + unsigned int num_newcoins, + const struct TALER_DenominationSignature *sigs) +{ + int newcoin_index; + json_t *root; + json_t *obj; + json_t *list; + int ret; + + list = json_array (); + for (newcoin_index = 0; newcoin_index < num_newcoins; newcoin_index++) + { + obj = json_object (); + json_object_set_new (obj, + "ev_sig", + GNUNET_JSON_from_rsa_signature (sigs[newcoin_index].rsa_signature)); + GNUNET_assert (0 == + json_array_append_new (list, + obj)); + } + root = json_object (); + json_object_set_new (root, + "ev_sigs", + list); + ret = TEH_RESPONSE_reply_json (connection, + root, + MHD_HTTP_OK); + json_decref (root); + return ret; +} + + +/** + * Send a response for a failed "/refresh/reveal", where the + * revealed value(s) do not match the original commitment. + * + * @param connection the connection to send the response to + * @param session info about session + * @param commit_coins array of @a num_newcoins committed envelopes at offset @a gamma + * @param denom_pubs array of @a num_newcoins denomination keys for the new coins + * @param gamma_tp transfer public key at offset @a gamma + * @return a MHD result code + */ +static int +reply_refresh_reveal_missmatch (struct MHD_Connection *connection, + const struct TALER_EXCHANGEDB_RefreshSession *session, + const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins, + const struct TALER_DenominationPublicKey *denom_pubs, + const struct TALER_TransferPublicKeyP *gamma_tp) +{ + json_t *info_new; + json_t *info_commit_k; + unsigned int i; + + info_new = json_array (); + info_commit_k = json_array (); + for (i=0;inum_newcoins;i++) + { + const struct TALER_EXCHANGEDB_RefreshCommitCoin *cc; + json_t *cc_json; + + GNUNET_assert (0 == + json_array_append_new (info_new, + GNUNET_JSON_from_rsa_public_key (denom_pubs[i].rsa_public_key))); + + cc = &commit_coins[i]; + cc_json = json_pack ("{s:o}", + "coin_ev", + GNUNET_JSON_from_data (cc->coin_ev, + cc->coin_ev_size)); + GNUNET_assert (0 == + json_array_append_new (info_commit_k, + cc_json)); + } + return TEH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_CONFLICT, + "{s:s, s:I, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:i}", + "error", "commitment violation", + "code", (json_int_t) TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION, + "coin_sig", GNUNET_JSON_from_data_auto (&session->melt.coin_sig), + "coin_pub", GNUNET_JSON_from_data_auto (&session->melt.coin.coin_pub), + "melt_amount_with_fee", TALER_JSON_from_amount (&session->melt.amount_with_fee), + "melt_fee", TALER_JSON_from_amount (&session->melt.melt_fee), + "newcoin_infos", info_new, + "commit_infos", info_commit_k, + "gamma_tp", GNUNET_JSON_from_data_auto (gamma_tp), + "gamma", (int) session->noreveal_index); +} + + + +/** + * Check if the given @a transfer_privs correspond to an honest + * commitment for the given session. + * Checks that the transfer private keys match their commitments. + * Then derives the shared secret for each #TALER_CNC_KAPPA, and check that they match. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param off commitment offset to check + * @param transfer_priv private transfer key + * @param melt information about the melted coin + * @param num_newcoins number of newcoins being generated + * @param denom_pubs array of @a num_newcoins keys for the new coins + * @param hash_context hash context to update by hashing in the data + * from this offset + * @return #GNUNET_OK if the committment was honest, + * #GNUNET_NO if there was a problem and we generated an error message + * #GNUNET_SYSERR if we could not even generate an error message + */ +static int +check_commitment (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int off, + const struct TALER_TransferPrivateKeyP *transfer_priv, + const struct TALER_EXCHANGEDB_RefreshMelt *melt, + unsigned int num_newcoins, + const struct TALER_DenominationPublicKey *denom_pubs, + struct GNUNET_HashContext *hash_context) +{ + struct TALER_TransferSecretP transfer_secret; + unsigned int j; + + TALER_link_reveal_transfer_secret (transfer_priv, + &melt->coin.coin_pub, + &transfer_secret); + + /* Check that the commitments for all new coins were correct */ + for (j = 0; j < num_newcoins; j++) + { + struct TALER_FreshCoinP fc; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode h_msg; + char *buf; + size_t buf_len; + + TALER_setup_fresh_coin (&transfer_secret, + j, + &fc); + GNUNET_CRYPTO_eddsa_key_get_public (&fc.coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash (&coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + &h_msg); + if (GNUNET_YES != + GNUNET_CRYPTO_rsa_blind (&h_msg, + &fc.blinding_key.bks, + denom_pubs[j].rsa_public_key, + &buf, + &buf_len)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Blind failed (bad denomination key!?)\n"); + return (MHD_YES == + TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_REFRESH_REVEAL_BLINDING_ERROR, + "Blinding error")) + ? GNUNET_NO : GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash_context_read (hash_context, + buf, + buf_len); + GNUNET_free (buf); + } + return GNUNET_OK; +} + + +/** + * Exchange a coin as part of a refresh operation. Obtains the + * envelope from the database and performs the signing operation. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param key_state key state to lookup denomination pubs + * @param denom_pub denomination key for the coin to create + * @param commit_coin the coin that was committed + * @param coin_off number of the coin + * @return NULL on error, otherwise signature over the coin + */ +static struct TALER_DenominationSignature +refresh_exchange_coin (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + struct TEH_KS_StateHandle *key_state, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin, + unsigned int coin_off) +{ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + struct TALER_DenominationSignature ev_sig; + + dki = TEH_KS_denomination_key_lookup (key_state, + denom_pub, + TEH_KS_DKU_WITHDRAW); + if (NULL == dki) + { + GNUNET_break (0); + ev_sig.rsa_signature = NULL; + return ev_sig; + } + if (GNUNET_OK == + TEH_plugin->get_refresh_out (TEH_plugin->cls, + session, + session_hash, + coin_off, + &ev_sig)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning cached reply for /refresh/reveal signature\n"); + return ev_sig; + } + + ev_sig.rsa_signature + = GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key, + commit_coin->coin_ev, + commit_coin->coin_ev_size); + if (NULL == ev_sig.rsa_signature) + { + GNUNET_break (0); + return ev_sig; + } + if (GNUNET_SYSERR == + TEH_plugin->insert_refresh_out (TEH_plugin->cls, + session, + session_hash, + coin_off, + &ev_sig)) + { + GNUNET_break (0); + GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature); + ev_sig.rsa_signature = NULL; + } + + return ev_sig; +} + + +/** + * The client request was well-formed, now execute the DB transaction + * of a "/refresh/reveal" operation. We use the @a ev_sigs and + * @a commit_coins to clean up resources after this function returns + * as we might experience retries of the database transaction. + * + * @param connection the MHD connection to handle + * @param session database session + * @param session_hash hash identifying the refresh session + * @param refresh_session information about the refresh operation we are doing + * @param denom_pubs array of "num_newcoins" denomination keys for the new coins + * @param[out] ev_sigs where to store generated signatures for the new coins, + * array of length "num_newcoins", memory released by the + * caller + * @param[out] commit_coins array of length "num_newcoins" to be used for + * information about the new coins from the commitment. + * @return MHD result code + */ +static int +execute_refresh_reveal_transaction (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + const struct GNUNET_HashCode *session_hash, + const struct TALER_EXCHANGEDB_RefreshSession *refresh_session, + const struct TALER_DenominationPublicKey *denom_pubs, + struct TALER_DenominationSignature *ev_sigs, + struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins) +{ + unsigned int j; + struct TEH_KS_StateHandle *key_state; + int ret; + + START_TRANSACTION (session, connection); + key_state = TEH_KS_acquire (); + for (j=0;jnum_newcoins;j++) + { + if (NULL == ev_sigs[j].rsa_signature) /* could be non-NULL during retries */ + ev_sigs[j] = refresh_exchange_coin (connection, + session, + session_hash, + key_state, + &denom_pubs[j], + &commit_coins[j], + j); + if (NULL == ev_sigs[j].rsa_signature) + { + TEH_plugin->rollback (TEH_plugin->cls, + session); + ret = TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_SIGNING_ERROR); + goto cleanup; + } + } + COMMIT_TRANSACTION (session, connection); + ret = reply_refresh_reveal_success (connection, + refresh_session->num_newcoins, + ev_sigs); + cleanup: + TEH_KS_release (key_state); + return ret; +} + + +/** + * Execute a "/refresh/reveal". The client is revealing to us the + * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the + * revealed transfer keys would allow linkage to the blinded coins, + * and if so, return the signed coins for corresponding to the set of + * coins that was not chosen. + * + * @param connection the MHD connection to handle + * @param session_hash hash identifying the refresh session + * @param transfer_privs array with the revealed transfer keys, + * length must be #TALER_CNC_KAPPA - 1 + * @return MHD result code + */ +static int +execute_refresh_reveal (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + struct TALER_TransferPrivateKeyP *transfer_privs) +{ + int res; + struct TALER_EXCHANGEDB_Session *session; + struct TALER_EXCHANGEDB_RefreshSession refresh_session; + struct TALER_DenominationPublicKey *denom_pubs; + struct TALER_DenominationSignature *ev_sigs; + struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins; + unsigned int i; + unsigned int j; + unsigned int off; + struct GNUNET_HashContext *hash_context; + struct GNUNET_HashCode sh_check; + int ret; + struct TALER_TransferPublicKeyP gamma_tp; + + if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls))) + { + GNUNET_break (0); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_DB_SETUP_FAILED); + } + + res = TEH_plugin->get_refresh_session (TEH_plugin->cls, + session, + session_hash, + &refresh_session); + if (GNUNET_NO == res) + return TEH_RESPONSE_reply_arg_invalid (connection, + TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, + "session_hash"); + if ( (GNUNET_SYSERR == res) || + (refresh_session.noreveal_index >= TALER_CNC_KAPPA) ) + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR); + denom_pubs = GNUNET_new_array (refresh_session.num_newcoins, + struct TALER_DenominationPublicKey); + if (GNUNET_OK != + TEH_plugin->get_refresh_order (TEH_plugin->cls, + session, + session_hash, + refresh_session.num_newcoins, + denom_pubs)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); + return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + hash_context = GNUNET_CRYPTO_hash_context_start (); + /* first, iterate over transfer public keys for hash_context */ + off = 0; + for (i=0;iget_refresh_transfer_public_key (TEH_plugin->cls, + session, + session_hash, + &gamma_tp)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); + GNUNET_CRYPTO_hash_context_abort (hash_context); + return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_TRANSFER_ERROR)) + ? GNUNET_NO : GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash_context_read (hash_context, + &gamma_tp, + sizeof (struct TALER_TransferPublicKeyP)); + } + else + { + /* compute tp from private key */ + struct TALER_TransferPublicKeyP tp; + + GNUNET_CRYPTO_ecdhe_key_get_public (&transfer_privs[i - off].ecdhe_priv, + &tp.ecdhe_pub); + GNUNET_CRYPTO_hash_context_read (hash_context, + &tp, + sizeof (struct TALER_TransferPublicKeyP)); + } + } + + /* next, add all of the hashes from the denomination keys to the + hash_context */ + { + struct TALER_DenominationPublicKey denom_pubs[refresh_session.num_newcoins]; + + if (GNUNET_OK != + TEH_plugin->get_refresh_order (TEH_plugin->cls, + session, + session_hash, + refresh_session.num_newcoins, + denom_pubs)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); + GNUNET_CRYPTO_hash_context_abort (hash_context); + return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR)) + ? GNUNET_NO : GNUNET_SYSERR; + } + for (i=0;iget_refresh_commit_coins (TEH_plugin->cls, + session, + session_hash, + refresh_session.num_newcoins, + commit_coins)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key); + GNUNET_CRYPTO_hash_context_abort (hash_context); + return TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_COMMIT_ERROR); + } + /* add envelopes to hash_context */ + for (j=0;j +*/ +/** + * @file taler-exchange-httpd_refresh_reveal.h + * @brief Handle /refresh/reveal requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_REFRESH_REVEAL_H +#define TALER_EXCHANGE_HTTPD_REFRESH_REVEAL_H + +#include +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a "/refresh/reveal" request. This time, the client reveals + * the private transfer keys except for the cut-and-choose value + * returned from "/refresh/commit". This function parses the revealed + * keys and secrets and ultimately passes everything to + * #TEH_DB_execute_refresh_reveal() which will verify that the + * revealed information is valid then returns the signed refreshed + * coins. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index 286572fea..85575b679 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -878,251 +878,6 @@ TEH_RESPONSE_compile_reserve_history (const struct TALER_EXCHANGEDB_ReserveHisto } -/** - * Send a response for a failed "/refresh/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 - */ -int -TEH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - struct TALER_Amount coin_value, - struct TALER_EXCHANGEDB_TransactionList *tl, - struct TALER_Amount requested, - struct TALER_Amount residual) -{ - json_t *history; - - history = TEH_RESPONSE_compile_transaction_history (tl); - if (NULL == history) - return TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS); - return TEH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_FORBIDDEN, - "{s:s, s:I, s:o, s:o, s:o, s:o, s:o}", - "error", - "insufficient funds", - "code", - (json_int_t) TALER_EC_REFRESH_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); -} - - -/** - * Send a response to a "/refresh/melt" request. - * - * @param connection the connection to send the response to - * @param session_hash hash of the refresh session - * @param noreveal_index which index will the client not have to reveal - * @return a MHD status code - */ -int -TEH_RESPONSE_reply_refresh_melt_success (struct MHD_Connection *connection, - const struct GNUNET_HashCode *session_hash, - uint16_t noreveal_index) -{ - struct TALER_RefreshMeltConfirmationPS body; - struct TALER_ExchangePublicKeyP pub; - struct TALER_ExchangeSignatureP sig; - json_t *sig_json; - - body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS)); - body.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); - body.session_hash = *session_hash; - body.noreveal_index = htons (noreveal_index); - body.reserved = htons (0); - TEH_KS_sign (&body.purpose, - &pub, - &sig); - sig_json = GNUNET_JSON_from_data_auto (&sig); - GNUNET_assert (NULL != sig_json); - return TEH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:i, s:o, s:o}", - "noreveal_index", (int) noreveal_index, - "exchange_sig", sig_json, - "exchange_pub", GNUNET_JSON_from_data_auto (&pub)); -} - - -/** - * Send a response for "/refresh/reveal". - * - * @param connection the connection to send the response to - * @param num_newcoins number of new coins for which we reveal data - * @param sigs array of @a num_newcoins signatures revealed - * @return a MHD result code - */ -int -TEH_RESPONSE_reply_refresh_reveal_success (struct MHD_Connection *connection, - unsigned int num_newcoins, - const struct TALER_DenominationSignature *sigs) -{ - int newcoin_index; - json_t *root; - json_t *obj; - json_t *list; - int ret; - - list = json_array (); - for (newcoin_index = 0; newcoin_index < num_newcoins; newcoin_index++) - { - obj = json_object (); - json_object_set_new (obj, - "ev_sig", - GNUNET_JSON_from_rsa_signature (sigs[newcoin_index].rsa_signature)); - GNUNET_assert (0 == - json_array_append_new (list, - obj)); - } - root = json_object (); - json_object_set_new (root, - "ev_sigs", - list); - ret = TEH_RESPONSE_reply_json (connection, - root, - MHD_HTTP_OK); - json_decref (root); - return ret; -} - - -/** - * Send a response for a failed "/refresh/reveal", where the - * revealed value(s) do not match the original commitment. - * - * @param connection the connection to send the response to - * @param session info about session - * @param commit_coins array of @a num_newcoins committed envelopes at offset @a gamma - * @param denom_pubs array of @a num_newcoins denomination keys for the new coins - * @param gamma_tp transfer public key at offset @a gamma - * @return a MHD result code - */ -int -TEH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_RefreshSession *session, - const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins, - const struct TALER_DenominationPublicKey *denom_pubs, - const struct TALER_TransferPublicKeyP *gamma_tp) -{ - json_t *info_new; - json_t *info_commit_k; - unsigned int i; - - info_new = json_array (); - info_commit_k = json_array (); - for (i=0;inum_newcoins;i++) - { - const struct TALER_EXCHANGEDB_RefreshCommitCoin *cc; - json_t *cc_json; - - GNUNET_assert (0 == - json_array_append_new (info_new, - GNUNET_JSON_from_rsa_public_key (denom_pubs[i].rsa_public_key))); - - cc = &commit_coins[i]; - cc_json = json_pack ("{s:o}", - "coin_ev", - GNUNET_JSON_from_data (cc->coin_ev, - cc->coin_ev_size)); - GNUNET_assert (0 == - json_array_append_new (info_commit_k, - cc_json)); - } - return TEH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_CONFLICT, - "{s:s, s:I, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:i}", - "error", "commitment violation", - "code", (json_int_t) TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION, - "coin_sig", GNUNET_JSON_from_data_auto (&session->melt.coin_sig), - "coin_pub", GNUNET_JSON_from_data_auto (&session->melt.coin.coin_pub), - "melt_amount_with_fee", TALER_JSON_from_amount (&session->melt.amount_with_fee), - "melt_fee", TALER_JSON_from_amount (&session->melt.melt_fee), - "newcoin_infos", info_new, - "commit_infos", info_commit_k, - "gamma_tp", GNUNET_JSON_from_data_auto (gamma_tp), - "gamma", (int) session->noreveal_index); -} - - -/** - * Send a response for "/refresh/link". - * - * @param connection the connection to send the response to - * @param num_sessions number of sessions the coin was used in - * @param sessions array of @a num_session entries with - * information for each session - * @return a MHD result code - */ -int -TEH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection, - unsigned int num_sessions, - const struct TEH_RESPONSE_LinkSessionInfo *sessions) -{ - json_t *root; - json_t *mlist; - int res; - unsigned int i; - - mlist = json_array (); - for (i=0;inext) - { - json_t *obj; - - obj = json_object (); - json_object_set_new (obj, - "denom_pub", - GNUNET_JSON_from_rsa_public_key (pos->denom_pub.rsa_public_key)); - json_object_set_new (obj, - "ev_sig", - GNUNET_JSON_from_rsa_signature (pos->ev_sig.rsa_signature)); - GNUNET_assert (0 == - json_array_append_new (list, - obj)); - } - root = json_object (); - json_object_set_new (root, - "new_coins", - list); - json_object_set_new (root, - "transfer_pub", - GNUNET_JSON_from_data_auto (&sessions[i].transfer_pub)); - GNUNET_assert (0 == - json_array_append_new (mlist, - root)); - } - res = TEH_RESPONSE_reply_json (connection, - mlist, - MHD_HTTP_OK); - json_decref (mlist); - return res; -} - - /** * A merchant asked for details about a deposit, but * we do not know anything about the deposit. Generate the diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h index 5a52be38a..226aa8aae 100644 --- a/src/exchange/taler-exchange-httpd_responses.h +++ b/src/exchange/taler-exchange-httpd_responses.h @@ -300,111 +300,6 @@ TEH_RESPONSE_reply_transfer_pending (struct MHD_Connection *connection, struct GNUNET_TIME_Absolute planned_exec_time); -/** - * Send a confirmation response to a "/refresh/melt" request. - * - * @param connection the connection to send the response to - * @param session_hash hash of the refresh session - * @param noreveal_index which index will the client not have to reveal - * @return a MHD status code - */ -int -TEH_RESPONSE_reply_refresh_melt_success (struct MHD_Connection *connection, - const struct GNUNET_HashCode *session_hash, - uint16_t noreveal_index); - - -/** - * Send a response for a failed "/refresh/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 - * @param residual remaining value of the coin (after subtracting @a tl) - * @return a MHD result code - */ -int -TEH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - struct TALER_Amount coin_value, - struct TALER_EXCHANGEDB_TransactionList *tl, - struct TALER_Amount requested, - struct TALER_Amount residual); - - -/** - * Send a response for "/refresh/reveal". - * - * @param connection the connection to send the response to - * @param num_newcoins number of new coins for which we reveal data - * @param sigs array of @a num_newcoins signatures revealed - * @return a MHD result code - */ -int -TEH_RESPONSE_reply_refresh_reveal_success (struct MHD_Connection *connection, - unsigned int num_newcoins, - const struct TALER_DenominationSignature *sigs); - - -/** - * Send a response for a failed "/refresh/reveal", where the - * revealed value(s) do not match the original commitment. - * - * @param connection the connection to send the response to - * @param session info about session - * @param commit_coins array of @a num_newcoins committed envelopes at offset @a gamma - * @param denom_pubs array of @a num_newcoins denomination keys for the new coins - * @param gamma_tp transfer public key at offset @a gamma - * @return a MHD result code - */ -int -TEH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection, - const struct TALER_EXCHANGEDB_RefreshSession *session, - const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins, - const struct TALER_DenominationPublicKey *denom_pubs, - const struct TALER_TransferPublicKeyP *gamma_tp); - - -/** - * @brief Information for each session a coin was melted into. - */ -struct TEH_RESPONSE_LinkSessionInfo -{ - /** - * Transfer public key of the coin. - */ - struct TALER_TransferPublicKeyP transfer_pub; - - /** - * Linked data of coins being created in the session. - */ - struct TALER_EXCHANGEDB_LinkDataList *ldl; - -}; - - -/** - * Send a response for "/refresh/link". - * - * @param connection the connection to send the response to - * @param num_sessions number of sessions the coin was used in - * @param sessions array of @a num_session entries with - * information for each session - * @return a MHD result code - */ -int -TEH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection, - unsigned int num_sessions, - const struct TEH_RESPONSE_LinkSessionInfo *sessions); - - - /** * Compile the transaction history of a coin into a JSON object. * -- cgit v1.2.3