From 0a2b049864c8dae0c53c203d46fca89e0e66849d Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 29 Feb 2020 16:42:10 +0100 Subject: big rename fest related to #6067 API renaming --- src/auditor/taler-auditor.c | 2 +- src/benchmark/taler-exchange-benchmark.c | 4 +- src/exchange/Makefile.am | 6 +- src/exchange/taler-exchange-httpd.c | 6 +- src/exchange/taler-exchange-httpd_link.c | 226 +++ src/exchange/taler-exchange-httpd_link.h | 45 + src/exchange/taler-exchange-httpd_melt.c | 628 +++++++ src/exchange/taler-exchange-httpd_melt.h | 48 + src/exchange/taler-exchange-httpd_refresh_link.c | 226 --- src/exchange/taler-exchange-httpd_refresh_link.h | 45 - src/exchange/taler-exchange-httpd_refresh_melt.c | 628 ------- src/exchange/taler-exchange-httpd_refresh_melt.h | 48 - src/exchange/taler-exchange-httpd_refresh_reveal.c | 979 ----------- src/exchange/taler-exchange-httpd_refresh_reveal.h | 53 - .../taler-exchange-httpd_refreshes_reveal.c | 979 +++++++++++ .../taler-exchange-httpd_refreshes_reveal.h | 53 + src/exchangedb/plugin_exchangedb_postgres.c | 22 +- src/include/taler_exchange_service.h | 225 +-- src/include/taler_exchangedb_plugin.h | 6 +- src/include/taler_testing_lib.h | 18 +- src/lib/Makefile.am | 13 +- src/lib/exchange_api_common.c | 416 +++++ src/lib/exchange_api_deposits_get.c | 400 +++++ src/lib/exchange_api_link.c | 488 ++++++ src/lib/exchange_api_melt.c | 505 ++++++ src/lib/exchange_api_refresh.c | 1770 -------------------- src/lib/exchange_api_refresh_common.c | 631 +++++++ src/lib/exchange_api_refresh_common.h | 240 +++ src/lib/exchange_api_refresh_link.c | 490 ------ src/lib/exchange_api_refreshes_reveal.c | 512 ++++++ src/lib/exchange_api_reserve.c | 1306 --------------- src/lib/exchange_api_reserves_get.c | 308 ++++ src/lib/exchange_api_track_transaction.c | 402 ----- src/lib/exchange_api_track_transfer.c | 400 ----- src/lib/exchange_api_transfers_get.c | 400 +++++ src/lib/exchange_api_withdraw.c | 611 +++++++ src/testing/test_auditor_api.c | 8 +- src/testing/test_exchange_api.c | 24 +- src/testing/test_exchange_api_revocation.c | 18 +- src/testing/test_exchange_api_twisted.c | 2 +- src/testing/testing_api_cmd_refresh.c | 92 +- 41 files changed, 6732 insertions(+), 6551 deletions(-) create mode 100644 src/exchange/taler-exchange-httpd_link.c create mode 100644 src/exchange/taler-exchange-httpd_link.h create mode 100644 src/exchange/taler-exchange-httpd_melt.c create mode 100644 src/exchange/taler-exchange-httpd_melt.h delete mode 100644 src/exchange/taler-exchange-httpd_refresh_link.c delete mode 100644 src/exchange/taler-exchange-httpd_refresh_link.h delete mode 100644 src/exchange/taler-exchange-httpd_refresh_melt.c delete mode 100644 src/exchange/taler-exchange-httpd_refresh_melt.h delete mode 100644 src/exchange/taler-exchange-httpd_refresh_reveal.c delete mode 100644 src/exchange/taler-exchange-httpd_refresh_reveal.h create mode 100644 src/exchange/taler-exchange-httpd_refreshes_reveal.c create mode 100644 src/exchange/taler-exchange-httpd_refreshes_reveal.h create mode 100644 src/lib/exchange_api_deposits_get.c create mode 100644 src/lib/exchange_api_link.c create mode 100644 src/lib/exchange_api_melt.c delete mode 100644 src/lib/exchange_api_refresh.c create mode 100644 src/lib/exchange_api_refresh_common.c create mode 100644 src/lib/exchange_api_refresh_common.h delete mode 100644 src/lib/exchange_api_refresh_link.c create mode 100644 src/lib/exchange_api_refreshes_reveal.c delete mode 100644 src/lib/exchange_api_reserve.c create mode 100644 src/lib/exchange_api_reserves_get.c delete mode 100644 src/lib/exchange_api_track_transaction.c delete mode 100644 src/lib/exchange_api_track_transfer.c create mode 100644 src/lib/exchange_api_transfers_get.c create mode 100644 src/lib/exchange_api_withdraw.c diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c index 1c6f82c86..d211bd94a 100644 --- a/src/auditor/taler-auditor.c +++ b/src/auditor/taler-auditor.c @@ -3763,7 +3763,7 @@ refresh_session_cb (void *cls, NULL); if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { - report_row_inconsistency ("refresh_melt", + report_row_inconsistency ("melt", rowid, "denomination key not found"); return GNUNET_OK; diff --git a/src/benchmark/taler-exchange-benchmark.c b/src/benchmark/taler-exchange-benchmark.c index 91ed5b047..7e1b8b723 100644 --- a/src/benchmark/taler-exchange-benchmark.c +++ b/src/benchmark/taler-exchange-benchmark.c @@ -374,8 +374,8 @@ run (void *cls, i, j); unit[2] = - TALER_TESTING_cmd_refresh_melt_with_retry - (TALER_TESTING_cmd_refresh_melt + TALER_TESTING_cmd_melt_with_retry + (TALER_TESTING_cmd_melt (add_label (melt_label), withdraw_label, MHD_HTTP_OK, diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index fe1fd397f..61d3341cc 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -51,11 +51,11 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_deposit.c taler-exchange-httpd_deposit.h \ taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \ taler-exchange-httpd_keystate.c taler-exchange-httpd_keystate.h \ + taler-exchange-httpd_link.c taler-exchange-httpd_link.h \ taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \ taler-exchange-httpd_recoup.c taler-exchange-httpd_recoup.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_melt.c taler-exchange-httpd_melt.h \ + taler-exchange-httpd_refreshes_reveal.c taler-exchange-httpd_refreshes_reveal.h \ taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \ taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \ taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 8d5692967..d353a7965 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -33,9 +33,9 @@ #include "taler-exchange-httpd_reserves_get.h" #include "taler-exchange-httpd_withdraw.h" #include "taler-exchange-httpd_recoup.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_link.h" +#include "taler-exchange-httpd_melt.h" +#include "taler-exchange-httpd_refreshes_reveal.h" #include "taler-exchange-httpd_terms.h" #include "taler-exchange-httpd_transfers_get.h" #include "taler-exchange-httpd_deposits_get.h" diff --git a/src/exchange/taler-exchange-httpd_link.c b/src/exchange/taler-exchange-httpd_link.c new file mode 100644 index 000000000..83d7f6a05 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_link.c @@ -0,0 +1,226 @@ +/* + This file is part of TALER + Copyright (C) 2014-2019 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + 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_mhd_lib.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_link.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + + +/** + * Closure for #handle_transfer_data(). + */ +struct HTD_Context +{ + + /** + * Public key of the coin for which we are running /refresh/link. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Json array with transfer data we collect. + */ + json_t *mlist; + + /** + * Taler error code. + */ + enum TALER_ErrorCode ec; +}; + + +/** + * 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 transfer_pub public transfer key for the session + * @param ldl link data related to @a transfer_pub + */ +static void +handle_link_data (void *cls, + const struct TALER_TransferPublicKeyP *transfer_pub, + const struct TALER_EXCHANGEDB_LinkDataList *ldl) +{ + struct HTD_Context *ctx = cls; + json_t *list; + json_t *root; + + if (NULL == ctx->mlist) + return; + if (NULL == (list = json_array ())) + goto fail; + + for (const struct TALER_EXCHANGEDB_LinkDataList *pos = ldl; + NULL != pos; + pos = pos->next) + { + json_t *obj; + + obj = json_pack ("{s:o, s:o, s:o}", + "denom_pub", + GNUNET_JSON_from_rsa_public_key + (pos->denom_pub.rsa_public_key), + "ev_sig", + GNUNET_JSON_from_rsa_signature + (pos->ev_sig.rsa_signature), + "link_sig", + GNUNET_JSON_from_data_auto (&pos->orig_coin_link_sig)); + if ( (NULL == obj) || + (0 != + json_array_append_new (list, + obj)) ) + { + json_decref (list); + goto fail; + } + } + root = json_pack ("{s:o, s:o}", + "new_coins", + list, + "transfer_pub", + GNUNET_JSON_from_data_auto (transfer_pub)); + if ( (NULL == root) || + (0 != + json_array_append_new (ctx->mlist, + root)) ) + goto fail; + return; +fail: + ctx->ec = TALER_EC_JSON_ALLOCATION_FAILURE; + json_decref (ctx->mlist); + ctx->mlist = NULL; +} + + +/** + * 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. + * + * If it returns a non-error code, the transaction logic MUST + * NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF + * it returns the soft error code, the function MAY be called again to + * retry and MUST not queue a MHD response. + * + * @param cls closure + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +refresh_link_transaction (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + struct HTD_Context *ctx = cls; + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->get_link_data (TEH_plugin->cls, + session, + &ctx->coin_pub, + &handle_link_data, + ctx); + if (NULL == ctx->mlist) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + ctx->ec, + "coin_pub"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFRESH_LINK_COIN_UNKNOWN, + "coin_pub"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + return qs; +} + + +/** + * Handle a "/coins/$COIN_PUB/link" request. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param args array of additional options (length: 2, first is the coin_pub, second must be "link") + * @return MHD result code + */ +int +TEH_REFRESH_handler_link (const struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + const char *const args[2]) +{ + struct HTD_Context ctx; + int mhd_ret; + + (void) rh; + memset (&ctx, + 0, + sizeof (ctx)); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &ctx.coin_pub, + sizeof (ctx.coin_pub))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_COINS_INVALID_COIN_PUB, + "coin public key malformed"); + } + ctx.mlist = json_array (); + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + "run link", + &mhd_ret, + &refresh_link_transaction, + &ctx)) + { + if (NULL != ctx.mlist) + json_decref (ctx.mlist); + return mhd_ret; + } + mhd_ret = TALER_MHD_reply_json (connection, + ctx.mlist, + MHD_HTTP_OK); + json_decref (ctx.mlist); + return mhd_ret; +} + + +/* end of taler-exchange-httpd_refresh_link.c */ diff --git a/src/exchange/taler-exchange-httpd_link.h b/src/exchange/taler-exchange-httpd_link.h new file mode 100644 index 000000000..9469c4713 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_link.h @@ -0,0 +1,45 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + 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 "/coins/$COIN_PUB/link" request. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param args array of additional options (length: 2, first is the coin_pub, second must be "link") + * @return MHD result code + */ +int +TEH_REFRESH_handler_link (const struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + const char *const args[2]); + + +#endif diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c new file mode 100644 index 000000000..035fc48f1 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_melt.c @@ -0,0 +1,628 @@ +/* + This file is part of TALER + Copyright (C) 2014-2019 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + 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_melt.c + * @brief Handle /refresh/melt requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_melt.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + + +/** + * Send a response for a failed "/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_melt_insufficient_funds (struct MHD_Connection *connection, + const struct + TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *coin_value, + struct TALER_EXCHANGEDB_TransactionList * + tl, + const struct TALER_Amount *requested, + const struct TALER_Amount *residual) +{ + json_t *history; + + history = TEH_RESPONSE_compile_transaction_history (coin_pub, + tl); + if (NULL == history) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS, + "Failed to compile transaction history"); + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_CONFLICT, + "{s:s, s:I, s:o, s:o, s:o, s:o, s:o}", + "hint", + "insufficient funds", + "code", + (json_int_t) + TALER_EC_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 rc value the client commited to + * @param noreveal_index which index will the client not have to reveal + * @return a MHD status code + */ +static int +reply_melt_success (struct MHD_Connection *connection, + const struct TALER_RefreshCommitmentP *rc, + uint32_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.rc = *rc; + body.noreveal_index = htonl (noreveal_index); + if (GNUNET_OK != + TEH_KS_sign (&body.purpose, + &pub, + &sig)) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_BAD_CONFIGURATION, + "no keys"); + } + sig_json = GNUNET_JSON_from_data_auto (&sig); + GNUNET_assert (NULL != sig_json); + return TALER_MHD_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)); +} + + +/** + * Context for the /refresh/melt operation. + */ +struct RefreshMeltContext +{ + + /** + * noreveal_index is only initialized during + * #melt_transaction(). + */ + struct TALER_EXCHANGEDB_RefreshSession refresh_session; + + /** + * Information about the @e coin's value. + */ + struct TALER_Amount coin_value; + + /** + * Information about the @e coin's refresh fee. + */ + struct TALER_Amount coin_refresh_fee; + + /** + * Set to #GNUNET_YES if this coin's denomination was revoked and the operation + * is thus only allowed for zombie coins where the transaction + * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP. + */ + int zombie_required; + +}; + + +/** + * Check that the coin has sufficient funds left for the selected + * melt operation. + * + * @param connection the connection to send errors to + * @param session the database connection + * @param[in,out] rmc melt context + * @param[out] mhd_ret status code to return to MHD on hard error + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +refresh_check_melt (struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + struct RefreshMeltContext *rmc, + int *mhd_ret) +{ + struct TALER_EXCHANGEDB_TransactionList *tl; + struct TALER_Amount spent; + enum GNUNET_DB_QueryStatus qs; + + /* Start with cost of this melt transaction */ + spent = rmc->refresh_session.amount_with_fee; + + /* add historic transaction costs of this coin, including recoups as + we might be a zombie coin */ + qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, + session, + &rmc->refresh_session.coin.coin_pub, + GNUNET_YES, + &tl); + if (0 > qs) + { + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_MELT_DB_FETCH_ERROR, + "failed to fetch old coin history"); + return qs; + } + if (rmc->zombie_required) + { + for (struct TALER_EXCHANGEDB_TransactionList *tp = tl; + NULL != tp; + tp = tp->next) + { + if (TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP == tp->type) + { + rmc->zombie_required = GNUNET_NO; /* was satisfied! */ + break; + } + } + if (rmc->zombie_required) + { + /* zombie status not satisfied */ + GNUNET_break (0); + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_MELT_COIN_EXPIRED_NO_ZOMBIE, + "denomination expired"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + if (GNUNET_OK != + TALER_EXCHANGEDB_calculate_transaction_list_totals (tl, + &spent, + &spent)) + { + GNUNET_break (0); + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED, + "failed to compute coin transaction history"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + /* Refuse to refresh when the coin's value is insufficient + for the cost of all transactions. */ + if (TALER_amount_cmp (&rmc->coin_value, + &spent) < 0) + { + struct TALER_Amount coin_residual; + + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_subtract (&coin_residual, + &spent, + &rmc->refresh_session.amount_with_fee)); + *mhd_ret = reply_melt_insufficient_funds (connection, + &rmc->refresh_session.coin + .coin_pub, + &rmc->coin_value, + tl, + &rmc->refresh_session. + amount_with_fee, + &coin_residual); + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + /* we're good, coin has sufficient funds to be melted */ + TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, + tl); + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +/** + * 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. + * + * If it returns a non-error code, the transaction logic MUST NOT + * queue a MHD response. IF it returns an hard error, the transaction + * logic MUST queue a MHD response and set @a mhd_ret. If it returns + * the soft error code, the function MAY be called again to retry and + * MUST not queue a MHD response. + * + * @param cls our `struct RefreshMeltContext` + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +melt_transaction (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + struct RefreshMeltContext *rmc = cls; + enum GNUNET_DB_QueryStatus qs; + uint32_t noreveal_index; + + /* Check if we already created such a session */ + qs = TEH_plugin->get_melt_index (TEH_plugin->cls, + session, + &rmc->refresh_session.rc, + &noreveal_index); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + TALER_LOG_DEBUG ("Found already-melted coin\n"); + *mhd_ret = reply_melt_success (connection, + &rmc->refresh_session.rc, + noreveal_index); + /* Note: we return "hard error" to ensure the wrapper + does not retry the transaction, and to also not generate + a "fresh" response (as we would on "success") */ + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (0 > qs) + { + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_MELT_DB_FETCH_ERROR, + "failed to fetch melt index"); + return qs; + } + + /* check coin has enough funds remaining on it to cover melt cost */ + qs = refresh_check_melt (connection, + session, + rmc, + mhd_ret); + if (0 > qs) + return qs; + + /* pick challenge and persist it */ + rmc->refresh_session.noreveal_index + = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + TALER_CNC_KAPPA); + + if (0 >= + (qs = TEH_plugin->insert_melt (TEH_plugin->cls, + session, + &rmc->refresh_session))) + { + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR, + "failed to persist melt data"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + return qs; + } + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +/** + * Handle a "/refresh/melt" request after the first parsing has + * happened. We now need to validate the coins being melted and the + * session signature and then hand things of to execute the melt + * operation. This function parses the JSON arrays and then passes + * processing on to #melt_transaction(). + * + * @param connection the MHD connection to handle + * @param[in,out] rmc details about the melt request + * @return MHD result code + */ +static int +handle_melt (struct MHD_Connection *connection, + struct RefreshMeltContext *rmc) +{ + + /* verify signature of coin for melt operation */ + { + struct TALER_RefreshMeltCoinAffirmationPS body; + + body.purpose.size = htonl (sizeof (struct + TALER_RefreshMeltCoinAffirmationPS)); + body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + body.rc = rmc->refresh_session.rc; + TALER_amount_hton (&body.amount_with_fee, + &rmc->refresh_session.amount_with_fee); + TALER_amount_hton (&body.melt_fee, + &rmc->coin_refresh_fee); + body.coin_pub = rmc->refresh_session.coin.coin_pub; + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, + &body.purpose, + &rmc->refresh_session.coin_sig. + eddsa_signature, + &rmc->refresh_session.coin.coin_pub. + eddsa_pub)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_REFRESH_MELT_COIN_SIGNATURE_INVALID, + "confirm_sig"); + } + } + + /* run transaction */ + { + int mhd_ret; + + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + "run melt", + &mhd_ret, + &melt_transaction, + rmc)) + return mhd_ret; + } + + /* generate ordinary response */ + return reply_melt_success (connection, + &rmc->refresh_session.rc, + rmc->refresh_session.noreveal_index); +} + + +/** + * Check for information about the melted coin's denomination, + * extracting its validity status and fee structure. + * + * @param connection HTTP connection we are handling + * @param rmc parsed request information + * @return MHD status code + */ +static int +check_for_denomination_key (struct MHD_Connection *connection, + struct RefreshMeltContext *rmc) +{ + struct TEH_KS_StateHandle *key_state; + + key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); + if (NULL == key_state) + { + TALER_LOG_ERROR ("Lacking keys to operate\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_BAD_CONFIGURATION, + "no keys"); + } + + { + /* Baseline: check if deposits/refreshs are generally + simply still allowed for this denomination */ + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; + unsigned int hc; + enum TALER_ErrorCode ec; + + dki = TEH_KS_denomination_key_lookup_by_hash (key_state, + &rmc->refresh_session.coin. + denom_pub_hash, + TEH_KS_DKU_DEPOSIT, + &ec, + &hc); + /* Consider case that denomination was revoked but + this coin was already seen and thus refresh is OK. */ + if (NULL == dki) + { + dki = TEH_KS_denomination_key_lookup_by_hash (key_state, + &rmc->refresh_session.coin. + denom_pub_hash, + TEH_KS_DKU_RECOUP, + &ec, + &hc); + if (NULL != dki) + { + struct GNUNET_HashCode denom_hash; + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls, + NULL, + &rmc->refresh_session.coin. + coin_pub, + &denom_hash); + if (0 > qs) + { + TEH_KS_release (key_state); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_MELT_DB_FETCH_ERROR, + "failed to find information about old coin"); + } + GNUNET_break (0 == + GNUNET_memcmp (&denom_hash, + &rmc->refresh_session.coin.denom_pub_hash)); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + /* We never saw this coin before, so _this_ justification is not OK */ + dki = NULL; + } + } + } + + /* Consider the case that the denomination expired for deposits, + but /refresh/recoup refilled the balance of the 'zombie' coin + and we should thus allow the refresh during the legal period. */ + if (NULL == dki) + { + dki = TEH_KS_denomination_key_lookup_by_hash (key_state, + &rmc->refresh_session.coin. + denom_pub_hash, + TEH_KS_DKU_ZOMBIE, + &ec, + &hc); + if (NULL != dki) + rmc->zombie_required = GNUNET_YES; + } + if (NULL == dki) + { + TEH_KS_release (key_state); + TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); + return TALER_MHD_reply_with_error (connection, + hc, + ec, + "unknown denomination"); + } + TALER_amount_ntoh (&rmc->coin_refresh_fee, + &dki->issue.properties.fee_refresh); + TALER_amount_ntoh (&rmc->coin_value, + &dki->issue.properties.value); + if (GNUNET_OK != + TALER_test_coin_valid (&rmc->refresh_session.coin, + &dki->denom_pub)) + { + GNUNET_break_op (0); + TEH_KS_release (key_state); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_REFRESH_MELT_DENOMINATION_SIGNATURE_INVALID, + "denom_sig"); + } + } + TEH_KS_release (key_state); + + /* run actual logic, now that the request was parsed */ + /* make sure coin is 'known' in database */ + { + struct TEH_DB_KnowCoinContext kcc; + int mhd_ret; + + kcc.coin = &rmc->refresh_session.coin; + kcc.connection = connection; + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + "know coin for refresh-melt", + &mhd_ret, + &TEH_DB_know_coin_transaction, + &kcc)) + return mhd_ret; + } + + /* sanity-check that "total melt amount > melt fee" */ + if (TALER_amount_cmp (&rmc->coin_refresh_fee, + &rmc->refresh_session.amount_with_fee) > 0) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_MELT_FEES_EXCEED_CONTRIBUTION, + "melt amount smaller than melting fee"); + } + return handle_melt (connection, + rmc); +} + + +/** + * Handle a "/coins/$COIN_PUB/melt" request. Parses the request into the JSON + * components and then hands things of to #check_for_denomination_key() to + * validate the melted coins, the signature and execute the melt using + * handle_melt(). + + * @param connection the MHD connection to handle + * @param coin_pub public key of the coin + * @param root uploaded JSON data + * @return MHD result code + */ +int +TEH_REFRESH_handler_melt (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const json_t *root) +{ + struct RefreshMeltContext rmc; + int res; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_denomination_signature ("denom_sig", + &rmc.refresh_session.coin.denom_sig), + GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", + &rmc.refresh_session.coin.denom_pub_hash), + GNUNET_JSON_spec_fixed_auto ("confirm_sig", + &rmc.refresh_session.coin_sig), + TALER_JSON_spec_amount ("value_with_fee", + &rmc.refresh_session.amount_with_fee), + GNUNET_JSON_spec_fixed_auto ("rc", + &rmc.refresh_session.rc), + GNUNET_JSON_spec_end () + }; + + memset (&rmc, + 0, + sizeof (rmc)); + rmc.refresh_session.coin.coin_pub = *coin_pub; + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + + res = check_for_denomination_key (connection, + &rmc); + GNUNET_JSON_parse_free (spec); + return res; +} + + +/* end of taler-exchange-httpd_melt.c */ diff --git a/src/exchange/taler-exchange-httpd_melt.h b/src/exchange/taler-exchange-httpd_melt.h new file mode 100644 index 000000000..daf8ffc82 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_melt.h @@ -0,0 +1,48 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + 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_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 "/coins/$COIN_PUB/melt" request. Parses the request into the JSON + * components and then hands things of to #check_for_denomination_key() to + * validate the melted coins, the signature and execute the melt using + * handle_melt(). + + * @param connection the MHD connection to handle + * @param coin_pub public key of the coin + * @param root uploaded JSON data + * @return MHD result code + */ +int +TEH_REFRESH_handler_melt (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const json_t *root); + + +#endif diff --git a/src/exchange/taler-exchange-httpd_refresh_link.c b/src/exchange/taler-exchange-httpd_refresh_link.c deleted file mode 100644 index 6dbaed497..000000000 --- a/src/exchange/taler-exchange-httpd_refresh_link.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2019 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - 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_mhd_lib.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" - - -/** - * Closure for #handle_transfer_data(). - */ -struct HTD_Context -{ - - /** - * Public key of the coin for which we are running /refresh/link. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Json array with transfer data we collect. - */ - json_t *mlist; - - /** - * Taler error code. - */ - enum TALER_ErrorCode ec; -}; - - -/** - * 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 transfer_pub public transfer key for the session - * @param ldl link data related to @a transfer_pub - */ -static void -handle_link_data (void *cls, - const struct TALER_TransferPublicKeyP *transfer_pub, - const struct TALER_EXCHANGEDB_LinkDataList *ldl) -{ - struct HTD_Context *ctx = cls; - json_t *list; - json_t *root; - - if (NULL == ctx->mlist) - return; - if (NULL == (list = json_array ())) - goto fail; - - for (const struct TALER_EXCHANGEDB_LinkDataList *pos = ldl; - NULL != pos; - pos = pos->next) - { - json_t *obj; - - obj = json_pack ("{s:o, s:o, s:o}", - "denom_pub", - GNUNET_JSON_from_rsa_public_key - (pos->denom_pub.rsa_public_key), - "ev_sig", - GNUNET_JSON_from_rsa_signature - (pos->ev_sig.rsa_signature), - "link_sig", - GNUNET_JSON_from_data_auto (&pos->orig_coin_link_sig)); - if ( (NULL == obj) || - (0 != - json_array_append_new (list, - obj)) ) - { - json_decref (list); - goto fail; - } - } - root = json_pack ("{s:o, s:o}", - "new_coins", - list, - "transfer_pub", - GNUNET_JSON_from_data_auto (transfer_pub)); - if ( (NULL == root) || - (0 != - json_array_append_new (ctx->mlist, - root)) ) - goto fail; - return; -fail: - ctx->ec = TALER_EC_JSON_ALLOCATION_FAILURE; - json_decref (ctx->mlist); - ctx->mlist = NULL; -} - - -/** - * 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. - * - * If it returns a non-error code, the transaction logic MUST - * NOT queue a MHD response. IF it returns an hard error, the - * transaction logic MUST queue a MHD response and set @a mhd_ret. IF - * it returns the soft error code, the function MAY be called again to - * retry and MUST not queue a MHD response. - * - * @param cls closure - * @param connection MHD request which triggered the transaction - * @param session database session to use - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -refresh_link_transaction (void *cls, - struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - int *mhd_ret) -{ - struct HTD_Context *ctx = cls; - enum GNUNET_DB_QueryStatus qs; - - qs = TEH_plugin->get_link_data (TEH_plugin->cls, - session, - &ctx->coin_pub, - &handle_link_data, - ctx); - if (NULL == ctx->mlist) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - ctx->ec, - "coin_pub"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFRESH_LINK_COIN_UNKNOWN, - "coin_pub"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - return qs; -} - - -/** - * Handle a "/coins/$COIN_PUB/link" request. - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @param args array of additional options (length: 2, first is the coin_pub, second must be "link") - * @return MHD result code - */ -int -TEH_REFRESH_handler_link (const struct TEH_RequestHandler *rh, - struct MHD_Connection *connection, - const char *const args[2]) -{ - struct HTD_Context ctx; - int mhd_ret; - - (void) rh; - memset (&ctx, - 0, - sizeof (ctx)); - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[0], - strlen (args[0]), - &ctx.coin_pub, - sizeof (ctx.coin_pub))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_COINS_INVALID_COIN_PUB, - "coin public key malformed"); - } - ctx.mlist = json_array (); - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "run link", - &mhd_ret, - &refresh_link_transaction, - &ctx)) - { - if (NULL != ctx.mlist) - json_decref (ctx.mlist); - return mhd_ret; - } - mhd_ret = TALER_MHD_reply_json (connection, - ctx.mlist, - MHD_HTTP_OK); - json_decref (ctx.mlist); - return mhd_ret; -} - - -/* 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 deleted file mode 100644 index 9469c4713..000000000 --- a/src/exchange/taler-exchange-httpd_refresh_link.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2017 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - 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 "/coins/$COIN_PUB/link" request. - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @param args array of additional options (length: 2, first is the coin_pub, second must be "link") - * @return MHD result code - */ -int -TEH_REFRESH_handler_link (const struct TEH_RequestHandler *rh, - struct MHD_Connection *connection, - const char *const args[2]); - - -#endif diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c deleted file mode 100644 index 9d92a4cee..000000000 --- a/src/exchange/taler-exchange-httpd_refresh_melt.c +++ /dev/null @@ -1,628 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2019 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - 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_json_lib.h" -#include "taler_mhd_lib.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" - - -/** - * 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, - const struct TALER_Amount *coin_value, - struct TALER_EXCHANGEDB_TransactionList * - tl, - const struct TALER_Amount *requested, - const struct TALER_Amount *residual) -{ - json_t *history; - - history = TEH_RESPONSE_compile_transaction_history (coin_pub, - tl); - if (NULL == history) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS, - "Failed to compile transaction history"); - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_CONFLICT, - "{s:s, s:I, s:o, s:o, s:o, s:o, s:o}", - "hint", - "insufficient funds", - "code", - (json_int_t) - TALER_EC_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 rc value the client commited to - * @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 TALER_RefreshCommitmentP *rc, - uint32_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.rc = *rc; - body.noreveal_index = htonl (noreveal_index); - if (GNUNET_OK != - TEH_KS_sign (&body.purpose, - &pub, - &sig)) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); - } - sig_json = GNUNET_JSON_from_data_auto (&sig); - GNUNET_assert (NULL != sig_json); - return TALER_MHD_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)); -} - - -/** - * Context for the /refresh/melt operation. - */ -struct RefreshMeltContext -{ - - /** - * noreveal_index is only initialized during - * #refresh_melt_transaction(). - */ - struct TALER_EXCHANGEDB_RefreshSession refresh_session; - - /** - * Information about the @e coin's value. - */ - struct TALER_Amount coin_value; - - /** - * Information about the @e coin's refresh fee. - */ - struct TALER_Amount coin_refresh_fee; - - /** - * Set to #GNUNET_YES if this coin's denomination was revoked and the operation - * is thus only allowed for zombie coins where the transaction - * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP. - */ - int zombie_required; - -}; - - -/** - * Check that the coin has sufficient funds left for the selected - * melt operation. - * - * @param connection the connection to send errors to - * @param session the database connection - * @param[in,out] rmc melt context - * @param[out] mhd_ret status code to return to MHD on hard error - * @return transaction status code - */ -static enum GNUNET_DB_QueryStatus -refresh_check_melt (struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - struct RefreshMeltContext *rmc, - int *mhd_ret) -{ - struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_Amount spent; - enum GNUNET_DB_QueryStatus qs; - - /* Start with cost of this melt transaction */ - spent = rmc->refresh_session.amount_with_fee; - - /* add historic transaction costs of this coin, including recoups as - we might be a zombie coin */ - qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, - session, - &rmc->refresh_session.coin.coin_pub, - GNUNET_YES, - &tl); - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_MELT_DB_FETCH_ERROR, - "failed to fetch old coin history"); - return qs; - } - if (rmc->zombie_required) - { - for (struct TALER_EXCHANGEDB_TransactionList *tp = tl; - NULL != tp; - tp = tp->next) - { - if (TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP == tp->type) - { - rmc->zombie_required = GNUNET_NO; /* was satisfied! */ - break; - } - } - if (rmc->zombie_required) - { - /* zombie status not satisfied */ - GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFRESH_MELT_COIN_EXPIRED_NO_ZOMBIE, - "denomination expired"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } - if (GNUNET_OK != - TALER_EXCHANGEDB_calculate_transaction_list_totals (tl, - &spent, - &spent)) - { - GNUNET_break (0); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED, - "failed to compute coin transaction history"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* Refuse to refresh when the coin's value is insufficient - for the cost of all transactions. */ - if (TALER_amount_cmp (&rmc->coin_value, - &spent) < 0) - { - struct TALER_Amount coin_residual; - - GNUNET_assert (GNUNET_SYSERR != - TALER_amount_subtract (&coin_residual, - &spent, - &rmc->refresh_session.amount_with_fee)); - *mhd_ret = reply_refresh_melt_insufficient_funds (connection, - &rmc->refresh_session.coin - .coin_pub, - &rmc->coin_value, - tl, - &rmc->refresh_session. - amount_with_fee, - &coin_residual); - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* we're good, coin has sufficient funds to be melted */ - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - -/** - * 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. - * - * If it returns a non-error code, the transaction logic MUST NOT - * queue a MHD response. IF it returns an hard error, the transaction - * logic MUST queue a MHD response and set @a mhd_ret. If it returns - * the soft error code, the function MAY be called again to retry and - * MUST not queue a MHD response. - * - * @param cls our `struct RefreshMeltContext` - * @param connection MHD request which triggered the transaction - * @param session database session to use - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -refresh_melt_transaction (void *cls, - struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - int *mhd_ret) -{ - struct RefreshMeltContext *rmc = cls; - enum GNUNET_DB_QueryStatus qs; - uint32_t noreveal_index; - - /* Check if we already created such a session */ - qs = TEH_plugin->get_melt_index (TEH_plugin->cls, - session, - &rmc->refresh_session.rc, - &noreveal_index); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - TALER_LOG_DEBUG ("Found already-melted coin\n"); - *mhd_ret = reply_refresh_melt_success (connection, - &rmc->refresh_session.rc, - noreveal_index); - /* Note: we return "hard error" to ensure the wrapper - does not retry the transaction, and to also not generate - a "fresh" response (as we would on "success") */ - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_MELT_DB_FETCH_ERROR, - "failed to fetch melt index"); - return qs; - } - - /* check coin has enough funds remaining on it to cover melt cost */ - qs = refresh_check_melt (connection, - session, - rmc, - mhd_ret); - if (0 > qs) - return qs; - - /* pick challenge and persist it */ - rmc->refresh_session.noreveal_index - = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, - TALER_CNC_KAPPA); - - if (0 >= - (qs = TEH_plugin->insert_melt (TEH_plugin->cls, - session, - &rmc->refresh_session))) - { - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR, - "failed to persist melt data"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - return qs; - } - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; -} - - -/** - * Handle a "/refresh/melt" request after the first parsing has - * happened. We now need to validate the coins being melted and the - * session signature and then hand things of to execute the melt - * operation. This function parses the JSON arrays and then passes - * processing on to #refresh_melt_transaction(). - * - * @param connection the MHD connection to handle - * @param[in,out] rmc details about the melt request - * @return MHD result code - */ -static int -handle_refresh_melt (struct MHD_Connection *connection, - struct RefreshMeltContext *rmc) -{ - - /* verify signature of coin for melt operation */ - { - struct TALER_RefreshMeltCoinAffirmationPS body; - - body.purpose.size = htonl (sizeof (struct - TALER_RefreshMeltCoinAffirmationPS)); - body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); - body.rc = rmc->refresh_session.rc; - TALER_amount_hton (&body.amount_with_fee, - &rmc->refresh_session.amount_with_fee); - TALER_amount_hton (&body.melt_fee, - &rmc->coin_refresh_fee); - body.coin_pub = rmc->refresh_session.coin.coin_pub; - - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, - &body.purpose, - &rmc->refresh_session.coin_sig. - eddsa_signature, - &rmc->refresh_session.coin.coin_pub. - eddsa_pub)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_REFRESH_MELT_COIN_SIGNATURE_INVALID, - "confirm_sig"); - } - } - - /* run transaction */ - { - int mhd_ret; - - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "run melt", - &mhd_ret, - &refresh_melt_transaction, - rmc)) - return mhd_ret; - } - - /* generate ordinary response */ - return reply_refresh_melt_success (connection, - &rmc->refresh_session.rc, - rmc->refresh_session.noreveal_index); -} - - -/** - * Check for information about the melted coin's denomination, - * extracting its validity status and fee structure. - * - * @param connection HTTP connection we are handling - * @param rmc parsed request information - * @return MHD status code - */ -static int -check_for_denomination_key (struct MHD_Connection *connection, - struct RefreshMeltContext *rmc) -{ - struct TEH_KS_StateHandle *key_state; - - key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); - if (NULL == key_state) - { - TALER_LOG_ERROR ("Lacking keys to operate\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); - } - - { - /* Baseline: check if deposits/refreshs are generally - simply still allowed for this denomination */ - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; - unsigned int hc; - enum TALER_ErrorCode ec; - - dki = TEH_KS_denomination_key_lookup_by_hash (key_state, - &rmc->refresh_session.coin. - denom_pub_hash, - TEH_KS_DKU_DEPOSIT, - &ec, - &hc); - /* Consider case that denomination was revoked but - this coin was already seen and thus refresh is OK. */ - if (NULL == dki) - { - dki = TEH_KS_denomination_key_lookup_by_hash (key_state, - &rmc->refresh_session.coin. - denom_pub_hash, - TEH_KS_DKU_RECOUP, - &ec, - &hc); - if (NULL != dki) - { - struct GNUNET_HashCode denom_hash; - enum GNUNET_DB_QueryStatus qs; - - qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls, - NULL, - &rmc->refresh_session.coin. - coin_pub, - &denom_hash); - if (0 > qs) - { - TEH_KS_release (key_state); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_MELT_DB_FETCH_ERROR, - "failed to find information about old coin"); - } - GNUNET_break (0 == - GNUNET_memcmp (&denom_hash, - &rmc->refresh_session.coin.denom_pub_hash)); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - /* We never saw this coin before, so _this_ justification is not OK */ - dki = NULL; - } - } - } - - /* Consider the case that the denomination expired for deposits, - but /refresh/recoup refilled the balance of the 'zombie' coin - and we should thus allow the refresh during the legal period. */ - if (NULL == dki) - { - dki = TEH_KS_denomination_key_lookup_by_hash (key_state, - &rmc->refresh_session.coin. - denom_pub_hash, - TEH_KS_DKU_ZOMBIE, - &ec, - &hc); - if (NULL != dki) - rmc->zombie_required = GNUNET_YES; - } - if (NULL == dki) - { - TEH_KS_release (key_state); - TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); - return TALER_MHD_reply_with_error (connection, - hc, - ec, - "unknown denomination"); - } - TALER_amount_ntoh (&rmc->coin_refresh_fee, - &dki->issue.properties.fee_refresh); - TALER_amount_ntoh (&rmc->coin_value, - &dki->issue.properties.value); - if (GNUNET_OK != - TALER_test_coin_valid (&rmc->refresh_session.coin, - &dki->denom_pub)) - { - GNUNET_break_op (0); - TEH_KS_release (key_state); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_REFRESH_MELT_DENOMINATION_SIGNATURE_INVALID, - "denom_sig"); - } - } - TEH_KS_release (key_state); - - /* run actual logic, now that the request was parsed */ - /* make sure coin is 'known' in database */ - { - struct TEH_DB_KnowCoinContext kcc; - int mhd_ret; - - kcc.coin = &rmc->refresh_session.coin; - kcc.connection = connection; - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "know coin for refresh-melt", - &mhd_ret, - &TEH_DB_know_coin_transaction, - &kcc)) - return mhd_ret; - } - - /* sanity-check that "total melt amount > melt fee" */ - if (TALER_amount_cmp (&rmc->coin_refresh_fee, - &rmc->refresh_session.amount_with_fee) > 0) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFRESH_MELT_FEES_EXCEED_CONTRIBUTION, - "melt amount smaller than melting fee"); - } - return handle_refresh_melt (connection, - rmc); -} - - -/** - * Handle a "/coins/$COIN_PUB/melt" request. Parses the request into the JSON - * components and then hands things of to #check_for_denomination_key() to - * validate the melted coins, the signature and execute the melt using - * handle_refresh_melt(). - - * @param connection the MHD connection to handle - * @param coin_pub public key of the coin - * @param root uploaded JSON data - * @return MHD result code - */ -int -TEH_REFRESH_handler_melt (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const json_t *root) -{ - struct RefreshMeltContext rmc; - int res; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_denomination_signature ("denom_sig", - &rmc.refresh_session.coin.denom_sig), - GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", - &rmc.refresh_session.coin.denom_pub_hash), - GNUNET_JSON_spec_fixed_auto ("confirm_sig", - &rmc.refresh_session.coin_sig), - TALER_JSON_spec_amount ("value_with_fee", - &rmc.refresh_session.amount_with_fee), - GNUNET_JSON_spec_fixed_auto ("rc", - &rmc.refresh_session.rc), - GNUNET_JSON_spec_end () - }; - - memset (&rmc, - 0, - sizeof (rmc)); - rmc.refresh_session.coin.coin_pub = *coin_pub; - res = TALER_MHD_parse_json_data (connection, - root, - spec); - if (GNUNET_OK != res) - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - - res = check_for_denomination_key (connection, - &rmc); - GNUNET_JSON_parse_free (spec); - return res; -} - - -/* end of taler-exchange-httpd_refresh_melt.c */ diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.h b/src/exchange/taler-exchange-httpd_refresh_melt.h deleted file mode 100644 index 41488c818..000000000 --- a/src/exchange/taler-exchange-httpd_refresh_melt.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2017 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - 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.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 "/coins/$COIN_PUB/melt" request. Parses the request into the JSON - * components and then hands things of to #check_for_denomination_key() to - * validate the melted coins, the signature and execute the melt using - * handle_refresh_melt(). - - * @param connection the MHD connection to handle - * @param coin_pub public key of the coin - * @param root uploaded JSON data - * @return MHD result code - */ -int -TEH_REFRESH_handler_melt (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const json_t *root); - - -#endif diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.c b/src/exchange/taler-exchange-httpd_refresh_reveal.c deleted file mode 100644 index 802f0a8a3..000000000 --- a/src/exchange/taler-exchange-httpd_refresh_reveal.c +++ /dev/null @@ -1,979 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2019 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - 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_mhd_lib.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" - - -/** - * Maximum number of fresh coins we allow per refresh operation. - */ -#define MAX_FRESH_COINS 256 - -/** - * How often do we at most retry the reveal transaction sequence? - * Twice should really suffice in all cases (as the possible conflict - * cannot happen more than once). - */ -#define MAX_REVEAL_RETRIES 2 - - -/** - * 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) -{ - json_t *list; - int ret; - - list = json_array (); - for (unsigned int newcoin_index = 0; - newcoin_index < num_newcoins; - newcoin_index++) - { - json_t *obj; - - 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)); - } - - { - json_t *root; - - root = json_object (); - json_object_set_new (root, - "ev_sigs", - list); - ret = TALER_MHD_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 rc commitment computed by the exchange - * @return a MHD result code - */ -static int -reply_refresh_reveal_mismatch (struct MHD_Connection *connection, - const struct TALER_RefreshCommitmentP *rc) -{ - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_CONFLICT, - "{s:s, s:I, s:o}", - "hint", "commitment violation", - "code", - (json_int_t) - TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION, - "rc_expected", - GNUNET_JSON_from_data_auto (rc)); -} - - -/** - * State for a /refresh/reveal operation. - */ -struct RevealContext -{ - - /** - * Commitment of the refresh operaton. - */ - struct TALER_RefreshCommitmentP rc; - - /** - * Transfer public key at gamma. - */ - struct TALER_TransferPublicKeyP gamma_tp; - - /** - * Transfer private keys revealed to us. - */ - struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1]; - - /** - * Denominations being requested. - */ - const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation **dkis; - - /** - * Envelopes to be signed. - */ - const struct TALER_RefreshCoinData *rcds; - - /** - * Signatures over the link data (of type - * #TALER_SIGNATURE_WALLET_COIN_LINK) - */ - const struct TALER_CoinSpendSignatureP *link_sigs; - - /** - * Envelopes with the signatures to be returned. Initially NULL. - */ - struct TALER_DenominationSignature *ev_sigs; - - /** - * Size of the @e dkis, @e rcds and @e ev_sigs arrays (if non-NULL). - */ - unsigned int num_fresh_coins; - - /** - * Result from preflight checks. #GNUNET_NO for no result, - * #GNUNET_YES if preflight found previous successful operation, - * #GNUNET_SYSERR if prefight check failed hard (and generated - * an MHD response already). - */ - int preflight_ok; - -}; - - -/** - * Function called with information about a refresh order we already - * persisted. Stores the result in @a cls so we don't do the calculation - * again. - * - * @param cls closure with a `struct RevealContext` - * @param num_newcoins size of the @a rrcs array - * @param rrcs array of @a num_newcoins information about coins to be created - * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1 - * @param tprivs array of @e num_tprivs transfer private keys - * @param tp transfer public key information - */ -static void -check_exists_cb (void *cls, - uint32_t num_newcoins, - const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs, - unsigned int num_tprivs, - const struct TALER_TransferPrivateKeyP *tprivs, - const struct TALER_TransferPublicKeyP *tp) -{ - struct RevealContext *rctx = cls; - - if (0 == num_newcoins) - { - GNUNET_break (0); - return; - } - GNUNET_break (TALER_CNC_KAPPA - 1 == num_tprivs); - GNUNET_break_op (0 == - GNUNET_memcmp (tp, - &rctx->gamma_tp)); - GNUNET_break_op (0 == - memcmp (tprivs, - &rctx->transfer_privs, - sizeof (struct TALER_TransferPrivateKeyP) - * num_tprivs)); - /* We usually sign early (optimistic!), but in case we change that *and* - we do find the operation in the database, we could use this: */ - if (NULL == rctx->ev_sigs) - { - rctx->ev_sigs = GNUNET_new_array (num_newcoins, - struct TALER_DenominationSignature); - for (unsigned int i = 0; iev_sigs[i].rsa_signature - = GNUNET_CRYPTO_rsa_signature_dup (rrcs[i].coin_sig.rsa_signature); - } -} - - -/** - * Check if the "/refresh/reveal" was already successful before. - * If so, just return the old result. - * - * @param cls closure of type `struct RevealContext` - * @param connection MHD request which triggered the transaction - * @param session database session to use - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -refresh_reveal_preflight (void *cls, - struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - int *mhd_ret) -{ - struct RevealContext *rctx = cls; - enum GNUNET_DB_QueryStatus qs; - - /* Try to see if we already have given an answer before. */ - qs = TEH_plugin->get_refresh_reveal (TEH_plugin->cls, - session, - &rctx->rc, - &check_exists_cb, - rctx); - switch (qs) - { - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - return qs; /* continue normal execution */ - case GNUNET_DB_STATUS_SOFT_ERROR: - return qs; - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (qs); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_REVEAL_DB_FETCH_REVEAL_ERROR, - "failed to fetch reveal data"); - rctx->preflight_ok = GNUNET_SYSERR; - return GNUNET_DB_STATUS_HARD_ERROR; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - default: - /* Hossa, already found our reply! */ - GNUNET_assert (NULL != rctx->ev_sigs); - rctx->preflight_ok = GNUNET_YES; - return qs; - } -} - - -/** - * 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. - * - * IF it returns a non-error code, the transaction logic MUST - * NOT queue a MHD response. IF it returns an hard error, the - * transaction logic MUST queue a MHD response and set @a mhd_ret. IF - * it returns the soft error code, the function MAY be called again to - * retry and MUST not queue a MHD response. - * - * @param cls closure of type `struct RevealContext` - * @param connection MHD request which triggered the transaction - * @param session database session to use - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -refresh_reveal_transaction (void *cls, - struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - int *mhd_ret) -{ - struct RevealContext *rctx = cls; - struct TALER_EXCHANGEDB_RefreshMelt refresh_melt; - enum GNUNET_DB_QueryStatus qs; - - /* Obtain basic information about the refresh operation and what - gamma we committed to. */ - qs = TEH_plugin->get_melt (TEH_plugin->cls, - session, - &rctx->rc, - &refresh_melt); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, - "rc"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - return qs; - if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) || - (refresh_melt.session.noreveal_index >= TALER_CNC_KAPPA) ) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR, - "failed to fetch valid challenge from database"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - - /* Verify commitment */ - { - /* Note that the contents of rcs[refresh_melt.session.noreveal_index] - will be aliased and are *not* allocated (or deallocated) in - this function -- in contrast to the other offsets! */ - struct TALER_RefreshCommitmentEntry rcs[TALER_CNC_KAPPA]; - struct TALER_RefreshCommitmentP rc_expected; - unsigned int off; - - off = 0; /* did we pass session.noreveal_index yet? */ - for (unsigned int i = 0; itransfer_pub = rctx->gamma_tp; - rce->new_coins = (struct TALER_RefreshCoinData *) rctx->rcds; - off = 1; - } - else - { - /* Reconstruct coin envelopes from transfer private key */ - struct TALER_TransferPrivateKeyP *tpriv = &rctx->transfer_privs[i - - off]; - struct TALER_TransferSecretP ts; - - GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv, - &rce->transfer_pub.ecdhe_pub); - TALER_link_reveal_transfer_secret (tpriv, - &refresh_melt.session.coin.coin_pub, - &ts); - rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins, - struct TALER_RefreshCoinData); - for (unsigned int j = 0; jnum_fresh_coins; j++) - { - struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; - struct TALER_PlanchetSecretsP ps; - struct TALER_PlanchetDetail pd; - - rcd->dk = &rctx->dkis[j]->denom_pub; - TALER_planchet_setup_refresh (&ts, - j, - &ps); - GNUNET_assert (GNUNET_OK == - TALER_planchet_prepare (rcd->dk, - &ps, - &pd)); - rcd->coin_ev = pd.coin_ev; - rcd->coin_ev_size = pd.coin_ev_size; - } - } - } - TALER_refresh_get_commitment (&rc_expected, - TALER_CNC_KAPPA, - rctx->num_fresh_coins, - rcs, - &refresh_melt.session.coin.coin_pub, - &refresh_melt.session.amount_with_fee); - - /* Free resources allocated above */ - for (unsigned int i = 0; inum_fresh_coins; j++) - { - struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; - - GNUNET_free (rcd->coin_ev); - } - GNUNET_free (rce->new_coins); - } - - /* Verify rc_expected matches rc */ - if (0 != GNUNET_memcmp (&rctx->rc, - &rc_expected)) - { - GNUNET_break_op (0); - *mhd_ret = reply_refresh_reveal_mismatch (connection, - &rc_expected); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } /* end of checking "rc_expected" */ - - /* check amounts add up! */ - { - struct TALER_Amount refresh_cost; - - refresh_cost = refresh_melt.melt_fee; - for (unsigned int i = 0; inum_fresh_coins; i++) - { - struct TALER_Amount fee_withdraw; - struct TALER_Amount value; - struct TALER_Amount total; - - TALER_amount_ntoh (&fee_withdraw, - &rctx->dkis[i]->issue.properties.fee_withdraw); - TALER_amount_ntoh (&value, - &rctx->dkis[i]->issue.properties.value); - if ( (GNUNET_OK != - TALER_amount_add (&total, - &fee_withdraw, - &value)) || - (GNUNET_OK != - TALER_amount_add (&refresh_cost, - &refresh_cost, - &total)) ) - { - GNUNET_break_op (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_REVEAL_COST_CALCULATION_OVERFLOW, - "failed to add up refresh costs"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } - if (0 < TALER_amount_cmp (&refresh_cost, - &refresh_melt.session.amount_with_fee)) - { - GNUNET_break_op (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_REVEAL_AMOUNT_INSUFFICIENT, - "melted coin value is insufficient to cover cost of operation"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; -} - - -/** - * Persist result of a "/refresh/reveal". - * - * @param cls closure of type `struct RevealContext` - * @param connection MHD request which triggered the transaction - * @param session database session to use - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!) - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -refresh_reveal_persist (void *cls, - struct MHD_Connection *connection, - struct TALER_EXCHANGEDB_Session *session, - int *mhd_ret) -{ - struct RevealContext *rctx = cls; - enum GNUNET_DB_QueryStatus qs; - - /* Persist operation result in DB */ - { - struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[rctx->num_fresh_coins]; - - for (unsigned int i = 0; inum_fresh_coins; i++) - { - struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; - - rrc->denom_pub = rctx->dkis[i]->denom_pub; - rrc->orig_coin_link_sig = rctx->link_sigs[i]; - rrc->coin_ev = rctx->rcds[i].coin_ev; - rrc->coin_ev_size = rctx->rcds[i].coin_ev_size; - rrc->coin_sig = rctx->ev_sigs[i]; - } - qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls, - session, - &rctx->rc, - rctx->num_fresh_coins, - rrcs, - TALER_CNC_KAPPA - 1, - rctx->transfer_privs, - &rctx->gamma_tp); - } - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_REVEAL_DB_COMMIT_ERROR, - "failed to persist reveal data"); - } - return qs; -} - - -/** - * Resolve denomination hashes using the @a key_state - * - * @param key_state the key state - * @param connection the MHD connection to handle - * @param rctx context for the operation, partially built at this time - * @param link_sigs_json link signatures in JSON format - * @param new_denoms_h_json requests for fresh coins to be created - * @param coin_evs envelopes of gamma-selected coins to be signed - * @return MHD result code - */ -static int -resolve_refresh_reveal_denominations (struct TEH_KS_StateHandle *key_state, - struct MHD_Connection *connection, - struct RevealContext *rctx, - const json_t *link_sigs_json, - const json_t *new_denoms_h_json, - const json_t *coin_evs) -{ - unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); - const struct - TALER_EXCHANGEDB_DenominationKeyIssueInformation *dkis[num_fresh_coins]; - struct GNUNET_HashCode dki_h[num_fresh_coins]; - struct TALER_RefreshCoinData rcds[num_fresh_coins]; - struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins]; - struct TALER_EXCHANGEDB_RefreshMelt refresh_melt; - int res; - - /* Parse denomination key hashes */ - for (unsigned int i = 0; idenom_priv.rsa_private_key); - } - - /* Parse coin envelopes */ - for (unsigned int i = 0; icoin_ev, - &rcd->coin_ev_size), - GNUNET_JSON_spec_end () - }; - - res = TALER_MHD_parse_json_array (connection, - coin_evs, - spec, - i, - -1); - if (GNUNET_OK != res) - { - for (unsigned int j = 0; jdk = &dkis[i]->denom_pub; - } - - /* lookup old_coin_pub in database */ - { - enum GNUNET_DB_QueryStatus qs; - - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - (qs = TEH_plugin->get_melt (TEH_plugin->cls, - NULL, - &rctx->rc, - &refresh_melt))) - { - switch (qs) - { - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - res = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, - "rc"); - break; - case GNUNET_DB_STATUS_HARD_ERROR: - res = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR, - "failed to fetch session data"); - break; - case GNUNET_DB_STATUS_SOFT_ERROR: - default: - GNUNET_break (0); /* should be impossible */ - res = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_INTERNAL_INVARIANT_FAILURE, - "assertion failed"); - break; - } - goto cleanup; - } - } - /* Parse link signatures array */ - for (unsigned int i = 0; igamma_tp; - GNUNET_CRYPTO_hash (rcds[i].coin_ev, - rcds[i].coin_ev_size, - &ldp.coin_envelope_hash); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK, - &ldp.purpose, - &link_sigs[i].eddsa_signature, - &refresh_melt.session.coin.coin_pub. - eddsa_pub)) - { - GNUNET_break_op (0); - res = TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID, - "link_sig"); - goto cleanup; - } - } - } - - rctx->num_fresh_coins = num_fresh_coins; - rctx->rcds = rcds; - rctx->dkis = dkis; - rctx->link_sigs = link_sigs; - - /* sign _early_ (optimistic!) to keep out of transaction scope! */ - rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins, - struct TALER_DenominationSignature); - for (unsigned int i = 0; inum_fresh_coins; i++) - { - rctx->ev_sigs[i].rsa_signature - = GNUNET_CRYPTO_rsa_sign_blinded ( - rctx->dkis[i]->denom_priv.rsa_private_key, - rctx->rcds[i].coin_ev, - rctx->rcds[i].coin_ev_size); - if (NULL == rctx->ev_sigs[i].rsa_signature) - { - GNUNET_break (0); - res = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_REVEAL_SIGNING_ERROR, - "internal signing error"); - goto cleanup; - } - } - - /* We try the three transactions a few times, as theoretically - the pre-check might be satisfied by a concurrent transaction - voiding our final commit due to uniqueness violation; naturally, - on hard errors we exit immediately */ - for (unsigned int retries = 0; retries < MAX_REVEAL_RETRIES; retries++) - { - /* do transactional work */ - rctx->preflight_ok = GNUNET_NO; - if ( (GNUNET_OK == - TEH_DB_run_transaction (connection, - "reveal pre-check", - &res, - &refresh_reveal_preflight, - rctx)) && - (GNUNET_YES == rctx->preflight_ok) ) - { - /* Generate final (positive) response */ - GNUNET_assert (NULL != rctx->ev_sigs); - res = reply_refresh_reveal_success (connection, - num_fresh_coins, - rctx->ev_sigs); - GNUNET_break (MHD_NO != res); - goto cleanup; /* aka 'break' */ - } - if (GNUNET_SYSERR == rctx->preflight_ok) - { - GNUNET_break (0); - goto cleanup; /* aka 'break' */ - } - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "run reveal", - &res, - &refresh_reveal_transaction, - rctx)) - { - /* reveal failed, too bad */ - GNUNET_break_op (0); - goto cleanup; /* aka 'break' */ - } - if (GNUNET_OK == - TEH_DB_run_transaction (connection, - "persist reveal", - &res, - &refresh_reveal_persist, - rctx)) - { - /* Generate final (positive) response */ - GNUNET_assert (NULL != rctx->ev_sigs); - res = reply_refresh_reveal_success (connection, - num_fresh_coins, - rctx->ev_sigs); - break; - } - } /* end for (retries...) */ - -cleanup: - GNUNET_break (MHD_NO != res); - /* free resources */ - if (NULL != rctx->ev_sigs) - { - for (unsigned int i = 0; iev_sigs[i].rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (rctx->ev_sigs[i].rsa_signature); - GNUNET_free (rctx->ev_sigs); - rctx->ev_sigs = NULL; /* just to be safe... */ - } - for (unsigned int i = 0; i= MAX_FRESH_COINS) || - (0 == num_fresh_coins) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE, - "new_denoms_h"); - - } - if (json_array_size (new_denoms_h_json) != - json_array_size (coin_evs)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH, - "new_denoms/coin_evs"); - } - if (json_array_size (new_denoms_h_json) != - json_array_size (link_sigs_json)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH, - "new_denoms/link_sigs"); - } - - /* Parse transfer private keys array */ - for (unsigned int i = 0; itransfer_privs[i]), - GNUNET_JSON_spec_end () - }; - int res; - - res = TALER_MHD_parse_json_array (connection, - tp_json, - trans_spec, - i, - -1); - if (GNUNET_OK != res) - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - } - - { - struct TEH_KS_StateHandle *key_state; - int ret; - - key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); - if (NULL == key_state) - { - TALER_LOG_ERROR ("Lacking keys to operate\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFRESH_REVEAL_KEYS_MISSING, - "exchange lacks keys"); - } - ret = resolve_refresh_reveal_denominations (key_state, - connection, - rctx, - link_sigs_json, - new_denoms_h_json, - coin_evs); - TEH_KS_release (key_state); - return ret; - } -} - - -/** - * Handle a "/refreshes/$RCH/reveal" request. This time, the client reveals the - * private transfer keys except for the cut-and-choose value returned from - * "/coins/$COIN_PUB/melt". This function parses the revealed keys and secrets and - * ultimately passes everything to #resolve_refresh_reveal_denominations() - * which will verify that the revealed information is valid then runs the - * transaction in #refresh_reveal_transaction() and finally returns the signed - * refreshed coins. - * - * @param rh context of the handler - * @param coin_pub public key of the coin - * @param root uploaded JSON data - * @param args array of additional options (length: 2, session hash and the string "reveal") - * @return MHD result code - */ -int -TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh, - struct MHD_Connection *connection, - const json_t *root, - const char *const args[2]) -{ - int res; - json_t *coin_evs; - json_t *transfer_privs; - json_t *link_sigs; - json_t *new_denoms_h; - struct RevealContext rctx; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp), - GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs), - GNUNET_JSON_spec_json ("link_sigs", &link_sigs), - GNUNET_JSON_spec_json ("coin_evs", &coin_evs), - GNUNET_JSON_spec_json ("new_denoms_h", &new_denoms_h), - GNUNET_JSON_spec_end () - }; - - (void) rh; - memset (&rctx, - 0, - sizeof (rctx)); - - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[0], - strlen (args[0]), - &rctx.rc, - sizeof (rctx.rc))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFRESHES_INVALID_RCH, - "refresh commitment hash malformed"); - } - if (0 != strcmp (args[1], - "reveal")) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_OPERATION_INVALID, - "expected 'reveal' operation"); - } - res = TALER_MHD_parse_json_data (connection, - root, - spec); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - } - - /* Check we got enough transfer private keys */ - /* Note we do +1 as 1 row (cut-and-choose!) is missing! */ - if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1) - { - GNUNET_JSON_parse_free (spec); - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFRESH_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID, - "transfer_privs"); - } - res = handle_refresh_reveal_json (connection, - &rctx, - transfer_privs, - link_sigs, - new_denoms_h, - coin_evs); - GNUNET_JSON_parse_free (spec); - return res; -} - - -/* end of taler-exchange-httpd_refresh_reveal.c */ diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.h b/src/exchange/taler-exchange-httpd_refresh_reveal.h deleted file mode 100644 index afc9adce5..000000000 --- a/src/exchange/taler-exchange-httpd_refresh_reveal.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2017 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - 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.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 "/refreshes/$RCH/reveal" request. This time, the client reveals the - * private transfer keys except for the cut-and-choose value returned from - * "/coins/$COIN_PUB/melt". This function parses the revealed keys and secrets and - * ultimately passes everything to #resolve_refresh_reveal_denominations() - * which will verify that the revealed information is valid then runs the - * transaction in #refresh_reveal_transaction() and finally returns the signed - * refreshed coins. - * - * @param rh context of the handler - * @param coin_pub public key of the coin - * @param root uploaded JSON data - * @param args array of additional options (length: 2, session hash and the string "reveal") - * @return MHD result code - */ -int -TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh, - struct MHD_Connection *connection, - const json_t *root, - const char *const args[2]); - - -#endif diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c new file mode 100644 index 000000000..e7e5b97da --- /dev/null +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c @@ -0,0 +1,979 @@ +/* + This file is part of TALER + Copyright (C) 2014-2019 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + 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_refreshes_reveal.c + * @brief Handle /refreshes/$RCH/reveal requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_refreshes_reveal.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + + +/** + * Maximum number of fresh coins we allow per refresh operation. + */ +#define MAX_FRESH_COINS 256 + +/** + * How often do we at most retry the reveal transaction sequence? + * Twice should really suffice in all cases (as the possible conflict + * cannot happen more than once). + */ +#define MAX_REVEAL_RETRIES 2 + + +/** + * 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) +{ + json_t *list; + int ret; + + list = json_array (); + for (unsigned int newcoin_index = 0; + newcoin_index < num_newcoins; + newcoin_index++) + { + json_t *obj; + + 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)); + } + + { + json_t *root; + + root = json_object (); + json_object_set_new (root, + "ev_sigs", + list); + ret = TALER_MHD_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 rc commitment computed by the exchange + * @return a MHD result code + */ +static int +reply_refresh_reveal_mismatch (struct MHD_Connection *connection, + const struct TALER_RefreshCommitmentP *rc) +{ + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_CONFLICT, + "{s:s, s:I, s:o}", + "hint", "commitment violation", + "code", + (json_int_t) + TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION, + "rc_expected", + GNUNET_JSON_from_data_auto (rc)); +} + + +/** + * State for a /refresh/reveal operation. + */ +struct RevealContext +{ + + /** + * Commitment of the refresh operaton. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Transfer public key at gamma. + */ + struct TALER_TransferPublicKeyP gamma_tp; + + /** + * Transfer private keys revealed to us. + */ + struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1]; + + /** + * Denominations being requested. + */ + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation **dkis; + + /** + * Envelopes to be signed. + */ + const struct TALER_RefreshCoinData *rcds; + + /** + * Signatures over the link data (of type + * #TALER_SIGNATURE_WALLET_COIN_LINK) + */ + const struct TALER_CoinSpendSignatureP *link_sigs; + + /** + * Envelopes with the signatures to be returned. Initially NULL. + */ + struct TALER_DenominationSignature *ev_sigs; + + /** + * Size of the @e dkis, @e rcds and @e ev_sigs arrays (if non-NULL). + */ + unsigned int num_fresh_coins; + + /** + * Result from preflight checks. #GNUNET_NO for no result, + * #GNUNET_YES if preflight found previous successful operation, + * #GNUNET_SYSERR if prefight check failed hard (and generated + * an MHD response already). + */ + int preflight_ok; + +}; + + +/** + * Function called with information about a refresh order we already + * persisted. Stores the result in @a cls so we don't do the calculation + * again. + * + * @param cls closure with a `struct RevealContext` + * @param num_newcoins size of the @a rrcs array + * @param rrcs array of @a num_newcoins information about coins to be created + * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1 + * @param tprivs array of @e num_tprivs transfer private keys + * @param tp transfer public key information + */ +static void +check_exists_cb (void *cls, + uint32_t num_newcoins, + const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs, + unsigned int num_tprivs, + const struct TALER_TransferPrivateKeyP *tprivs, + const struct TALER_TransferPublicKeyP *tp) +{ + struct RevealContext *rctx = cls; + + if (0 == num_newcoins) + { + GNUNET_break (0); + return; + } + GNUNET_break (TALER_CNC_KAPPA - 1 == num_tprivs); + GNUNET_break_op (0 == + GNUNET_memcmp (tp, + &rctx->gamma_tp)); + GNUNET_break_op (0 == + memcmp (tprivs, + &rctx->transfer_privs, + sizeof (struct TALER_TransferPrivateKeyP) + * num_tprivs)); + /* We usually sign early (optimistic!), but in case we change that *and* + we do find the operation in the database, we could use this: */ + if (NULL == rctx->ev_sigs) + { + rctx->ev_sigs = GNUNET_new_array (num_newcoins, + struct TALER_DenominationSignature); + for (unsigned int i = 0; iev_sigs[i].rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (rrcs[i].coin_sig.rsa_signature); + } +} + + +/** + * Check if the "/refresh/reveal" was already successful before. + * If so, just return the old result. + * + * @param cls closure of type `struct RevealContext` + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +refresh_reveal_preflight (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + struct RevealContext *rctx = cls; + enum GNUNET_DB_QueryStatus qs; + + /* Try to see if we already have given an answer before. */ + qs = TEH_plugin->get_refresh_reveal (TEH_plugin->cls, + session, + &rctx->rc, + &check_exists_cb, + rctx); + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return qs; /* continue normal execution */ + case GNUNET_DB_STATUS_SOFT_ERROR: + return qs; + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (qs); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_DB_FETCH_REVEAL_ERROR, + "failed to fetch reveal data"); + rctx->preflight_ok = GNUNET_SYSERR; + return GNUNET_DB_STATUS_HARD_ERROR; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + default: + /* Hossa, already found our reply! */ + GNUNET_assert (NULL != rctx->ev_sigs); + rctx->preflight_ok = GNUNET_YES; + return qs; + } +} + + +/** + * 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. + * + * IF it returns a non-error code, the transaction logic MUST + * NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF + * it returns the soft error code, the function MAY be called again to + * retry and MUST not queue a MHD response. + * + * @param cls closure of type `struct RevealContext` + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +refresh_reveal_transaction (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + struct RevealContext *rctx = cls; + struct TALER_EXCHANGEDB_RefreshMelt melt; + enum GNUNET_DB_QueryStatus qs; + + /* Obtain basic information about the refresh operation and what + gamma we committed to. */ + qs = TEH_plugin->get_melt (TEH_plugin->cls, + session, + &rctx->rc, + &melt); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, + "rc"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; + if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) || + (melt.session.noreveal_index >= TALER_CNC_KAPPA) ) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR, + "failed to fetch valid challenge from database"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + /* Verify commitment */ + { + /* Note that the contents of rcs[melt.session.noreveal_index] + will be aliased and are *not* allocated (or deallocated) in + this function -- in contrast to the other offsets! */ + struct TALER_RefreshCommitmentEntry rcs[TALER_CNC_KAPPA]; + struct TALER_RefreshCommitmentP rc_expected; + unsigned int off; + + off = 0; /* did we pass session.noreveal_index yet? */ + for (unsigned int i = 0; itransfer_pub = rctx->gamma_tp; + rce->new_coins = (struct TALER_RefreshCoinData *) rctx->rcds; + off = 1; + } + else + { + /* Reconstruct coin envelopes from transfer private key */ + struct TALER_TransferPrivateKeyP *tpriv = &rctx->transfer_privs[i + - off]; + struct TALER_TransferSecretP ts; + + GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv, + &rce->transfer_pub.ecdhe_pub); + TALER_link_reveal_transfer_secret (tpriv, + &melt.session.coin.coin_pub, + &ts); + rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins, + struct TALER_RefreshCoinData); + for (unsigned int j = 0; jnum_fresh_coins; j++) + { + struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; + struct TALER_PlanchetSecretsP ps; + struct TALER_PlanchetDetail pd; + + rcd->dk = &rctx->dkis[j]->denom_pub; + TALER_planchet_setup_refresh (&ts, + j, + &ps); + GNUNET_assert (GNUNET_OK == + TALER_planchet_prepare (rcd->dk, + &ps, + &pd)); + rcd->coin_ev = pd.coin_ev; + rcd->coin_ev_size = pd.coin_ev_size; + } + } + } + TALER_refresh_get_commitment (&rc_expected, + TALER_CNC_KAPPA, + rctx->num_fresh_coins, + rcs, + &melt.session.coin.coin_pub, + &melt.session.amount_with_fee); + + /* Free resources allocated above */ + for (unsigned int i = 0; inum_fresh_coins; j++) + { + struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; + + GNUNET_free (rcd->coin_ev); + } + GNUNET_free (rce->new_coins); + } + + /* Verify rc_expected matches rc */ + if (0 != GNUNET_memcmp (&rctx->rc, + &rc_expected)) + { + GNUNET_break_op (0); + *mhd_ret = reply_refresh_reveal_mismatch (connection, + &rc_expected); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } /* end of checking "rc_expected" */ + + /* check amounts add up! */ + { + struct TALER_Amount refresh_cost; + + refresh_cost = melt.melt_fee; + for (unsigned int i = 0; inum_fresh_coins; i++) + { + struct TALER_Amount fee_withdraw; + struct TALER_Amount value; + struct TALER_Amount total; + + TALER_amount_ntoh (&fee_withdraw, + &rctx->dkis[i]->issue.properties.fee_withdraw); + TALER_amount_ntoh (&value, + &rctx->dkis[i]->issue.properties.value); + if ( (GNUNET_OK != + TALER_amount_add (&total, + &fee_withdraw, + &value)) || + (GNUNET_OK != + TALER_amount_add (&refresh_cost, + &refresh_cost, + &total)) ) + { + GNUNET_break_op (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_COST_CALCULATION_OVERFLOW, + "failed to add up refresh costs"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + if (0 < TALER_amount_cmp (&refresh_cost, + &melt.session.amount_with_fee)) + { + GNUNET_break_op (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_AMOUNT_INSUFFICIENT, + "melted coin value is insufficient to cover cost of operation"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; +} + + +/** + * Persist result of a "/refresh/reveal". + * + * @param cls closure of type `struct RevealContext` + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +refresh_reveal_persist (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + struct RevealContext *rctx = cls; + enum GNUNET_DB_QueryStatus qs; + + /* Persist operation result in DB */ + { + struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[rctx->num_fresh_coins]; + + for (unsigned int i = 0; inum_fresh_coins; i++) + { + struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; + + rrc->denom_pub = rctx->dkis[i]->denom_pub; + rrc->orig_coin_link_sig = rctx->link_sigs[i]; + rrc->coin_ev = rctx->rcds[i].coin_ev; + rrc->coin_ev_size = rctx->rcds[i].coin_ev_size; + rrc->coin_sig = rctx->ev_sigs[i]; + } + qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls, + session, + &rctx->rc, + rctx->num_fresh_coins, + rrcs, + TALER_CNC_KAPPA - 1, + rctx->transfer_privs, + &rctx->gamma_tp); + } + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_DB_COMMIT_ERROR, + "failed to persist reveal data"); + } + return qs; +} + + +/** + * Resolve denomination hashes using the @a key_state + * + * @param key_state the key state + * @param connection the MHD connection to handle + * @param rctx context for the operation, partially built at this time + * @param link_sigs_json link signatures in JSON format + * @param new_denoms_h_json requests for fresh coins to be created + * @param coin_evs envelopes of gamma-selected coins to be signed + * @return MHD result code + */ +static int +resolve_refresh_reveal_denominations (struct TEH_KS_StateHandle *key_state, + struct MHD_Connection *connection, + struct RevealContext *rctx, + const json_t *link_sigs_json, + const json_t *new_denoms_h_json, + const json_t *coin_evs) +{ + unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); + const struct + TALER_EXCHANGEDB_DenominationKeyIssueInformation *dkis[num_fresh_coins]; + struct GNUNET_HashCode dki_h[num_fresh_coins]; + struct TALER_RefreshCoinData rcds[num_fresh_coins]; + struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins]; + struct TALER_EXCHANGEDB_RefreshMelt melt; + int res; + + /* Parse denomination key hashes */ + for (unsigned int i = 0; idenom_priv.rsa_private_key); + } + + /* Parse coin envelopes */ + for (unsigned int i = 0; icoin_ev, + &rcd->coin_ev_size), + GNUNET_JSON_spec_end () + }; + + res = TALER_MHD_parse_json_array (connection, + coin_evs, + spec, + i, + -1); + if (GNUNET_OK != res) + { + for (unsigned int j = 0; jdk = &dkis[i]->denom_pub; + } + + /* lookup old_coin_pub in database */ + { + enum GNUNET_DB_QueryStatus qs; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + (qs = TEH_plugin->get_melt (TEH_plugin->cls, + NULL, + &rctx->rc, + &melt))) + { + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, + "rc"); + break; + case GNUNET_DB_STATUS_HARD_ERROR: + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR, + "failed to fetch session data"); + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + default: + GNUNET_break (0); /* should be impossible */ + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "assertion failed"); + break; + } + goto cleanup; + } + } + /* Parse link signatures array */ + for (unsigned int i = 0; igamma_tp; + GNUNET_CRYPTO_hash (rcds[i].coin_ev, + rcds[i].coin_ev_size, + &ldp.coin_envelope_hash); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK, + &ldp.purpose, + &link_sigs[i].eddsa_signature, + &melt.session.coin.coin_pub. + eddsa_pub)) + { + GNUNET_break_op (0); + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID, + "link_sig"); + goto cleanup; + } + } + } + + rctx->num_fresh_coins = num_fresh_coins; + rctx->rcds = rcds; + rctx->dkis = dkis; + rctx->link_sigs = link_sigs; + + /* sign _early_ (optimistic!) to keep out of transaction scope! */ + rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins, + struct TALER_DenominationSignature); + for (unsigned int i = 0; inum_fresh_coins; i++) + { + rctx->ev_sigs[i].rsa_signature + = GNUNET_CRYPTO_rsa_sign_blinded ( + rctx->dkis[i]->denom_priv.rsa_private_key, + rctx->rcds[i].coin_ev, + rctx->rcds[i].coin_ev_size); + if (NULL == rctx->ev_sigs[i].rsa_signature) + { + GNUNET_break (0); + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_SIGNING_ERROR, + "internal signing error"); + goto cleanup; + } + } + + /* We try the three transactions a few times, as theoretically + the pre-check might be satisfied by a concurrent transaction + voiding our final commit due to uniqueness violation; naturally, + on hard errors we exit immediately */ + for (unsigned int retries = 0; retries < MAX_REVEAL_RETRIES; retries++) + { + /* do transactional work */ + rctx->preflight_ok = GNUNET_NO; + if ( (GNUNET_OK == + TEH_DB_run_transaction (connection, + "reveal pre-check", + &res, + &refresh_reveal_preflight, + rctx)) && + (GNUNET_YES == rctx->preflight_ok) ) + { + /* Generate final (positive) response */ + GNUNET_assert (NULL != rctx->ev_sigs); + res = reply_refresh_reveal_success (connection, + num_fresh_coins, + rctx->ev_sigs); + GNUNET_break (MHD_NO != res); + goto cleanup; /* aka 'break' */ + } + if (GNUNET_SYSERR == rctx->preflight_ok) + { + GNUNET_break (0); + goto cleanup; /* aka 'break' */ + } + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + "run reveal", + &res, + &refresh_reveal_transaction, + rctx)) + { + /* reveal failed, too bad */ + GNUNET_break_op (0); + goto cleanup; /* aka 'break' */ + } + if (GNUNET_OK == + TEH_DB_run_transaction (connection, + "persist reveal", + &res, + &refresh_reveal_persist, + rctx)) + { + /* Generate final (positive) response */ + GNUNET_assert (NULL != rctx->ev_sigs); + res = reply_refresh_reveal_success (connection, + num_fresh_coins, + rctx->ev_sigs); + break; + } + } /* end for (retries...) */ + +cleanup: + GNUNET_break (MHD_NO != res); + /* free resources */ + if (NULL != rctx->ev_sigs) + { + for (unsigned int i = 0; iev_sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (rctx->ev_sigs[i].rsa_signature); + GNUNET_free (rctx->ev_sigs); + rctx->ev_sigs = NULL; /* just to be safe... */ + } + for (unsigned int i = 0; i= MAX_FRESH_COINS) || + (0 == num_fresh_coins) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE, + "new_denoms_h"); + + } + if (json_array_size (new_denoms_h_json) != + json_array_size (coin_evs)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH, + "new_denoms/coin_evs"); + } + if (json_array_size (new_denoms_h_json) != + json_array_size (link_sigs_json)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH, + "new_denoms/link_sigs"); + } + + /* Parse transfer private keys array */ + for (unsigned int i = 0; itransfer_privs[i]), + GNUNET_JSON_spec_end () + }; + int res; + + res = TALER_MHD_parse_json_array (connection, + tp_json, + trans_spec, + i, + -1); + if (GNUNET_OK != res) + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + + { + struct TEH_KS_StateHandle *key_state; + int ret; + + key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); + if (NULL == key_state) + { + TALER_LOG_ERROR ("Lacking keys to operate\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_KEYS_MISSING, + "exchange lacks keys"); + } + ret = resolve_refresh_reveal_denominations (key_state, + connection, + rctx, + link_sigs_json, + new_denoms_h_json, + coin_evs); + TEH_KS_release (key_state); + return ret; + } +} + + +/** + * Handle a "/refreshes/$RCH/reveal" request. This time, the client reveals the + * private transfer keys except for the cut-and-choose value returned from + * "/coins/$COIN_PUB/melt". This function parses the revealed keys and secrets and + * ultimately passes everything to #resolve_refresh_reveal_denominations() + * which will verify that the revealed information is valid then runs the + * transaction in #refresh_reveal_transaction() and finally returns the signed + * refreshed coins. + * + * @param rh context of the handler + * @param coin_pub public key of the coin + * @param root uploaded JSON data + * @param args array of additional options (length: 2, session hash and the string "reveal") + * @return MHD result code + */ +int +TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + const json_t *root, + const char *const args[2]) +{ + int res; + json_t *coin_evs; + json_t *transfer_privs; + json_t *link_sigs; + json_t *new_denoms_h; + struct RevealContext rctx; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp), + GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs), + GNUNET_JSON_spec_json ("link_sigs", &link_sigs), + GNUNET_JSON_spec_json ("coin_evs", &coin_evs), + GNUNET_JSON_spec_json ("new_denoms_h", &new_denoms_h), + GNUNET_JSON_spec_end () + }; + + (void) rh; + memset (&rctx, + 0, + sizeof (rctx)); + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &rctx.rc, + sizeof (rctx.rc))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESHES_INVALID_RCH, + "refresh commitment hash malformed"); + } + if (0 != strcmp (args[1], + "reveal")) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_OPERATION_INVALID, + "expected 'reveal' operation"); + } + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + /* Check we got enough transfer private keys */ + /* Note we do +1 as 1 row (cut-and-choose!) is missing! */ + if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1) + { + GNUNET_JSON_parse_free (spec); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID, + "transfer_privs"); + } + res = handle_refresh_reveal_json (connection, + &rctx, + transfer_privs, + link_sigs, + new_denoms_h, + coin_evs); + GNUNET_JSON_parse_free (spec); + return res; +} + + +/* end of taler-exchange-httpd_refresh_reveal.c */ diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.h b/src/exchange/taler-exchange-httpd_refreshes_reveal.h new file mode 100644 index 000000000..afc9adce5 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.h @@ -0,0 +1,53 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + 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.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 "/refreshes/$RCH/reveal" request. This time, the client reveals the + * private transfer keys except for the cut-and-choose value returned from + * "/coins/$COIN_PUB/melt". This function parses the revealed keys and secrets and + * ultimately passes everything to #resolve_refresh_reveal_denominations() + * which will verify that the revealed information is valid then runs the + * transaction in #refresh_reveal_transaction() and finally returns the signed + * refreshed coins. + * + * @param rh context of the handler + * @param coin_pub public key of the coin + * @param root uploaded JSON data + * @param args array of additional options (length: 2, session hash and the string "reveal") + * @return MHD result code + */ +int +TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + const json_t *root, + const char *const args[2]); + + +#endif diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 9df2fe770..f8aea50c0 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -3389,8 +3389,8 @@ postgres_select_refunds_by_coin (void *cls, * @param cls the `struct PostgresClosure` with the plugin-specific state * @param session database handle to use, NULL if not run in any transaction * @param rc commitment hash to use to locate the operation - * @param[out] refresh_melt where to store the result; note that - * refresh_melt->session.coin.denom_sig will be set to NULL + * @param[out] melt where to store the result; note that + * melt->session.coin.denom_sig will be set to NULL * and is not fetched by this routine (as it is not needed by the client) * @return transaction status */ @@ -3398,7 +3398,7 @@ static enum GNUNET_DB_QueryStatus postgres_get_melt (void *cls, struct TALER_EXCHANGEDB_Session *session, const struct TALER_RefreshCommitmentP *rc, - struct TALER_EXCHANGEDB_RefreshMelt *refresh_melt) + struct TALER_EXCHANGEDB_RefreshMelt *melt) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -3407,30 +3407,30 @@ postgres_get_melt (void *cls, }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", - &refresh_melt->session.coin. + &melt->session.coin. denom_pub_hash), TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh", - &refresh_melt->melt_fee), + &melt->melt_fee), GNUNET_PQ_result_spec_uint32 ("noreveal_index", - &refresh_melt->session.noreveal_index), + &melt->session.noreveal_index), GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub", - &refresh_melt->session.coin.coin_pub), + &melt->session.coin.coin_pub), GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", - &refresh_melt->session.coin_sig), + &melt->session.coin_sig), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", - &refresh_melt->session.amount_with_fee), + &melt->session.amount_with_fee), GNUNET_PQ_result_spec_end }; enum GNUNET_DB_QueryStatus qs; - refresh_melt->session.coin.denom_sig.rsa_signature = NULL; + melt->session.coin.denom_sig.rsa_signature = NULL; if (NULL == session) session = postgres_get_session (pg); qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn, "get_melt", params, rs); - refresh_melt->session.rc = *rc; + melt->session.rc = *rc; return qs; } diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 0696d96cd..4e917df87 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -697,7 +697,7 @@ void TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh); -/* ********************* /deposit *********************** */ +/* ********************* /coins/$COIN_PUB/deposit *********************** */ /** @@ -799,7 +799,7 @@ void TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit); -/* ********************* /refund *********************** */ +/* ********************* /coins/$COIN_PUB/refund *********************** */ /** * @brief A Refund Handle @@ -926,7 +926,7 @@ void TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund); -/* ********************* /reserve/status *********************** */ +/* ********************* GET /reserves/$RESERVE_PUB *********************** */ /** @@ -1147,14 +1147,14 @@ TALER_EXCHANGE_reserves_get (struct TALER_EXCHANGE_Handle *exchange, */ void TALER_EXCHANGE_reserves_get_cancel (struct - TALER_EXCHANGE_ReservesGetHandle *rsh); + TALER_EXCHANGE_ReservesGetHandle *rhh); -/* ********************* /reserve/withdraw *********************** */ +/* ********************* POST /reserves/$RESERVE_PUB/withdraw *********************** */ /** - * @brief A /reserve/withdraw Handle + * @brief A /reserves/$RESERVE_PUB/withdraw Handle */ struct TALER_EXCHANGE_WithdrawHandle; @@ -1175,13 +1175,12 @@ typedef void unsigned int http_status, enum TALER_ErrorCode ec, const struct - TALER_DenominationSignature * - sig, + TALER_DenominationSignature *sig, const json_t *full_response); /** - * Withdraw a coin from the exchange using a /reserve/withdraw + * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw * request. This API is typically used by a wallet to withdraw from a * reserve. * @@ -1213,7 +1212,7 @@ TALER_EXCHANGE_withdraw (struct TALER_EXCHANGE_Handle *exchange, /** - * Withdraw a coin from the exchange using a /reserve/withdraw + * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw * request. This API is typically used by a wallet to withdraw a tip * where the reserve's signature was created by the merchant already. * @@ -1252,12 +1251,10 @@ TALER_EXCHANGE_withdraw2 (struct TALER_EXCHANGE_Handle *exchange, * Cancel a withdraw status request. This function cannot be used * on a request handle if a response is already served for it. * - * @param sign the withdraw sign request handle + * @param wh the withdraw handle */ void -TALER_EXCHANGE_withdraw_cancel (struct - TALER_EXCHANGE_WithdrawHandle * - sign); +TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh); /* ********************* /refresh/melt+reveal ***************************** */ @@ -1273,7 +1270,7 @@ TALER_EXCHANGE_withdraw_cancel (struct * no money is lost in case of hardware failures, is operation does * not actually initiate the request. Instead, it generates a buffer * which the caller must store before proceeding with the actual call - * to #TALER_EXCHANGE_refresh_melt() that will generate the request. + * to #TALER_EXCHANGE_melt() that will generate the request. * * This function does verify that the given request data is internally * consistent. However, the @a melts_sigs are NOT verified. @@ -1298,7 +1295,7 @@ TALER_EXCHANGE_withdraw_cancel (struct * @return NULL * if the inputs are invalid (i.e. denomination key not with this exchange). * Otherwise, pointer to a buffer of @a res_size to store persistently - * before proceeding to #TALER_EXCHANGE_refresh_melt(). + * before proceeding to #TALER_EXCHANGE_melt(). * Non-null results should be freed using GNUNET_free(). */ char * @@ -1315,18 +1312,19 @@ TALER_EXCHANGE_refresh_prepare (const struct size_t *res_size); -/* ********************* /refresh/melt ***************************** */ +/* ********************* /coins/$COIN_PUB/melt ***************************** */ /** - * @brief A /refresh/melt Handle + * @brief A /coins/$COIN_PUB/melt Handle */ -struct TALER_EXCHANGE_RefreshMeltHandle; +struct TALER_EXCHANGE_MeltHandle; /** - * Callbacks of this type are used to notify the application about the - * result of the /refresh/melt stage. If successful, the @a noreveal_index - * should be committed to disk prior to proceeding #TALER_EXCHANGE_refresh_reveal(). + * Callbacks of this type are used to notify the application about the result + * of the /coins/$COIN_PUB/melt stage. If successful, the @a noreveal_index + * should be committed to disk prior to proceeding + * #TALER_EXCHANGE_refreshes_reveal(). * * @param cls closure * @param http_status HTTP response code, never #MHD_HTTP_OK (200) as for successful intermediate response this callback is skipped. @@ -1338,17 +1336,16 @@ struct TALER_EXCHANGE_RefreshMeltHandle; * @param full_response full response from the exchange (for logging, in case of errors) */ typedef void -(*TALER_EXCHANGE_RefreshMeltCallback) (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - uint32_t noreveal_index, - const struct - TALER_ExchangePublicKeyP *sign_key, - const json_t *full_response); +(*TALER_EXCHANGE_MeltCallback) (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + uint32_t noreveal_index, + const struct TALER_ExchangePublicKeyP *sign_key, + const json_t *full_response); /** - * Submit a refresh melt request to the exchange and get the exchange's + * Submit a melt request to the exchange and get the exchange's * response. * * This API is typically used by a wallet. Note that to ensure that @@ -1367,26 +1364,25 @@ typedef void * @return a handle for this request; NULL if the argument was invalid. * In this case, neither callback will be called. */ -struct TALER_EXCHANGE_RefreshMeltHandle * -TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange, - size_t refresh_data_length, - const char *refresh_data, - TALER_EXCHANGE_RefreshMeltCallback melt_cb, - void *melt_cb_cls); +struct TALER_EXCHANGE_MeltHandle * +TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange, + size_t refresh_data_length, + const char *refresh_data, + TALER_EXCHANGE_MeltCallback melt_cb, + void *melt_cb_cls); /** - * Cancel a refresh melt request. This function cannot be used + * Cancel a melt request. This function cannot be used * on a request handle if the callback was already invoked. * - * @param rmh the refresh handle + * @param mh the melt handle */ void -TALER_EXCHANGE_refresh_melt_cancel (struct - TALER_EXCHANGE_RefreshMeltHandle *rmh); +TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh); -/* ********************* /refresh/reveal ***************************** */ +/* ********************* /refreshes/$RCH/reveal ***************************** */ /** @@ -1407,25 +1403,25 @@ TALER_EXCHANGE_refresh_melt_cancel (struct * @param full_response full response from the exchange (for logging, in case of errors) */ typedef void -(*TALER_EXCHANGE_RefreshRevealCallback) (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - unsigned int num_coins, - const struct - TALER_PlanchetSecretsP *coin_privs, - const struct - TALER_DenominationSignature *sigs, - const json_t *full_response); +(*TALER_EXCHANGE_RefreshesRevealCallback)(void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_coins, + const struct + TALER_PlanchetSecretsP *coin_privs, + const struct + TALER_DenominationSignature *sigs, + const json_t *full_response); /** - * @brief A /refresh/reveal Handle + * @brief A /refreshes/$RCH/reveal Handle */ -struct TALER_EXCHANGE_RefreshRevealHandle; +struct TALER_EXCHANGE_RefreshesRevealHandle; /** - * Submit a /refresh/reval request to the exchange and get the exchange's + * Submit a /refreshes/$RCH/reval request to the exchange and get the exchange's * response. * * This API is typically used by a wallet. Note that to ensure that @@ -1439,20 +1435,21 @@ struct TALER_EXCHANGE_RefreshRevealHandle; * @param refresh_data the refresh data as returned from #TALER_EXCHANGE_refresh_prepare()) * @param noreveal_index response from the exchange to the - * #TALER_EXCHANGE_refresh_melt() invocation + * #TALER_EXCHANGE_melt() invocation * @param reveal_cb the callback to call with the final result of the * refresh operation * @param reveal_cb_cls closure for the above callback * @return a handle for this request; NULL if the argument was invalid. * In this case, neither callback will be called. */ -struct TALER_EXCHANGE_RefreshRevealHandle * -TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange, - size_t refresh_data_length, - const char *refresh_data, - uint32_t noreveal_index, - TALER_EXCHANGE_RefreshRevealCallback reveal_cb, - void *reveal_cb_cls); +struct TALER_EXCHANGE_RefreshesRevealHandle * +TALER_EXCHANGE_refreshes_reveal (struct TALER_EXCHANGE_Handle *exchange, + size_t refresh_data_length, + const char *refresh_data, + uint32_t noreveal_index, + TALER_EXCHANGE_RefreshesRevealCallback + reveal_cb, + void *reveal_cb_cls); /** @@ -1462,24 +1459,25 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange, * @param rrh the refresh reval handle */ void -TALER_EXCHANGE_refresh_reveal_cancel (struct - TALER_EXCHANGE_RefreshRevealHandle *rrh); +TALER_EXCHANGE_refreshes_reveal_cancel (struct + TALER_EXCHANGE_RefreshesRevealHandle * + rrh); -/* ********************* /refresh/link ***************************** */ +/* ********************* /coins/$COIN_PUB/link ***************************** */ /** - * @brief A /refresh/link Handle + * @brief A /coins/$COIN_PUB/link Handle */ -struct TALER_EXCHANGE_RefreshLinkHandle; +struct TALER_EXCHANGE_LinkHandle; /** - * Callbacks of this type are used to return the final result of - * submitting a /refresh/link request to a exchange. If the operation was - * successful, this function returns the signatures over the coins - * that were created when the original coin was melted. + * Callbacks of this type are used to return the final result of submitting a + * /coins/$COIN_PUB/link request to a exchange. If the operation was + * successful, this function returns the signatures over the coins that were + * created when the original coin was melted. * * @param cls closure * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request @@ -1492,26 +1490,24 @@ struct TALER_EXCHANGE_RefreshLinkHandle; * @param full_response full response from the exchange (for logging, in case of errors) */ typedef void -(*TALER_EXCHANGE_RefreshLinkCallback) (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - unsigned int num_coins, - const struct - TALER_CoinSpendPrivateKeyP *coin_privs, - const struct - TALER_DenominationSignature *sigs, - const struct - TALER_DenominationPublicKey *pubs, - const json_t *full_response); +(*TALER_EXCHANGE_LinkCallback) (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_coins, + const struct + TALER_CoinSpendPrivateKeyP *coin_privs, + const struct + TALER_DenominationSignature *sigs, + const struct + TALER_DenominationPublicKey *pubs, + const json_t *full_response); /** - * Submit a refresh link request to the exchange and get the - * exchange's response. + * Submit a link request to the exchange and get the exchange's response. * - * This API is typically not used by anyone, it is more a threat - * against those trying to receive a funds transfer by abusing the - * /refresh protocol. + * This API is typically not used by anyone, it is more a threat against those + * trying to receive a funds transfer by abusing the refresh protocol. * * @param exchange the exchange handle; the exchange must be ready to operate * @param coin_priv private key to request link data for @@ -1520,22 +1516,21 @@ typedef void * @param link_cb_cls closure for @a link_cb * @return a handle for this request */ -struct TALER_EXCHANGE_RefreshLinkHandle * -TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_CoinSpendPrivateKeyP *coin_priv, - TALER_EXCHANGE_RefreshLinkCallback link_cb, - void *link_cb_cls); +struct TALER_EXCHANGE_LinkHandle * +TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + TALER_EXCHANGE_LinkCallback link_cb, + void *link_cb_cls); /** - * Cancel a refresh link request. This function cannot be used + * Cancel a link request. This function cannot be used * on a request handle if the callback was already invoked. * - * @param rlh the refresh link handle + * @param lh the link handle */ void -TALER_EXCHANGE_refresh_link_cancel (struct - TALER_EXCHANGE_RefreshLinkHandle *rlh); +TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh); /* ********************* /transfers/$WTID *********************** */ @@ -1709,6 +1704,46 @@ TALER_EXCHANGE_verify_coin_history (const struct struct TALER_Amount *total); +/** + * Parse history given in JSON format and return it in binary + * format. + * + * @param exchange connection to the exchange we can use + * @param history JSON array with the history + * @param reserve_pub public key of the reserve to inspect + * @param currency currency we expect the balance to be in + * @param[out] balance final balance + * @param history_length number of entries in @a history + * @param[out] rhistory array of length @a history_length, set to the + * parsed history entries + * @return #GNUNET_OK if history was valid and @a rhistory and @a balance + * were set, + * #GNUNET_SYSERR if there was a protocol violation in @a history + */ +int +TALER_EXCHANGE_parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange, + const json_t *history, + const struct + TALER_ReservePublicKeyP *reserve_pub, + const char *currency, + struct TALER_Amount *balance, + unsigned int history_length, + struct TALER_EXCHANGE_ReserveHistory * + rhistory); + + +/** + * Free memory (potentially) allocated by #TALER_EXCHANGE_parse_reserve_history(). + * + * @param rhistory result to free + * @param len number of entries in @a rhistory + */ +void +TALER_EXCHANGE_free_reserve_history (struct + TALER_EXCHANGE_ReserveHistory *rhistory, + unsigned int len); + + /* ********************* /recoup *********************** */ diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 4db76dd77..754ac1ef6 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -2087,8 +2087,8 @@ struct TALER_EXCHANGEDB_Plugin * @param cls the @e cls of this struct with the plugin-specific state * @param session database handle to use * @param rc commitment to use for the lookup - * @param[out] refresh_melt where to store the result; note that - * refresh_melt->session.coin.denom_sig will be set to NULL + * @param[out] melt where to store the result; note that + * melt->session.coin.denom_sig will be set to NULL * and is not fetched by this routine (as it is not needed by the client) * @return transaction status */ @@ -2096,7 +2096,7 @@ struct TALER_EXCHANGEDB_Plugin (*get_melt)(void *cls, struct TALER_EXCHANGEDB_Session *session, const struct TALER_RefreshCommitmentP *rc, - struct TALER_EXCHANGEDB_RefreshMelt *refresh_melt); + struct TALER_EXCHANGEDB_RefreshMelt *melt); /** diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 5aa50155c..7dadc1f47 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -1307,10 +1307,10 @@ TALER_TESTING_cmd_deposit_with_retry (struct TALER_TESTING_Command cmd); * @return the command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_refresh_melt (const char *label, - const char *coin_reference, - unsigned int expected_response_code, - ...); +TALER_TESTING_cmd_melt (const char *label, + const char *coin_reference, + unsigned int expected_response_code, + ...); /** @@ -1326,10 +1326,10 @@ TALER_TESTING_cmd_refresh_melt (const char *label, * @return the command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_refresh_melt_double (const char *label, - const char *coin_reference, - unsigned int expected_response_code, - ...); +TALER_TESTING_cmd_melt_double (const char *label, + const char *coin_reference, + unsigned int expected_response_code, + ...); /** @@ -1339,7 +1339,7 @@ TALER_TESTING_cmd_refresh_melt_double (const char *label, * @return modified command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_refresh_melt_with_retry (struct TALER_TESTING_Command cmd); +TALER_TESTING_cmd_melt_with_retry (struct TALER_TESTING_Command cmd); /** diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index f03522a69..87f343094 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -25,13 +25,16 @@ libtalerexchange_la_SOURCES = \ exchange_api_common.c \ exchange_api_handle.c exchange_api_handle.h \ exchange_api_deposit.c \ + exchange_api_deposits_get.c \ + exchange_api_link.c \ + exchange_api_melt.c \ exchange_api_recoup.c \ - exchange_api_refresh.c \ - exchange_api_refresh_link.c \ + exchange_api_refresh_common.c exchange_api_refresh_common.h \ + exchange_api_refreshes_reveal.c \ exchange_api_refund.c \ - exchange_api_reserve.c \ - exchange_api_track_transaction.c \ - exchange_api_track_transfer.c \ + exchange_api_reserves_get.c \ + exchange_api_transfers_get.c \ + exchange_api_withdraw.c \ exchange_api_wire.c libtalerexchange_la_LIBADD = \ libtalerauditor.la \ diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index d5f31b064..6b0d638e8 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -26,6 +26,422 @@ #include "taler_signatures.h" +/** + * Parse history given in JSON format and return it in binary + * format. + * + * @param exchange connection to the exchange we can use + * @param history JSON array with the history + * @param reserve_pub public key of the reserve to inspect + * @param currency currency we expect the balance to be in + * @param[out] balance final balance + * @param history_length number of entries in @a history + * @param[out] rhistory array of length @a history_length, set to the + * parsed history entries + * @return #GNUNET_OK if history was valid and @a rhistory and @a balance + * were set, + * #GNUNET_SYSERR if there was a protocol violation in @a history + */ +int +TALER_EXCHANGE_parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange, + const json_t *history, + const struct + TALER_ReservePublicKeyP *reserve_pub, + const char *currency, + struct TALER_Amount *balance, + unsigned int history_length, + struct TALER_EXCHANGE_ReserveHistory * + rhistory) +{ + struct GNUNET_HashCode uuid[history_length]; + unsigned int uuid_off; + struct TALER_Amount total_in; + struct TALER_Amount total_out; + size_t off; + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_in)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_out)); + uuid_off = 0; + for (off = 0; offeddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + /* check that withdraw fee matches expectations! */ + { + const struct TALER_EXCHANGE_Keys *key_state; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + struct TALER_Amount fee; + + key_state = TALER_EXCHANGE_get_keys (exchange); + dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, + &withdraw_purpose. + h_denomination_pub); + TALER_amount_ntoh (&fee, + &withdraw_purpose.withdraw_fee); + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&fee, + &dki->fee_withdraw)) || + (0 != + TALER_amount_cmp (&fee, + &dki->fee_withdraw)) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + } + rhistory[off].details.out_authorization_sig + = json_object_get (transaction, + "signature"); + /* Check check that the same withdraw transaction + isn't listed twice by the exchange. We use the + "uuid" array to remember the hashes of all + purposes, and compare the hashes to find + duplicates. */// + GNUNET_CRYPTO_hash (&withdraw_purpose, + ntohl (withdraw_purpose.purpose.size), + &uuid[uuid_off]); + for (unsigned int i = 0; i +*/ +/** + * @file lib/exchange_api_deposits_get.c + * @brief Implementation of the /deposits/ GET request + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include /* just for HTTP status codes */ +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Deposit Wtid Handle + */ +struct TALER_EXCHANGE_DepositGetHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_DepositGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Information the exchange should sign in response. + * (with pre-filled fields from the request). + */ + struct TALER_ConfirmWirePS depconf; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param dwh deposit wtid handle + * @param json json reply with the signature + * @param[out] exchange_pub set to the exchange's public key + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_wtid_signature_ok (const struct + TALER_EXCHANGE_DepositGetHandle *dwh, + const json_t *json, + struct TALER_ExchangePublicKeyP *exchange_pub) +{ + struct TALER_ExchangeSignatureP exchange_sig; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (dwh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE, + &dwh->depconf.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /track/transaction request. + * + * @param cls the `struct TALER_EXCHANGE_DepositGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_deposit_wtid_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_DepositGetHandle *dwh = cls; + const struct TALER_WireTransferIdentifierRawP *wtid = NULL; + struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS; + const struct TALER_Amount *coin_contribution = NULL; + struct TALER_Amount coin_contribution_s; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangePublicKeyP *ep = NULL; + const json_t *j = response; + + dwh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("wtid", &dwh->depconf.wtid), + GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), + TALER_JSON_spec_amount ("coin_contribution", &coin_contribution_s), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + wtid = &dwh->depconf.wtid; + dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time); + TALER_amount_hton (&dwh->depconf.coin_contribution, + &coin_contribution_s); + coin_contribution = &coin_contribution_s; + if (GNUNET_OK != + verify_deposit_wtid_signature_ok (dwh, + j, + &exchange_pub)) + { + GNUNET_break_op (0); + response_code = 0; + } + else + { + ep = &exchange_pub; + } + } + break; + case MHD_HTTP_ACCEPTED: + { + /* Transaction known, but not executed yet */ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dwh->cb (dwh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + ep, + j, + wtid, + execution_time, + coin_contribution); + TALER_EXCHANGE_deposits_get_cancel (dwh); +} + + +/** + * Obtain wire transfer details about an existing deposit operation. + * + * @param exchange the exchange to query + * @param merchant_priv the merchant's private key + * @param h_wire hash of merchant's wire transfer details + * @param h_contract_terms hash of the proposal data from the contract + * between merchant and customer + * @param coin_pub public key of the coin + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle to abort request + */ +struct TALER_EXCHANGE_DepositGetHandle * +TALER_EXCHANGE_deposits_get (struct TALER_EXCHANGE_Handle *exchange, + const struct + TALER_MerchantPrivateKeyP *merchant_priv, + const struct GNUNET_HashCode *h_wire, + const struct + GNUNET_HashCode *h_contract_terms, + const struct + TALER_CoinSpendPublicKeyP *coin_pub, + TALER_EXCHANGE_DepositGetCallback cb, + void *cb_cls) +{ + struct TALER_DepositTrackPS dtp; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_EXCHANGE_DepositGetHandle *dwh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP) + + sizeof (struct GNUNET_HashCode) + + sizeof (struct TALER_MerchantPublicKeyP) + + sizeof (struct GNUNET_HashCode) + + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48]; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION); + dtp.purpose.size = htonl (sizeof (dtp)); + dtp.h_contract_terms = *h_contract_terms; + dtp.h_wire = *h_wire; + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &dtp.merchant.eddsa_pub); + + dtp.coin_pub = *coin_pub; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &dtp.purpose, + &merchant_sig.eddsa_sig)); + { + char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2]; + char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2]; + char chash_str[sizeof (struct GNUNET_HashCode) * 2]; + char whash_str[sizeof (struct GNUNET_HashCode) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (h_wire, + sizeof (struct + GNUNET_HashCode), + whash_str, + sizeof (whash_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (&dtp.merchant, + sizeof (struct + TALER_MerchantPublicKeyP), + mpub_str, + sizeof (mpub_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (h_contract_terms, + sizeof (struct + GNUNET_HashCode), + chash_str, + sizeof (chash_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (coin_pub, + sizeof (struct + TALER_CoinSpendPublicKeyP), + cpub_str, + sizeof (cpub_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (&merchant_sig, + sizeof (struct + TALER_MerchantSignatureP), + msig_str, + sizeof (msig_str)); + *end = '\0'; + + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "/deposits/%s/%s/%s/%s?merchant_sig=%s", + whash_str, + mpub_str, + chash_str, + cpub_str, + msig_str); + } + + dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle); + dwh->exchange = exchange; + dwh->cb = cb; + dwh->cb_cls = cb_cls; + dwh->url = TEAH_path_to_url (exchange, + arg_str); + dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS)); + dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE); + dwh->depconf.h_wire = *h_wire; + dwh->depconf.h_contract_terms = *h_contract_terms; + dwh->depconf.coin_pub = *coin_pub; + + eh = TEL_curl_easy_get (dwh->url); + ctx = TEAH_handle_to_context (exchange); + dwh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_NO, + &handle_deposit_wtid_finished, + dwh); + return dwh; +} + + +/** + * Cancel /deposits/$WTID request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param dwh the wire deposits request handle + */ +void +TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh) +{ + if (NULL != dwh->job) + { + GNUNET_CURL_job_cancel (dwh->job); + dwh->job = NULL; + } + GNUNET_free (dwh->url); + TALER_curl_easy_post_finished (&dwh->ctx); + GNUNET_free (dwh); +} + + +/* end of exchange_api_deposits_get.c */ diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c new file mode 100644 index 000000000..3204ca842 --- /dev/null +++ b/src/lib/exchange_api_link.c @@ -0,0 +1,488 @@ +/* + This file is part of TALER + Copyright (C) 2015-2020 Taler Systems SA + + 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 + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/exchange_api_link.c + * @brief Implementation of the /coins/$COIN_PUB/link request + * @author Christian Grothoff + */ +#include "platform.h" +#include /* just for HTTP status codes */ +#include +#include +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /coins/$COIN_PUB/link Handle + */ +struct TALER_EXCHANGE_LinkHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_LinkCallback link_cb; + + /** + * Closure for @e cb. + */ + void *link_cb_cls; + + /** + * Private key of the coin, required to decode link information. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + +}; + + +/** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param lh link handle + * @param json json reply with the data for one coin + * @param coin_num number of the coin to decode + * @param trans_pub our transfer public key + * @param[out] coin_priv where to return private coin key + * @param[out] sig where to return private coin signature + * @param[out] pub where to return the public key for the coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh, + const json_t *json, + unsigned int coin_num, + const struct TALER_TransferPublicKeyP *trans_pub, + struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_DenominationSignature *sig, + struct TALER_DenominationPublicKey *pub) +{ + struct GNUNET_CRYPTO_RsaSignature *bsig; + struct GNUNET_CRYPTO_RsaPublicKey *rpub; + struct TALER_CoinSpendSignatureP link_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_public_key ("denom_pub", &rpub), + GNUNET_JSON_spec_rsa_signature ("ev_sig", &bsig), + GNUNET_JSON_spec_fixed_auto ("link_sig", &link_sig), + GNUNET_JSON_spec_end () + }; + struct TALER_TransferSecretP secret; + struct TALER_PlanchetSecretsP fc; + + /* parse reply */ + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + TALER_link_recover_transfer_secret (trans_pub, + &lh->coin_priv, + &secret); + TALER_planchet_setup_refresh (&secret, + coin_num, + &fc); + + /* extract coin and signature */ + *coin_priv = fc.coin_priv; + sig->rsa_signature + = GNUNET_CRYPTO_rsa_unblind (bsig, + &fc.blinding_key.bks, + rpub); + /* verify link_sig */ + { + struct TALER_LinkDataPS ldp; + struct TALER_PlanchetDetail pd; + + ldp.purpose.size = htonl (sizeof (ldp)); + ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK); + GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv, + &ldp.old_coin_pub.eddsa_pub); + ldp.transfer_pub = *trans_pub; + pub->rsa_public_key = rpub; + if (GNUNET_OK != + TALER_planchet_prepare (pub, + &fc, + &pd)) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + ldp.h_denom_pub = pd.denom_pub_hash; + GNUNET_CRYPTO_hash (pd.coin_ev, + pd.coin_ev_size, + &ldp.coin_envelope_hash); + GNUNET_free (pd.coin_ev); + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK, + &ldp.purpose, + &link_sig.eddsa_signature, + &ldp.old_coin_pub.eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + } + + /* clean up */ + pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub); + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param[in,out] lh link handle (callback may be zero'ed out) + * @param json json reply with the data for one coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh, + const json_t *json) +{ + unsigned int session; + unsigned int num_coins; + int ret; + + if (! json_is_array (json)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + num_coins = 0; + /* Theoretically, a coin may have been melted repeatedly + into different sessions; so the response is an array + which contains information by melting session. That + array contains another array. However, our API returns + a single 1d array, so we flatten the 2d array that is + returned into a single array. Note that usually a coin + is melted at most once, and so we'll only run this + loop once for 'session=0' in most cases. + + num_coins tracks the size of the 1d array we return, + whilst 'i' and 'session' track the 2d array. */// + for (session = 0; sessionlink_cb (lh->link_cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + num_coins, + coin_privs, + sigs, + pubs, + json); + lh->link_cb = NULL; + ret = GNUNET_OK; + } + else + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + } + + /* clean up */ + GNUNET_assert (off_coin <= num_coins); + for (i = 0; ijob = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_link_ok (lh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != lh->link_cb) + lh->link_cb (lh->link_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + 0, + NULL, + NULL, + NULL, + j); + TALER_EXCHANGE_link_cancel (lh); +} + + +/** + * Submit a link request to the exchange and get the exchange's response. + * + * This API is typically not used by anyone, it is more a threat against those + * trying to receive a funds transfer by abusing the refresh protocol. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param coin_priv private key to request link data for + * @param link_cb the callback to call with the useful result of the + * refresh operation the @a coin_priv was involved in (if any) + * @param link_cb_cls closure for @a link_cb + * @return a handle for this request + */ +struct TALER_EXCHANGE_LinkHandle * +TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + TALER_EXCHANGE_LinkCallback link_cb, + void *link_cb_cls) +{ + struct TALER_EXCHANGE_LinkHandle *lh; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct TALER_CoinSpendPublicKeyP coin_pub; + char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + { + char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (&coin_pub, + sizeof (struct + TALER_CoinSpendPublicKeyP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "/coins/%s/link", + pub_str); + } + lh = GNUNET_new (struct TALER_EXCHANGE_LinkHandle); + lh->exchange = exchange; + lh->link_cb = link_cb; + lh->link_cb_cls = link_cb_cls; + lh->coin_priv = *coin_priv; + lh->url = TEAH_path_to_url (exchange, + arg_str); + eh = TEL_curl_easy_get (lh->url); + ctx = TEAH_handle_to_context (exchange); + lh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_link_finished, + lh); + return lh; +} + + +/** + * Cancel a link request. This function cannot be used + * on a request handle if the callback was already invoked. + * + * @param lh the link handle + */ +void +TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh) +{ + if (NULL != lh->job) + { + GNUNET_CURL_job_cancel (lh->job); + lh->job = NULL; + } + GNUNET_free (lh->url); + GNUNET_free (lh); +} + + +/* end of exchange_api_link.c */ diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c new file mode 100644 index 000000000..9c85fa188 --- /dev/null +++ b/src/lib/exchange_api_melt.c @@ -0,0 +1,505 @@ +/* + This file is part of TALER + Copyright (C) 2015-2020 Taler Systems SA + + 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 + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/exchange_api_melt.c + * @brief Implementation of the /coins/$COIN_PUB/melt request + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include /* just for HTTP status codes */ +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "exchange_api_refresh_common.h" + + +/** + * @brief A /coins/$COIN_PUB/melt Handle + */ +struct TALER_EXCHANGE_MeltHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with refresh melt failure results. + */ + TALER_EXCHANGE_MeltCallback melt_cb; + + /** + * Closure for @e result_cb and @e melt_failure_cb. + */ + void *melt_cb_cls; + + /** + * Actual information about the melt operation. + */ + struct MeltData *md; + + /** + * @brief Public information about the coin's denomination key + */ + struct TALER_EXCHANGE_DenomPublicKey dki; +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param mh melt handle + * @param json json reply with the signature + * @param[out] exchange_pub public key of the exchange used for the signature + * @param[out] noreveal_index set to the noreveal index selected by the exchange + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh, + const json_t *json, + struct TALER_ExchangePublicKeyP *exchange_pub, + uint32_t *noreveal_index) +{ + struct TALER_ExchangeSignatureP exchange_sig; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub), + GNUNET_JSON_spec_uint32 ("noreveal_index", noreveal_index), + GNUNET_JSON_spec_end () + }; + struct TALER_RefreshMeltConfirmationPS confirm; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* check that exchange signing key is permitted */ + key_state = TALER_EXCHANGE_get_keys (mh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* check that noreveal index is in permitted range */ + if (TALER_CNC_KAPPA <= *noreveal_index) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* verify signature by exchange */ + confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); + confirm.purpose.size = htonl (sizeof (struct + TALER_RefreshMeltConfirmationPS)); + confirm.rc = mh->md->rc; + confirm.noreveal_index = htonl (*noreveal_index); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT, + &confirm.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify that the signatures on the "409 CONFLICT" response from the + * exchange demonstrating customer double-spending are valid. + * + * @param mh melt handle + * @param json json reply with the signature(s) and transaction history + * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not + */ +static int +verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh, + const json_t *json) +{ + json_t *history; + struct TALER_Amount original_value; + struct TALER_Amount melt_value_with_fee; + struct TALER_Amount total; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("history", &history), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), + TALER_JSON_spec_amount ("original_value", &original_value), + TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee), + GNUNET_JSON_spec_end () + }; + const struct MeltedCoin *mc; + + /* parse JSON reply */ + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* Find out which coin was deemed problematic by the exchange */ + mc = &mh->md->melted_coin; + + /* check basic coin properties */ + if (0 != TALER_amount_cmp (&original_value, + &mc->original_value)) + { + /* We disagree on the value of the coin */ + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + if (0 != TALER_amount_cmp (&melt_value_with_fee, + &mc->melt_amount_with_fee)) + { + /* We disagree on the value of the coin */ + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + + /* verify coin history */ + history = json_object_get (json, + "history"); + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (&mh->dki, + original_value.currency, + &coin_pub, + history, + &total)) + { + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + json_decref (history); + + /* check if melt operation was really too expensive given history */ + if (GNUNET_OK != + TALER_amount_add (&total, + &total, + &melt_value_with_fee)) + { + /* clearly not OK if our transaction would have caused + the overflow... */ + return GNUNET_OK; + } + + if (0 >= TALER_amount_cmp (&total, + &original_value)) + { + /* transaction should have still fit */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* everything OK, valid proof of double-spending was provided */ + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /coins/$COIN_PUB/melt request. + * + * @param cls the `struct TALER_EXCHANGE_MeltHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_melt_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_MeltHandle *mh = cls; + uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */ + struct TALER_ExchangePublicKeyP exchange_pub; + const json_t *j = response; + + mh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_melt_signature_ok (mh, + j, + &exchange_pub, + &noreveal_index)) + { + GNUNET_break_op (0); + response_code = 0; + } + if (NULL != mh->melt_cb) + { + mh->melt_cb (mh->melt_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + noreveal_index, + (0 == response_code) ? NULL : &exchange_pub, + j); + mh->melt_cb = NULL; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + /* Double spending; check signatures on transaction history */ + if (GNUNET_OK != + verify_melt_signature_conflict (mh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, exchange says one of the signatures is + invalid; assuming we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != mh->melt_cb) + mh->melt_cb (mh->melt_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + UINT32_MAX, + NULL, + j); + TALER_EXCHANGE_melt_cancel (mh); +} + + +/** + * Submit a melt request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, the provided + * argument should have been constructed using + * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage + * prior to calling this function. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param refresh_data_length size of the @a refresh_data (returned + * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) + * @param refresh_data the refresh data as returned from + #TALER_EXCHANGE_refresh_prepare()) + * @param melt_cb the callback to call with the result + * @param melt_cb_cls closure for @a melt_cb + * @return a handle for this request; NULL if the argument was invalid. + * In this case, neither callback will be called. + */ +struct TALER_EXCHANGE_MeltHandle * +TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange, + size_t refresh_data_length, + const char *refresh_data, + TALER_EXCHANGE_MeltCallback melt_cb, + void *melt_cb_cls) +{ + const struct TALER_EXCHANGE_Keys *key_state; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + json_t *melt_obj; + struct TALER_EXCHANGE_MeltHandle *mh; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct MeltData *md; + struct TALER_CoinSpendSignatureP confirm_sig; + struct TALER_RefreshMeltCoinAffirmationPS melt; + struct GNUNET_HashCode h_denom_pub; + char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + md = TALER_EXCHANGE_deserialize_melt_data_ (refresh_data, + refresh_data_length); + if (NULL == md) + { + GNUNET_break (0); + return NULL; + } + melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + melt.purpose.size = htonl (sizeof (struct + TALER_RefreshMeltCoinAffirmationPS)); + melt.rc = md->rc; + TALER_amount_hton (&melt.amount_with_fee, + &md->melted_coin.melt_amount_with_fee); + TALER_amount_hton (&melt.melt_fee, + &md->melted_coin.fee_melt); + GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv, + &melt.coin_pub.eddsa_pub); + GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv, + &melt.purpose, + &confirm_sig.eddsa_signature); + GNUNET_CRYPTO_rsa_public_key_hash (md->melted_coin.pub_key.rsa_public_key, + &h_denom_pub); + melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", + "coin_pub", + GNUNET_JSON_from_data_auto (&melt.coin_pub), + "denom_pub_hash", + GNUNET_JSON_from_data_auto (&h_denom_pub), + "denom_sig", + GNUNET_JSON_from_rsa_signature ( + md->melted_coin.sig.rsa_signature), + "confirm_sig", + GNUNET_JSON_from_data_auto (&confirm_sig), + "value_with_fee", + TALER_JSON_from_amount ( + &md->melted_coin.melt_amount_with_fee), + "rc", + GNUNET_JSON_from_data_auto (&melt.rc)); + if (NULL == melt_obj) + { + GNUNET_break (0); + TALER_EXCHANGE_free_melt_data_ (md); + return NULL; + } + { + char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (&melt.coin_pub, + sizeof (struct + TALER_CoinSpendPublicKeyP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "/coins/%s/melt", + pub_str); + } + + key_state = TALER_EXCHANGE_get_keys (exchange); + dki = TALER_EXCHANGE_get_denomination_key (key_state, + &md->melted_coin.pub_key); + + /* and now we can at last begin the actual request handling */ + mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle); + mh->exchange = exchange; + mh->dki = *dki; + mh->dki.key.rsa_public_key = NULL; /* lifetime not warranted, so better + not copy the pointer */ + mh->melt_cb = melt_cb; + mh->melt_cb_cls = melt_cb_cls; + mh->md = md; + mh->url = TEAH_path_to_url (exchange, + arg_str); + eh = TEL_curl_easy_get (mh->url); + if (GNUNET_OK != + TALER_curl_easy_post (&mh->ctx, + eh, + melt_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (melt_obj); + GNUNET_free (mh->url); + GNUNET_free (mh); + return NULL; + } + json_decref (melt_obj); + ctx = TEAH_handle_to_context (exchange); + mh->job = GNUNET_CURL_job_add2 (ctx, + eh, + mh->ctx.headers, + &handle_melt_finished, + mh); + return mh; +} + + +/** + * Cancel a melt request. This function cannot be used + * on a request handle if either callback was already invoked. + * + * @param mh the refresh melt handle + */ +void +TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh) +{ + if (NULL != mh->job) + { + GNUNET_CURL_job_cancel (mh->job); + mh->job = NULL; + } + TALER_EXCHANGE_free_melt_data_ (mh->md); /* does not free 'md' itself */ + GNUNET_free (mh->md); + GNUNET_free (mh->url); + TALER_curl_easy_post_finished (&mh->ctx); + GNUNET_free (mh); +} + + +/* end of exchange_api_melt.c */ diff --git a/src/lib/exchange_api_refresh.c b/src/lib/exchange_api_refresh.c deleted file mode 100644 index eb26b7c15..000000000 --- a/src/lib/exchange_api_refresh.c +++ /dev/null @@ -1,1770 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2020 Taler Systems SA - - 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 - 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - -*/ -/** - * @file lib/exchange_api_refresh.c - * @brief Implementation of the /refresh/melt+reveal requests of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "platform.h" -#include -#include /* just for HTTP status codes */ -#include -#include -#include -#include "taler_json_lib.h" -#include "taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/* ********************* /refresh/ common ***************************** */ - -/* structures for committing refresh data to disk before doing the - network interaction(s) */ - -GNUNET_NETWORK_STRUCT_BEGIN - -/** - * Header of serialized information about a coin we are melting. - */ -struct MeltedCoinP -{ - /** - * Private key of the coin. - */ - struct TALER_CoinSpendPrivateKeyP coin_priv; - - /** - * Amount this coin contributes to the melt, including fee. - */ - struct TALER_AmountNBO melt_amount_with_fee; - - /** - * The applicable fee for withdrawing a coin of this denomination - */ - struct TALER_AmountNBO fee_melt; - - /** - * The original value of the coin. - */ - struct TALER_AmountNBO original_value; - - /** - * Transfer private keys for each cut-and-choose dimension. - */ - struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; - - /** - * Timestamp indicating when coins of this denomination become invalid. - */ - struct GNUNET_TIME_AbsoluteNBO expire_deposit; - - /** - * Size of the encoded public key that follows. - */ - uint16_t pbuf_size; - - /** - * Size of the encoded signature that follows. - */ - uint16_t sbuf_size; - - /* Followed by serializations of: - 1) struct TALER_DenominationPublicKey pub_key; - 2) struct TALER_DenominationSignature sig; - */ -}; - - -/** - * Header of serialized data about a melt operation, suitable for - * persisting it on disk. - */ -struct MeltDataP -{ - - /** - * Hash over the melting session. - */ - struct TALER_RefreshCommitmentP rc; - - /** - * Number of coins we are melting, in NBO - */ - uint16_t num_melted_coins GNUNET_PACKED; - - /** - * Number of coins we are creating, in NBO - */ - uint16_t num_fresh_coins GNUNET_PACKED; - - /* Followed by serializations of: - 1) struct MeltedCoinP melted_coins[num_melted_coins]; - 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins]; - 3) TALER_CNC_KAPPA times: - 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins]; - */ -}; - - -GNUNET_NETWORK_STRUCT_END - - -/** - * Information about a coin we are melting. - */ -struct MeltedCoin -{ - /** - * Private key of the coin. - */ - struct TALER_CoinSpendPrivateKeyP coin_priv; - - /** - * Amount this coin contributes to the melt, including fee. - */ - struct TALER_Amount melt_amount_with_fee; - - /** - * The applicable fee for melting a coin of this denomination - */ - struct TALER_Amount fee_melt; - - /** - * The original value of the coin. - */ - struct TALER_Amount original_value; - - /** - * Transfer private keys for each cut-and-choose dimension. - */ - struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; - - /** - * Timestamp indicating when coins of this denomination become invalid. - */ - struct GNUNET_TIME_Absolute expire_deposit; - - /** - * Denomination key of the original coin. - */ - struct TALER_DenominationPublicKey pub_key; - - /** - * Exchange's signature over the coin. - */ - struct TALER_DenominationSignature sig; - -}; - - -/** - * Melt data in non-serialized format for convenient processing. - */ -struct MeltData -{ - - /** - * Hash over the committed data during refresh operation. - */ - struct TALER_RefreshCommitmentP rc; - - /** - * Number of coins we are creating - */ - uint16_t num_fresh_coins; - - /** - * Information about the melted coin. - */ - struct MeltedCoin melted_coin; - - /** - * Array of @e num_fresh_coins denomination keys for the coins to be - * freshly exchangeed. - */ - struct TALER_DenominationPublicKey *fresh_pks; - - /** - * Arrays of @e num_fresh_coins with information about the fresh - * coins to be created, for each cut-and-choose dimension. - */ - struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA]; -}; - - -/** - * Free all information associated with a melted coin session. - * - * @param mc melted coin to release, the pointer itself is NOT - * freed (as it is typically not allocated by itself) - */ -static void -free_melted_coin (struct MeltedCoin *mc) -{ - if (NULL != mc->pub_key.rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key); - if (NULL != mc->sig.rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature); -} - - -/** - * Free all information associated with a melting session. Note - * that we allow the melting session to be only partially initialized, - * as we use this function also when freeing melt data that was not - * fully initialized (i.e. due to failures in #deserialize_melt_data()). - * - * @param md melting data to release, the pointer itself is NOT - * freed (as it is typically not allocated by itself) - */ -static void -free_melt_data (struct MeltData *md) -{ - free_melted_coin (&md->melted_coin); - if (NULL != md->fresh_pks) - { - for (unsigned int i = 0; inum_fresh_coins; i++) - if (NULL != md->fresh_pks[i].rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key); - GNUNET_free (md->fresh_pks); - } - - for (unsigned int i = 0; ifresh_coins[i]); - /* Finally, clean up a bit... - (NOTE: compilers might optimize this away, so this is - not providing any strong assurances that the key material - is purged.) */ - memset (md, - 0, - sizeof (struct MeltData)); -} - - -/** - * Serialize information about a coin we are melting. - * - * @param mc information to serialize - * @param buf buffer to write data in, NULL to just compute - * required size - * @param off offeset at @a buf to use - * @return number of bytes written to @a buf at @a off, or if - * @a buf is NULL, number of bytes required; 0 on error - */ -static size_t -serialize_melted_coin (const struct MeltedCoin *mc, - char *buf, - size_t off) -{ - struct MeltedCoinP mcp; - unsigned int i; - char *pbuf; - size_t pbuf_size; - char *sbuf; - size_t sbuf_size; - - sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature, - &sbuf); - pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key, - &pbuf); - if (NULL == buf) - { - GNUNET_free (sbuf); - GNUNET_free (pbuf); - return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; - } - if ( (sbuf_size > UINT16_MAX) || - (pbuf_size > UINT16_MAX) ) - { - GNUNET_break (0); - return 0; - } - mcp.coin_priv = mc->coin_priv; - TALER_amount_hton (&mcp.melt_amount_with_fee, - &mc->melt_amount_with_fee); - TALER_amount_hton (&mcp.fee_melt, - &mc->fee_melt); - TALER_amount_hton (&mcp.original_value, - &mc->original_value); - for (i = 0; itransfer_priv[i]; - mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit); - mcp.pbuf_size = htons ((uint16_t) pbuf_size); - mcp.sbuf_size = htons ((uint16_t) sbuf_size); - memcpy (&buf[off], - &mcp, - sizeof (struct MeltedCoinP)); - memcpy (&buf[off + sizeof (struct MeltedCoinP)], - pbuf, - pbuf_size); - memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size], - sbuf, - sbuf_size); - GNUNET_free (sbuf); - GNUNET_free (pbuf); - return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; -} - - -/** - * Deserialize information about a coin we are melting. - * - * @param[out] mc information to deserialize - * @param buf buffer to read data from - * @param size number of bytes available at @a buf to use - * @param[out] ok set to #GNUNET_NO to report errors - * @return number of bytes read from @a buf, 0 on error - */ -static size_t -deserialize_melted_coin (struct MeltedCoin *mc, - const char *buf, - size_t size, - int *ok) -{ - struct MeltedCoinP mcp; - unsigned int i; - size_t pbuf_size; - size_t sbuf_size; - size_t off; - - if (size < sizeof (struct MeltedCoinP)) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - memcpy (&mcp, - buf, - sizeof (struct MeltedCoinP)); - pbuf_size = ntohs (mcp.pbuf_size); - sbuf_size = ntohs (mcp.sbuf_size); - if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - off = sizeof (struct MeltedCoinP); - mc->pub_key.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off], - pbuf_size); - off += pbuf_size; - mc->sig.rsa_signature - = GNUNET_CRYPTO_rsa_signature_decode (&buf[off], - sbuf_size); - off += sbuf_size; - if ( (NULL == mc->pub_key.rsa_public_key) || - (NULL == mc->sig.rsa_signature) ) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - - mc->coin_priv = mcp.coin_priv; - TALER_amount_ntoh (&mc->melt_amount_with_fee, - &mcp.melt_amount_with_fee); - TALER_amount_ntoh (&mc->fee_melt, - &mcp.fee_melt); - TALER_amount_ntoh (&mc->original_value, - &mcp.original_value); - for (i = 0; itransfer_priv[i] = mcp.transfer_priv[i]; - mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit); - return off; -} - - -/** - * Serialize information about a denomination key. - * - * @param dk information to serialize - * @param buf buffer to write data in, NULL to just compute - * required size - * @param off offeset at @a buf to use - * @return number of bytes written to @a buf at @a off, or if - * @a buf is NULL, number of bytes required - */ -static size_t -serialize_denomination_key (const struct TALER_DenominationPublicKey *dk, - char *buf, - size_t off) -{ - char *pbuf; - size_t pbuf_size; - uint32_t be; - - pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key, - &pbuf); - if (NULL == buf) - { - GNUNET_free (pbuf); - return pbuf_size + sizeof (uint32_t); - } - be = htonl ((uint32_t) pbuf_size); - memcpy (&buf[off], - &be, - sizeof (uint32_t)); - memcpy (&buf[off + sizeof (uint32_t)], - pbuf, - pbuf_size); - GNUNET_free (pbuf); - return pbuf_size + sizeof (uint32_t); -} - - -/** - * Deserialize information about a denomination key. - * - * @param[out] dk information to deserialize - * @param buf buffer to read data from - * @param size number of bytes available at @a buf to use - * @param[out] ok set to #GNUNET_NO to report errors - * @return number of bytes read from @a buf, 0 on error - */ -static size_t -deserialize_denomination_key (struct TALER_DenominationPublicKey *dk, - const char *buf, - size_t size, - int *ok) -{ - size_t pbuf_size; - uint32_t be; - - if (size < sizeof (uint32_t)) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - memcpy (&be, - buf, - sizeof (uint32_t)); - pbuf_size = ntohl (be); - if (size < sizeof (uint32_t) + pbuf_size) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - dk->rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)], - pbuf_size); - if (NULL == dk->rsa_public_key) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - return sizeof (uint32_t) + pbuf_size; -} - - -/** - * Serialize information about a fresh coin we are generating. - * - * @param fc information to serialize - * @param buf buffer to write data in, NULL to just compute - * required size - * @param off offeset at @a buf to use - * @return number of bytes written to @a buf at @a off, or if - * @a buf is NULL, number of bytes required - */ -static size_t -serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc, - char *buf, - size_t off) -{ - if (NULL != buf) - memcpy (&buf[off], - fc, - sizeof (struct TALER_PlanchetSecretsP)); - return sizeof (struct TALER_PlanchetSecretsP); -} - - -/** - * Deserialize information about a fresh coin we are generating. - * - * @param[out] fc information to deserialize - * @param buf buffer to read data from - * @param size number of bytes available at @a buf to use - * @param[out] ok set to #GNUNET_NO to report errors - * @return number of bytes read from @a buf, 0 on error - */ -static size_t -deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc, - const char *buf, - size_t size, - int *ok) -{ - if (size < sizeof (struct TALER_PlanchetSecretsP)) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - memcpy (fc, - buf, - sizeof (struct TALER_PlanchetSecretsP)); - return sizeof (struct TALER_PlanchetSecretsP); -} - - -/** - * Serialize melt data. - * - * @param md data to serialize - * @param[out] res_size size of buffer returned - * @return serialized melt data - */ -static char * -serialize_melt_data (const struct MeltData *md, - size_t *res_size) -{ - size_t size; - size_t asize; - char *buf; - - size = 0; - asize = (size_t) -1; /* make the compiler happy */ - buf = NULL; - /* we do 2 iterations, #1 to determine total size, #2 to - actually construct the buffer */ - do { - if (0 == size) - { - size = sizeof (struct MeltDataP); - } - else - { - struct MeltDataP *mdp; - - buf = GNUNET_malloc (size); - asize = size; /* just for invariant check later */ - size = sizeof (struct MeltDataP); - mdp = (struct MeltDataP *) buf; - mdp->rc = md->rc; - mdp->num_fresh_coins = htons (md->num_fresh_coins); - } - size += serialize_melted_coin (&md->melted_coin, - buf, - size); - for (unsigned int i = 0; inum_fresh_coins; i++) - size += serialize_denomination_key (&md->fresh_pks[i], - buf, - size); - for (unsigned int i = 0; inum_fresh_coins; j++) - size += serialize_fresh_coin (&md->fresh_coins[i][j], - buf, - size); - } while (NULL == buf); - GNUNET_assert (size == asize); - *res_size = size; - return buf; -} - - -/** - * Deserialize melt data. - * - * @param buf serialized data - * @param buf_size size of @a buf - * @return deserialized melt data, NULL on error - */ -static struct MeltData * -deserialize_melt_data (const char *buf, - size_t buf_size) -{ - struct MeltData *md; - struct MeltDataP mdp; - size_t off; - int ok; - - if (buf_size < sizeof (struct MeltDataP)) - return NULL; - memcpy (&mdp, - buf, - sizeof (struct MeltDataP)); - md = GNUNET_new (struct MeltData); - md->rc = mdp.rc; - md->num_fresh_coins = ntohs (mdp.num_fresh_coins); - md->fresh_pks = GNUNET_new_array (md->num_fresh_coins, - struct TALER_DenominationPublicKey); - for (unsigned int i = 0; ifresh_coins[i] = GNUNET_new_array (md->num_fresh_coins, - struct TALER_PlanchetSecretsP); - off = sizeof (struct MeltDataP); - ok = GNUNET_YES; - off += deserialize_melted_coin (&md->melted_coin, - &buf[off], - buf_size - off, - &ok); - for (unsigned int i = 0; (inum_fresh_coins) && (GNUNET_YES == ok); i++) - off += deserialize_denomination_key (&md->fresh_pks[i], - &buf[off], - buf_size - off, - &ok); - - for (unsigned int i = 0; inum_fresh_coins) && (GNUNET_YES == ok); j++) - off += deserialize_fresh_coin (&md->fresh_coins[i][j], - &buf[off], - buf_size - off, - &ok); - if (off != buf_size) - { - GNUNET_break (0); - ok = GNUNET_NO; - } - if (GNUNET_YES != ok) - { - free_melt_data (md); - GNUNET_free (md); - return NULL; - } - return md; -} - - -/** - * Melt (partially spent) coins to obtain fresh coins that are - * unlinkable to the original coin(s). Note that melting more - * than one coin in a single request will make those coins linkable, - * so the safest operation only melts one coin at a time. - * - * This API is typically used by a wallet. Note that to ensure that - * no money is lost in case of hardware failures, this operation does - * not actually initiate the request. Instead, it generates a buffer - * which the caller must store before proceeding with the actual call - * to #TALER_EXCHANGE_refresh_melt() that will generate the request. - * - * This function does verify that the given request data is internally - * consistent. However, the @a melts_sigs are NOT verified. - * - * Aside from some non-trivial cryptographic operations that might - * take a bit of CPU time to complete, this function returns - * its result immediately and does not start any asynchronous - * processing. This function is also thread-safe. - * - * @param melt_priv private key of the coin to melt - * @param melt_amount amount specifying how much - * the coin will contribute to the melt (including fee) - * @param melt_sig signature affirming the - * validity of the public keys corresponding to the - * @a melt_priv private key - * @param melt_pk denomination key information - * record corresponding to the @a melt_sig - * validity of the keys - * @param fresh_pks_len length of the @a pks array - * @param fresh_pks array of @a pks_len denominations of fresh coins to create - * @param[out] res_size set to the size of the return value, or 0 on error - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * Otherwise, pointer to a buffer of @a res_size to store persistently - * before proceeding to #TALER_EXCHANGE_refresh_melt(). - * Non-null results should be freed using GNUNET_free(). - */ -char * -TALER_EXCHANGE_refresh_prepare (const struct - TALER_CoinSpendPrivateKeyP *melt_priv, - const struct TALER_Amount *melt_amount, - const struct - TALER_DenominationSignature *melt_sig, - const struct - TALER_EXCHANGE_DenomPublicKey *melt_pk, - unsigned int fresh_pks_len, - const struct - TALER_EXCHANGE_DenomPublicKey *fresh_pks, - size_t *res_size) -{ - struct MeltData md; - char *buf; - struct TALER_Amount total; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA]; - struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA]; - - GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv, - &coin_pub.eddsa_pub); - /* build up melt data structure */ - memset (&md, 0, sizeof (md)); - md.num_fresh_coins = fresh_pks_len; - md.melted_coin.coin_priv = *melt_priv; - md.melted_coin.melt_amount_with_fee = *melt_amount; - md.melted_coin.fee_melt = melt_pk->fee_refresh; - md.melted_coin.original_value = melt_pk->value; - md.melted_coin.expire_deposit - = melt_pk->expire_deposit; - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (melt_amount->currency, - &total)); - md.melted_coin.pub_key.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key); - md.melted_coin.sig.rsa_signature - = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature); - md.fresh_pks = GNUNET_new_array (fresh_pks_len, - struct TALER_DenominationPublicKey); - for (unsigned int i = 0; idk = &md.fresh_pks[j]; - rcd->coin_ev = pd.coin_ev; - rcd->coin_ev_size = pd.coin_ev_size; - } - } - - /* Compute refresh commitment */ - TALER_refresh_get_commitment (&md.rc, - TALER_CNC_KAPPA, - fresh_pks_len, - rce, - &coin_pub, - melt_amount); - /* finally, serialize everything */ - buf = serialize_melt_data (&md, - res_size); - for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++) - { - for (unsigned int j = 0; j < fresh_pks_len; j++) - GNUNET_free_non_null (rce[i].new_coins[j].coin_ev); - GNUNET_free_non_null (rce[i].new_coins); - } - free_melt_data (&md); - return buf; -} - - -/* ********************* /refresh/melt ***************************** */ - - -/** - * @brief A /refresh/melt Handle - */ -struct TALER_EXCHANGE_RefreshMeltHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with refresh melt failure results. - */ - TALER_EXCHANGE_RefreshMeltCallback melt_cb; - - /** - * Closure for @e result_cb and @e melt_failure_cb. - */ - void *melt_cb_cls; - - /** - * Actual information about the melt operation. - */ - struct MeltData *md; - - /** - * @brief Public information about the coin's denomination key - */ - struct TALER_EXCHANGE_DenomPublicKey dki; -}; - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. - * - * @param rmh melt handle - * @param json json reply with the signature - * @param[out] exchange_pub public key of the exchange used for the signature - * @param[out] noreveal_index set to the noreveal index selected by the exchange - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static int -verify_refresh_melt_signature_ok (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, - const json_t *json, - struct TALER_ExchangePublicKeyP *exchange_pub, - uint32_t *noreveal_index) -{ - struct TALER_ExchangeSignatureP exchange_sig; - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub), - GNUNET_JSON_spec_uint32 ("noreveal_index", noreveal_index), - GNUNET_JSON_spec_end () - }; - struct TALER_RefreshMeltConfirmationPS confirm; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* check that exchange signing key is permitted */ - key_state = TALER_EXCHANGE_get_keys (rmh->exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* check that noreveal index is in permitted range */ - if (TALER_CNC_KAPPA <= *noreveal_index) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* verify signature by exchange */ - confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); - confirm.purpose.size = htonl (sizeof (struct - TALER_RefreshMeltConfirmationPS)); - confirm.rc = rmh->md->rc; - confirm.noreveal_index = htonl (*noreveal_index); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT, - &confirm.purpose, - &exchange_sig.eddsa_signature, - &exchange_pub->eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Verify that the signatures on the "409 CONFLICT" response from the - * exchange demonstrating customer double-spending are valid. - * - * @param rmh melt handle - * @param json json reply with the signature(s) and transaction history - * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not - */ -static int -verify_refresh_melt_signature_conflict (struct - TALER_EXCHANGE_RefreshMeltHandle *rmh, - const json_t *json) -{ - json_t *history; - struct TALER_Amount original_value; - struct TALER_Amount melt_value_with_fee; - struct TALER_Amount total; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("history", &history), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), - TALER_JSON_spec_amount ("original_value", &original_value), - TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee), - GNUNET_JSON_spec_end () - }; - const struct MeltedCoin *mc; - - /* parse JSON reply */ - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* Find out which coin was deemed problematic by the exchange */ - mc = &rmh->md->melted_coin; - - /* check basic coin properties */ - if (0 != TALER_amount_cmp (&original_value, - &mc->original_value)) - { - /* We disagree on the value of the coin */ - GNUNET_break_op (0); - json_decref (history); - return GNUNET_SYSERR; - } - if (0 != TALER_amount_cmp (&melt_value_with_fee, - &mc->melt_amount_with_fee)) - { - /* We disagree on the value of the coin */ - GNUNET_break_op (0); - json_decref (history); - return GNUNET_SYSERR; - } - - /* verify coin history */ - history = json_object_get (json, - "history"); - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (&rmh->dki, - original_value.currency, - &coin_pub, - history, - &total)) - { - GNUNET_break_op (0); - json_decref (history); - return GNUNET_SYSERR; - } - json_decref (history); - - /* check if melt operation was really too expensive given history */ - if (GNUNET_OK != - TALER_amount_add (&total, - &total, - &melt_value_with_fee)) - { - /* clearly not OK if our transaction would have caused - the overflow... */ - return GNUNET_OK; - } - - if (0 >= TALER_amount_cmp (&total, - &original_value)) - { - /* transaction should have still fit */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - - /* everything OK, valid proof of double-spending was provided */ - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /refresh/melt request. - * - * @param cls the `struct TALER_EXCHANGE_RefreshMeltHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_refresh_melt_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RefreshMeltHandle *rmh = cls; - uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */ - struct TALER_ExchangePublicKeyP exchange_pub; - const json_t *j = response; - - rmh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - verify_refresh_melt_signature_ok (rmh, - j, - &exchange_pub, - &noreveal_index)) - { - GNUNET_break_op (0); - response_code = 0; - } - if (NULL != rmh->melt_cb) - { - rmh->melt_cb (rmh->melt_cb_cls, - response_code, - TALER_JSON_get_error_code (j), - noreveal_index, - (0 == response_code) ? NULL : &exchange_pub, - j); - rmh->melt_cb = NULL; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - /* Double spending; check signatures on transaction history */ - if (GNUNET_OK != - verify_refresh_melt_signature_conflict (rmh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, exchange says one of the signatures is - invalid; assuming we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rmh->melt_cb) - rmh->melt_cb (rmh->melt_cb_cls, - response_code, - TALER_JSON_get_error_code (j), - UINT32_MAX, - NULL, - j); - TALER_EXCHANGE_refresh_melt_cancel (rmh); -} - - -/** - * Submit a melt request to the exchange and get the exchange's - * response. - * - * This API is typically used by a wallet. Note that to ensure that - * no money is lost in case of hardware failures, the provided - * argument should have been constructed using - * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage - * prior to calling this function. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param refresh_data_length size of the @a refresh_data (returned - * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) - * @param refresh_data the refresh data as returned from - #TALER_EXCHANGE_refresh_prepare()) - * @param melt_cb the callback to call with the result - * @param melt_cb_cls closure for @a melt_cb - * @return a handle for this request; NULL if the argument was invalid. - * In this case, neither callback will be called. - */ -struct TALER_EXCHANGE_RefreshMeltHandle * -TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange, - size_t refresh_data_length, - const char *refresh_data, - TALER_EXCHANGE_RefreshMeltCallback melt_cb, - void *melt_cb_cls) -{ - const struct TALER_EXCHANGE_Keys *key_state; - const struct TALER_EXCHANGE_DenomPublicKey *dki; - json_t *melt_obj; - struct TALER_EXCHANGE_RefreshMeltHandle *rmh; - CURL *eh; - struct GNUNET_CURL_Context *ctx; - struct MeltData *md; - struct TALER_CoinSpendSignatureP confirm_sig; - struct TALER_RefreshMeltCoinAffirmationPS melt; - struct GNUNET_HashCode h_denom_pub; - char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; - - GNUNET_assert (GNUNET_YES == - TEAH_handle_is_ready (exchange)); - md = deserialize_melt_data (refresh_data, - refresh_data_length); - if (NULL == md) - { - GNUNET_break (0); - return NULL; - } - melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); - melt.purpose.size = htonl (sizeof (struct - TALER_RefreshMeltCoinAffirmationPS)); - melt.rc = md->rc; - TALER_amount_hton (&melt.amount_with_fee, - &md->melted_coin.melt_amount_with_fee); - TALER_amount_hton (&melt.melt_fee, - &md->melted_coin.fee_melt); - GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv, - &melt.coin_pub.eddsa_pub); - GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv, - &melt.purpose, - &confirm_sig.eddsa_signature); - GNUNET_CRYPTO_rsa_public_key_hash (md->melted_coin.pub_key.rsa_public_key, - &h_denom_pub); - melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", - "coin_pub", - GNUNET_JSON_from_data_auto (&melt.coin_pub), - "denom_pub_hash", - GNUNET_JSON_from_data_auto (&h_denom_pub), - "denom_sig", - GNUNET_JSON_from_rsa_signature ( - md->melted_coin.sig.rsa_signature), - "confirm_sig", - GNUNET_JSON_from_data_auto (&confirm_sig), - "value_with_fee", - TALER_JSON_from_amount ( - &md->melted_coin.melt_amount_with_fee), - "rc", - GNUNET_JSON_from_data_auto (&melt.rc)); - if (NULL == melt_obj) - { - GNUNET_break (0); - free_melt_data (md); - return NULL; - } - { - char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (&melt.coin_pub, - sizeof (struct - TALER_CoinSpendPublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "/coins/%s/melt", - pub_str); - } - - key_state = TALER_EXCHANGE_get_keys (exchange); - dki = TALER_EXCHANGE_get_denomination_key (key_state, - &md->melted_coin.pub_key); - - /* and now we can at last begin the actual request handling */ - rmh = GNUNET_new (struct TALER_EXCHANGE_RefreshMeltHandle); - rmh->exchange = exchange; - rmh->dki = *dki; - rmh->dki.key.rsa_public_key = NULL; /* lifetime not warranted, so better - not copy the pointer */ - rmh->melt_cb = melt_cb; - rmh->melt_cb_cls = melt_cb_cls; - rmh->md = md; - rmh->url = TEAH_path_to_url (exchange, - arg_str); - eh = TEL_curl_easy_get (rmh->url); - if (GNUNET_OK != - TALER_curl_easy_post (&rmh->ctx, - eh, - melt_obj)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (melt_obj); - GNUNET_free (rmh->url); - GNUNET_free (rmh); - return NULL; - } - json_decref (melt_obj); - ctx = TEAH_handle_to_context (exchange); - rmh->job = GNUNET_CURL_job_add2 (ctx, - eh, - rmh->ctx.headers, - &handle_refresh_melt_finished, - rmh); - return rmh; -} - - -/** - * Cancel a refresh execute request. This function cannot be used - * on a request handle if either callback was already invoked. - * - * @param rmh the refresh melt handle - */ -void -TALER_EXCHANGE_refresh_melt_cancel (struct - TALER_EXCHANGE_RefreshMeltHandle *rmh) -{ - if (NULL != rmh->job) - { - GNUNET_CURL_job_cancel (rmh->job); - rmh->job = NULL; - } - free_melt_data (rmh->md); /* does not free 'md' itself */ - GNUNET_free (rmh->md); - GNUNET_free (rmh->url); - TALER_curl_easy_post_finished (&rmh->ctx); - GNUNET_free (rmh); -} - - -/* ********************* /refreshes/$RCH/reveal ***************************** */ - - -/** - * @brief A /refreshes/$RCH/reveal Handle - */ -struct TALER_EXCHANGE_RefreshRevealHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_RefreshRevealCallback reveal_cb; - - /** - * Closure for @e reveal_cb. - */ - void *reveal_cb_cls; - - /** - * Actual information about the melt operation. - */ - struct MeltData *md; - - /** - * The index selected by the exchange in cut-and-choose to not be revealed. - */ - uint16_t noreveal_index; - -}; - - -/** - * We got a 200 OK response for the /refreshes/$RCH/reveal operation. - * Extract the coin signatures and return them to the caller. - * The signatures we get from the exchange is for the blinded value. - * Thus, we first must unblind them and then should verify their - * validity. - * - * If everything checks out, we return the unblinded signatures - * to the application via the callback. - * - * @param rrh operation handle - * @param json reply from the exchange - * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static int -refresh_reveal_ok (struct TALER_EXCHANGE_RefreshRevealHandle *rrh, - const json_t *json, - struct TALER_DenominationSignature *sigs) -{ - json_t *jsona; - struct GNUNET_JSON_Specification outer_spec[] = { - GNUNET_JSON_spec_json ("ev_sigs", &jsona), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - outer_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (! json_is_array (jsona)) - { - /* We expected an array of coins */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_SYSERR; - } - if (rrh->md->num_fresh_coins != json_array_size (jsona)) - { - /* Number of coins generated does not match our expectation */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_SYSERR; - } - for (unsigned int i = 0; imd->num_fresh_coins; i++) - { - const struct TALER_PlanchetSecretsP *fc; - struct TALER_DenominationPublicKey *pk; - json_t *jsonai; - struct GNUNET_CRYPTO_RsaSignature *blind_sig; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_HashCode coin_hash; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig), - GNUNET_JSON_spec_end () - }; - struct TALER_FreshCoin coin; - - fc = &rrh->md->fresh_coins[rrh->noreveal_index][i]; - pk = &rrh->md->fresh_pks[i]; - jsonai = json_array_get (jsona, i); - GNUNET_assert (NULL != jsonai); - - if (GNUNET_OK != - GNUNET_JSON_parse (jsonai, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_SYSERR; - } - - /* needed to verify the signature, and we didn't store it earlier, - hence recomputing it here... */ - GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); - GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, - sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), - &coin_hash); - if (GNUNET_OK != - TALER_planchet_to_coin (pk, - blind_sig, - fc, - &coin_hash, - &coin)) - { - GNUNET_break_op (0); - GNUNET_CRYPTO_rsa_signature_free (blind_sig); - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_SYSERR; - } - GNUNET_CRYPTO_rsa_signature_free (blind_sig); - sigs[i] = coin.sig; - } - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /refreshes/$RCH/reveal request. - * - * @param cls the `struct TALER_EXCHANGE_RefreshHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_refresh_reveal_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RefreshRevealHandle *rrh = cls; - const json_t *j = response; - - rrh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - { - struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins]; - int ret; - - memset (sigs, 0, sizeof (sigs)); - ret = refresh_reveal_ok (rrh, - j, - sigs); - if (GNUNET_OK != ret) - { - response_code = 0; - } - else - { - rrh->reveal_cb (rrh->reveal_cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - rrh->md->num_fresh_coins, - rrh->md->fresh_coins[rrh->noreveal_index], - sigs, - j); - rrh->reveal_cb = NULL; - } - for (unsigned int i = 0; imd->num_fresh_coins; i++) - if (NULL != sigs[i].rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - /* Nothing really to verify, exchange says our reveal is inconsitent - with our commitment, so either side is buggy; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rrh->reveal_cb) - rrh->reveal_cb (rrh->reveal_cb_cls, - response_code, - TALER_JSON_get_error_code (j), - 0, - NULL, - NULL, - j); - TALER_EXCHANGE_refresh_reveal_cancel (rrh); -} - - -/** - * Submit a /refresh/reval request to the exchange and get the exchange's - * response. - * - * This API is typically used by a wallet. Note that to ensure that - * no money is lost in case of hardware failures, the provided - * arguments should have been committed to persistent storage - * prior to calling this function. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param refresh_data_length size of the @a refresh_data (returned - * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) - * @param refresh_data the refresh data as returned from - #TALER_EXCHANGE_refresh_prepare()) - * @param noreveal_index response from the exchange to the - * #TALER_EXCHANGE_refresh_melt() invocation - * @param reveal_cb the callback to call with the final result of the - * refresh operation - * @param reveal_cb_cls closure for the above callback - * @return a handle for this request; NULL if the argument was invalid. - * In this case, neither callback will be called. - */ -struct TALER_EXCHANGE_RefreshRevealHandle * -TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange, - size_t refresh_data_length, - const char *refresh_data, - uint32_t noreveal_index, - TALER_EXCHANGE_RefreshRevealCallback reveal_cb, - void *reveal_cb_cls) -{ - struct TALER_EXCHANGE_RefreshRevealHandle *rrh; - json_t *transfer_privs; - json_t *new_denoms_h; - json_t *coin_evs; - json_t *reveal_obj; - json_t *link_sigs; - CURL *eh; - struct GNUNET_CURL_Context *ctx; - struct MeltData *md; - struct TALER_TransferPublicKeyP transfer_pub; - char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32]; - - if (noreveal_index >= TALER_CNC_KAPPA) - { - /* We check this here, as it would be really bad to below just - disclose all the transfer keys. Note that this error should - have been caught way earlier when the exchange replied, but maybe - we had some internal corruption that changed the value... */ - GNUNET_break (0); - return NULL; - } - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - md = deserialize_melt_data (refresh_data, - refresh_data_length); - if (NULL == md) - { - GNUNET_break (0); - return NULL; - } - - /* now transfer_pub */ - GNUNET_CRYPTO_ecdhe_key_get_public ( - &md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv, - &transfer_pub.ecdhe_pub); - - /* now new_denoms */ - GNUNET_assert (NULL != (new_denoms_h = json_array ())); - GNUNET_assert (NULL != (coin_evs = json_array ())); - GNUNET_assert (NULL != (link_sigs = json_array ())); - for (unsigned int i = 0; inum_fresh_coins; i++) - { - struct GNUNET_HashCode denom_hash; - struct TALER_PlanchetDetail pd; - - GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key, - &denom_hash); - GNUNET_assert (0 == - json_array_append_new (new_denoms_h, - GNUNET_JSON_from_data_auto ( - &denom_hash))); - - if (GNUNET_OK != - TALER_planchet_prepare (&md->fresh_pks[i], - &md->fresh_coins[noreveal_index][i], - &pd)) - { - /* This should have been noticed during the preparation stage. */ - GNUNET_break (0); - json_decref (new_denoms_h); - json_decref (coin_evs); - return NULL; - } - GNUNET_assert (0 == - json_array_append_new (coin_evs, - GNUNET_JSON_from_data (pd.coin_ev, - pd.coin_ev_size))); - - /* compute link signature */ - { - struct TALER_CoinSpendSignatureP link_sig; - struct TALER_LinkDataPS ldp; - - ldp.purpose.size = htonl (sizeof (ldp)); - ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK); - ldp.h_denom_pub = denom_hash; - GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv, - &ldp.old_coin_pub.eddsa_pub); - ldp.transfer_pub = transfer_pub; - GNUNET_CRYPTO_hash (pd.coin_ev, - pd.coin_ev_size, - &ldp.coin_envelope_hash); - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign ( - &md->melted_coin.coin_priv.eddsa_priv, - &ldp.purpose, - &link_sig.eddsa_signature)); - GNUNET_assert (0 == - json_array_append_new (link_sigs, - GNUNET_JSON_from_data_auto ( - &link_sig))); - } - - GNUNET_free (pd.coin_ev); - } - - /* build array of transfer private keys */ - GNUNET_assert (NULL != (transfer_privs = json_array ())); - for (unsigned int j = 0; jmelted_coin.transfer_priv[j]))); - } - - /* build main JSON request */ - reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}", - "transfer_pub", - GNUNET_JSON_from_data_auto (&transfer_pub), - "transfer_privs", - transfer_privs, - "link_sigs", - link_sigs, - "new_denoms_h", - new_denoms_h, - "coin_evs", - coin_evs); - if (NULL == reveal_obj) - { - GNUNET_break (0); - return NULL; - } - - { - char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (&md->rc, - sizeof (struct - TALER_RefreshCommitmentP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "/refreshes/%s/reveal", - pub_str); - } - /* finally, we can actually issue the request */ - rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle); - rrh->exchange = exchange; - rrh->noreveal_index = noreveal_index; - rrh->reveal_cb = reveal_cb; - rrh->reveal_cb_cls = reveal_cb_cls; - rrh->md = md; - rrh->url = TEAH_path_to_url (rrh->exchange, - arg_str); - - eh = TEL_curl_easy_get (rrh->url); - if (GNUNET_OK != - TALER_curl_easy_post (&rrh->ctx, - eh, - reveal_obj)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (reveal_obj); - GNUNET_free (rrh->url); - GNUNET_free (rrh); - return NULL; - } - json_decref (reveal_obj); - ctx = TEAH_handle_to_context (rrh->exchange); - rrh->job = GNUNET_CURL_job_add2 (ctx, - eh, - rrh->ctx.headers, - &handle_refresh_reveal_finished, - rrh); - return rrh; -} - - -/** - * Cancel a refresh reveal request. This function cannot be used - * on a request handle if the callback was already invoked. - * - * @param rrh the refresh reval handle - */ -void -TALER_EXCHANGE_refresh_reveal_cancel (struct - TALER_EXCHANGE_RefreshRevealHandle *rrh) -{ - if (NULL != rrh->job) - { - GNUNET_CURL_job_cancel (rrh->job); - rrh->job = NULL; - } - GNUNET_free (rrh->url); - TALER_curl_easy_post_finished (&rrh->ctx); - free_melt_data (rrh->md); /* does not free 'md' itself */ - GNUNET_free (rrh->md); - GNUNET_free (rrh); -} - - -/* end of exchange_api_refresh.c */ diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c new file mode 100644 index 000000000..b2eec2914 --- /dev/null +++ b/src/lib/exchange_api_refresh_common.c @@ -0,0 +1,631 @@ +/* + This file is part of TALER + Copyright (C) 2015-2020 Taler Systems SA + + 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 + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/exchange_api_refresh_common.c + * @brief Serialization logic shared between melt and reveal steps during refreshing + * @author Christian Grothoff + */ +#include "platform.h" +#include "exchange_api_refresh_common.h" + + +/** + * Free all information associated with a melted coin session. + * + * @param mc melted coin to release, the pointer itself is NOT + * freed (as it is typically not allocated by itself) + */ +static void +free_melted_coin (struct MeltedCoin *mc) +{ + if (NULL != mc->pub_key.rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key); + if (NULL != mc->sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature); +} + + +/** + * Free all information associated with a melting session. Note + * that we allow the melting session to be only partially initialized, + * as we use this function also when freeing melt data that was not + * fully initialized (i.e. due to failures in #TALER_EXCHANGE_deserialize_melt_data_()). + * + * @param md melting data to release, the pointer itself is NOT + * freed (as it is typically not allocated by itself) + */ +void +TALER_EXCHANGE_free_melt_data_ (struct MeltData *md) +{ + free_melted_coin (&md->melted_coin); + if (NULL != md->fresh_pks) + { + for (unsigned int i = 0; inum_fresh_coins; i++) + if (NULL != md->fresh_pks[i].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key); + GNUNET_free (md->fresh_pks); + } + + for (unsigned int i = 0; ifresh_coins[i]); + /* Finally, clean up a bit... + (NOTE: compilers might optimize this away, so this is + not providing any strong assurances that the key material + is purged.) */ + memset (md, + 0, + sizeof (struct MeltData)); +} + + +/** + * Serialize information about a coin we are melting. + * + * @param mc information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required; 0 on error + */ +static size_t +serialize_melted_coin (const struct MeltedCoin *mc, + char *buf, + size_t off) +{ + struct MeltedCoinP mcp; + unsigned int i; + char *pbuf; + size_t pbuf_size; + char *sbuf; + size_t sbuf_size; + + sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature, + &sbuf); + pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key, + &pbuf); + if (NULL == buf) + { + GNUNET_free (sbuf); + GNUNET_free (pbuf); + return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; + } + if ( (sbuf_size > UINT16_MAX) || + (pbuf_size > UINT16_MAX) ) + { + GNUNET_break (0); + return 0; + } + mcp.coin_priv = mc->coin_priv; + TALER_amount_hton (&mcp.melt_amount_with_fee, + &mc->melt_amount_with_fee); + TALER_amount_hton (&mcp.fee_melt, + &mc->fee_melt); + TALER_amount_hton (&mcp.original_value, + &mc->original_value); + for (i = 0; itransfer_priv[i]; + mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit); + mcp.pbuf_size = htons ((uint16_t) pbuf_size); + mcp.sbuf_size = htons ((uint16_t) sbuf_size); + memcpy (&buf[off], + &mcp, + sizeof (struct MeltedCoinP)); + memcpy (&buf[off + sizeof (struct MeltedCoinP)], + pbuf, + pbuf_size); + memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size], + sbuf, + sbuf_size); + GNUNET_free (sbuf); + GNUNET_free (pbuf); + return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; +} + + +/** + * Deserialize information about a coin we are melting. + * + * @param[out] mc information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_melted_coin (struct MeltedCoin *mc, + const char *buf, + size_t size, + int *ok) +{ + struct MeltedCoinP mcp; + unsigned int i; + size_t pbuf_size; + size_t sbuf_size; + size_t off; + + if (size < sizeof (struct MeltedCoinP)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (&mcp, + buf, + sizeof (struct MeltedCoinP)); + pbuf_size = ntohs (mcp.pbuf_size); + sbuf_size = ntohs (mcp.sbuf_size); + if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + off = sizeof (struct MeltedCoinP); + mc->pub_key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off], + pbuf_size); + off += pbuf_size; + mc->sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_decode (&buf[off], + sbuf_size); + off += sbuf_size; + if ( (NULL == mc->pub_key.rsa_public_key) || + (NULL == mc->sig.rsa_signature) ) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + + mc->coin_priv = mcp.coin_priv; + TALER_amount_ntoh (&mc->melt_amount_with_fee, + &mcp.melt_amount_with_fee); + TALER_amount_ntoh (&mc->fee_melt, + &mcp.fee_melt); + TALER_amount_ntoh (&mc->original_value, + &mcp.original_value); + for (i = 0; itransfer_priv[i] = mcp.transfer_priv[i]; + mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit); + return off; +} + + +/** + * Serialize information about a denomination key. + * + * @param dk information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required + */ +static size_t +serialize_denomination_key (const struct TALER_DenominationPublicKey *dk, + char *buf, + size_t off) +{ + char *pbuf; + size_t pbuf_size; + uint32_t be; + + pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key, + &pbuf); + if (NULL == buf) + { + GNUNET_free (pbuf); + return pbuf_size + sizeof (uint32_t); + } + be = htonl ((uint32_t) pbuf_size); + memcpy (&buf[off], + &be, + sizeof (uint32_t)); + memcpy (&buf[off + sizeof (uint32_t)], + pbuf, + pbuf_size); + GNUNET_free (pbuf); + return pbuf_size + sizeof (uint32_t); +} + + +/** + * Deserialize information about a denomination key. + * + * @param[out] dk information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_denomination_key (struct TALER_DenominationPublicKey *dk, + const char *buf, + size_t size, + int *ok) +{ + size_t pbuf_size; + uint32_t be; + + if (size < sizeof (uint32_t)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (&be, + buf, + sizeof (uint32_t)); + pbuf_size = ntohl (be); + if (size < sizeof (uint32_t) + pbuf_size) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + dk->rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)], + pbuf_size); + if (NULL == dk->rsa_public_key) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + return sizeof (uint32_t) + pbuf_size; +} + + +/** + * Serialize information about a fresh coin we are generating. + * + * @param fc information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required + */ +static size_t +serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc, + char *buf, + size_t off) +{ + if (NULL != buf) + memcpy (&buf[off], + fc, + sizeof (struct TALER_PlanchetSecretsP)); + return sizeof (struct TALER_PlanchetSecretsP); +} + + +/** + * Deserialize information about a fresh coin we are generating. + * + * @param[out] fc information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc, + const char *buf, + size_t size, + int *ok) +{ + if (size < sizeof (struct TALER_PlanchetSecretsP)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (fc, + buf, + sizeof (struct TALER_PlanchetSecretsP)); + return sizeof (struct TALER_PlanchetSecretsP); +} + + +/** + * Serialize melt data. + * + * @param md data to serialize + * @param[out] res_size size of buffer returned + * @return serialized melt data + */ +static char * +serialize_melt_data (const struct MeltData *md, + size_t *res_size) +{ + size_t size; + size_t asize; + char *buf; + + size = 0; + asize = (size_t) -1; /* make the compiler happy */ + buf = NULL; + /* we do 2 iterations, #1 to determine total size, #2 to + actually construct the buffer */ + do { + if (0 == size) + { + size = sizeof (struct MeltDataP); + } + else + { + struct MeltDataP *mdp; + + buf = GNUNET_malloc (size); + asize = size; /* just for invariant check later */ + size = sizeof (struct MeltDataP); + mdp = (struct MeltDataP *) buf; + mdp->rc = md->rc; + mdp->num_fresh_coins = htons (md->num_fresh_coins); + } + size += serialize_melted_coin (&md->melted_coin, + buf, + size); + for (unsigned int i = 0; inum_fresh_coins; i++) + size += serialize_denomination_key (&md->fresh_pks[i], + buf, + size); + for (unsigned int i = 0; inum_fresh_coins; j++) + size += serialize_fresh_coin (&md->fresh_coins[i][j], + buf, + size); + } while (NULL == buf); + GNUNET_assert (size == asize); + *res_size = size; + return buf; +} + + +/** + * Deserialize melt data. + * + * @param buf serialized data + * @param buf_size size of @a buf + * @return deserialized melt data, NULL on error + */ +struct MeltData * +TALER_EXCHANGE_deserialize_melt_data_ (const char *buf, + size_t buf_size) +{ + struct MeltData *md; + struct MeltDataP mdp; + size_t off; + int ok; + + if (buf_size < sizeof (struct MeltDataP)) + return NULL; + memcpy (&mdp, + buf, + sizeof (struct MeltDataP)); + md = GNUNET_new (struct MeltData); + md->rc = mdp.rc; + md->num_fresh_coins = ntohs (mdp.num_fresh_coins); + md->fresh_pks = GNUNET_new_array (md->num_fresh_coins, + struct TALER_DenominationPublicKey); + for (unsigned int i = 0; ifresh_coins[i] = GNUNET_new_array (md->num_fresh_coins, + struct TALER_PlanchetSecretsP); + off = sizeof (struct MeltDataP); + ok = GNUNET_YES; + off += deserialize_melted_coin (&md->melted_coin, + &buf[off], + buf_size - off, + &ok); + for (unsigned int i = 0; (inum_fresh_coins) && (GNUNET_YES == ok); i++) + off += deserialize_denomination_key (&md->fresh_pks[i], + &buf[off], + buf_size - off, + &ok); + + for (unsigned int i = 0; inum_fresh_coins) && (GNUNET_YES == ok); j++) + off += deserialize_fresh_coin (&md->fresh_coins[i][j], + &buf[off], + buf_size - off, + &ok); + if (off != buf_size) + { + GNUNET_break (0); + ok = GNUNET_NO; + } + if (GNUNET_YES != ok) + { + TALER_EXCHANGE_free_melt_data_ (md); + GNUNET_free (md); + return NULL; + } + return md; +} + + +/** + * Melt (partially spent) coins to obtain fresh coins that are + * unlinkable to the original coin(s). Note that melting more + * than one coin in a single request will make those coins linkable, + * so the safest operation only melts one coin at a time. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, this operation does + * not actually initiate the request. Instead, it generates a buffer + * which the caller must store before proceeding with the actual call + * to #TALER_EXCHANGE_melt() that will generate the request. + * + * This function does verify that the given request data is internally + * consistent. However, the @a melts_sigs are NOT verified. + * + * Aside from some non-trivial cryptographic operations that might + * take a bit of CPU time to complete, this function returns + * its result immediately and does not start any asynchronous + * processing. This function is also thread-safe. + * + * @param melt_priv private key of the coin to melt + * @param melt_amount amount specifying how much + * the coin will contribute to the melt (including fee) + * @param melt_sig signature affirming the + * validity of the public keys corresponding to the + * @a melt_priv private key + * @param melt_pk denomination key information + * record corresponding to the @a melt_sig + * validity of the keys + * @param fresh_pks_len length of the @a pks array + * @param fresh_pks array of @a pks_len denominations of fresh coins to create + * @param[out] res_size set to the size of the return value, or 0 on error + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * Otherwise, pointer to a buffer of @a res_size to store persistently + * before proceeding to #TALER_EXCHANGE_melt(). + * Non-null results should be freed using GNUNET_free(). + */ +char * +TALER_EXCHANGE_refresh_prepare (const struct + TALER_CoinSpendPrivateKeyP *melt_priv, + const struct TALER_Amount *melt_amount, + const struct + TALER_DenominationSignature *melt_sig, + const struct + TALER_EXCHANGE_DenomPublicKey *melt_pk, + unsigned int fresh_pks_len, + const struct + TALER_EXCHANGE_DenomPublicKey *fresh_pks, + size_t *res_size) +{ + struct MeltData md; + char *buf; + struct TALER_Amount total; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA]; + struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA]; + + GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv, + &coin_pub.eddsa_pub); + /* build up melt data structure */ + memset (&md, 0, sizeof (md)); + md.num_fresh_coins = fresh_pks_len; + md.melted_coin.coin_priv = *melt_priv; + md.melted_coin.melt_amount_with_fee = *melt_amount; + md.melted_coin.fee_melt = melt_pk->fee_refresh; + md.melted_coin.original_value = melt_pk->value; + md.melted_coin.expire_deposit + = melt_pk->expire_deposit; + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (melt_amount->currency, + &total)); + md.melted_coin.pub_key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key); + md.melted_coin.sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature); + md.fresh_pks = GNUNET_new_array (fresh_pks_len, + struct TALER_DenominationPublicKey); + for (unsigned int i = 0; idk = &md.fresh_pks[j]; + rcd->coin_ev = pd.coin_ev; + rcd->coin_ev_size = pd.coin_ev_size; + } + } + + /* Compute refresh commitment */ + TALER_refresh_get_commitment (&md.rc, + TALER_CNC_KAPPA, + fresh_pks_len, + rce, + &coin_pub, + melt_amount); + /* finally, serialize everything */ + buf = serialize_melt_data (&md, + res_size); + for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++) + { + for (unsigned int j = 0; j < fresh_pks_len; j++) + GNUNET_free_non_null (rce[i].new_coins[j].coin_ev); + GNUNET_free_non_null (rce[i].new_coins); + } + TALER_EXCHANGE_free_melt_data_ (&md); + return buf; +} diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h new file mode 100644 index 000000000..05988e65e --- /dev/null +++ b/src/lib/exchange_api_refresh_common.h @@ -0,0 +1,240 @@ +/* + This file is part of TALER + Copyright (C) 2015-2020 Taler Systems SA + + 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 + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/exchange_api_refresh.c + * @brief Implementation of the /refresh/melt+reveal requests of the exchange's HTTP API + * @author Christian Grothoff + */ +#ifndef REFRESH_COMMON_H +#define REFRESH_COMMON_H +#include +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "taler_signatures.h" + + +/* structures for committing refresh data to disk before doing the + network interaction(s) */ + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Header of serialized information about a coin we are melting. + */ +struct MeltedCoinP +{ + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Amount this coin contributes to the melt, including fee. + */ + struct TALER_AmountNBO melt_amount_with_fee; + + /** + * The applicable fee for withdrawing a coin of this denomination + */ + struct TALER_AmountNBO fee_melt; + + /** + * The original value of the coin. + */ + struct TALER_AmountNBO original_value; + + /** + * Transfer private keys for each cut-and-choose dimension. + */ + struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_AbsoluteNBO expire_deposit; + + /** + * Size of the encoded public key that follows. + */ + uint16_t pbuf_size; + + /** + * Size of the encoded signature that follows. + */ + uint16_t sbuf_size; + + /* Followed by serializations of: + 1) struct TALER_DenominationPublicKey pub_key; + 2) struct TALER_DenominationSignature sig; + */ +}; + + +/** + * Header of serialized data about a melt operation, suitable for + * persisting it on disk. + */ +struct MeltDataP +{ + + /** + * Hash over the melting session. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Number of coins we are melting, in NBO + */ + uint16_t num_melted_coins GNUNET_PACKED; + + /** + * Number of coins we are creating, in NBO + */ + uint16_t num_fresh_coins GNUNET_PACKED; + + /* Followed by serializations of: + 1) struct MeltedCoinP melted_coins[num_melted_coins]; + 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins]; + 3) TALER_CNC_KAPPA times: + 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins]; + */ +}; + + +GNUNET_NETWORK_STRUCT_END + + +/** + * Information about a coin we are melting. + */ +struct MeltedCoin +{ + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Amount this coin contributes to the melt, including fee. + */ + struct TALER_Amount melt_amount_with_fee; + + /** + * The applicable fee for melting a coin of this denomination + */ + struct TALER_Amount fee_melt; + + /** + * The original value of the coin. + */ + struct TALER_Amount original_value; + + /** + * Transfer private keys for each cut-and-choose dimension. + */ + struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_Absolute expire_deposit; + + /** + * Denomination key of the original coin. + */ + struct TALER_DenominationPublicKey pub_key; + + /** + * Exchange's signature over the coin. + */ + struct TALER_DenominationSignature sig; + +}; + + +/** + * Melt data in non-serialized format for convenient processing. + */ +struct MeltData +{ + + /** + * Hash over the committed data during refresh operation. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Number of coins we are creating + */ + uint16_t num_fresh_coins; + + /** + * Information about the melted coin. + */ + struct MeltedCoin melted_coin; + + /** + * Array of @e num_fresh_coins denomination keys for the coins to be + * freshly exchangeed. + */ + struct TALER_DenominationPublicKey *fresh_pks; + + /** + * Arrays of @e num_fresh_coins with information about the fresh + * coins to be created, for each cut-and-choose dimension. + */ + struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA]; +}; + + +/** + * Serialize melt data. + * + * @param md data to serialize + * @param[out] res_size size of buffer returned + * @return serialized melt data + */ +char * +TALER_EXCHANGE_serialize_melt_data_ (const struct MeltData *md, + size_t *res_size); + + +/** + * Deserialize melt data. + * + * @param buf serialized data + * @param buf_size size of @a buf + * @return deserialized melt data, NULL on error + */ +struct MeltData * +TALER_EXCHANGE_deserialize_melt_data_ (const char *buf, + size_t buf_size); + + +/** + * Free all information associated with a melting session. Note + * that we allow the melting session to be only partially initialized, + * as we use this function also when freeing melt data that was not + * fully initialized (i.e. due to failures in #deserialize_melt_data()). + * + * @param md melting data to release, the pointer itself is NOT + * freed (as it is typically not allocated by itself) + */ +void +TALER_EXCHANGE_free_melt_data_ (struct MeltData *md); + +#endif diff --git a/src/lib/exchange_api_refresh_link.c b/src/lib/exchange_api_refresh_link.c deleted file mode 100644 index 4b4b38ba1..000000000 --- a/src/lib/exchange_api_refresh_link.c +++ /dev/null @@ -1,490 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2020 Taler Systems SA - - 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 - 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - -*/ -/** - * @file lib/exchange_api_refresh_link.c - * @brief Implementation of the /refresh/link request of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "platform.h" -#include /* just for HTTP status codes */ -#include -#include -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /refresh/link Handle - */ -struct TALER_EXCHANGE_RefreshLinkHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_RefreshLinkCallback link_cb; - - /** - * Closure for @e cb. - */ - void *link_cb_cls; - - /** - * Private key of the coin, required to decode link information. - */ - struct TALER_CoinSpendPrivateKeyP coin_priv; - -}; - - -/** - * Parse the provided linkage data from the "200 OK" response - * for one of the coins. - * - * @param rlh refresh link handle - * @param json json reply with the data for one coin - * @param coin_num number of the coin to decode - * @param trans_pub our transfer public key - * @param[out] coin_priv where to return private coin key - * @param[out] sig where to return private coin signature - * @param[out] pub where to return the public key for the coin - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static int -parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh, - const json_t *json, - unsigned int coin_num, - const struct TALER_TransferPublicKeyP *trans_pub, - struct TALER_CoinSpendPrivateKeyP *coin_priv, - struct TALER_DenominationSignature *sig, - struct TALER_DenominationPublicKey *pub) -{ - struct GNUNET_CRYPTO_RsaSignature *bsig; - struct GNUNET_CRYPTO_RsaPublicKey *rpub; - struct TALER_CoinSpendSignatureP link_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_rsa_public_key ("denom_pub", &rpub), - GNUNET_JSON_spec_rsa_signature ("ev_sig", &bsig), - GNUNET_JSON_spec_fixed_auto ("link_sig", &link_sig), - GNUNET_JSON_spec_end () - }; - struct TALER_TransferSecretP secret; - struct TALER_PlanchetSecretsP fc; - - /* parse reply */ - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - TALER_link_recover_transfer_secret (trans_pub, - &rlh->coin_priv, - &secret); - TALER_planchet_setup_refresh (&secret, - coin_num, - &fc); - - /* extract coin and signature */ - *coin_priv = fc.coin_priv; - sig->rsa_signature - = GNUNET_CRYPTO_rsa_unblind (bsig, - &fc.blinding_key.bks, - rpub); - /* verify link_sig */ - { - struct TALER_LinkDataPS ldp; - struct TALER_PlanchetDetail pd; - - ldp.purpose.size = htonl (sizeof (ldp)); - ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK); - GNUNET_CRYPTO_eddsa_key_get_public (&rlh->coin_priv.eddsa_priv, - &ldp.old_coin_pub.eddsa_pub); - ldp.transfer_pub = *trans_pub; - pub->rsa_public_key = rpub; - if (GNUNET_OK != - TALER_planchet_prepare (pub, - &fc, - &pd)) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - ldp.h_denom_pub = pd.denom_pub_hash; - GNUNET_CRYPTO_hash (pd.coin_ev, - pd.coin_ev_size, - &ldp.coin_envelope_hash); - GNUNET_free (pd.coin_ev); - - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK, - &ldp.purpose, - &link_sig.eddsa_signature, - &ldp.old_coin_pub.eddsa_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - } - - /* clean up */ - pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub); - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Parse the provided linkage data from the "200 OK" response - * for one of the coins. - * - * @param[in,out] rlh refresh link handle (callback may be zero'ed out) - * @param json json reply with the data for one coin - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static int -parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh, - const json_t *json) -{ - unsigned int session; - unsigned int num_coins; - int ret; - - if (! json_is_array (json)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - num_coins = 0; - /* Theoretically, a coin may have been melted repeatedly - into different sessions; so the response is an array - which contains information by melting session. That - array contains another array. However, our API returns - a single 1d array, so we flatten the 2d array that is - returned into a single array. Note that usually a coin - is melted at most once, and so we'll only run this - loop once for 'session=0' in most cases. - - num_coins tracks the size of the 1d array we return, - whilst 'i' and 'session' track the 2d array. */// - for (session = 0; sessionlink_cb (rlh->link_cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - num_coins, - coin_privs, - sigs, - pubs, - json); - rlh->link_cb = NULL; - ret = GNUNET_OK; - } - else - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - } - - /* clean up */ - GNUNET_assert (off_coin <= num_coins); - for (i = 0; ijob = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - parse_refresh_link_ok (rlh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rlh->link_cb) - rlh->link_cb (rlh->link_cb_cls, - response_code, - TALER_JSON_get_error_code (j), - 0, - NULL, - NULL, - NULL, - j); - TALER_EXCHANGE_refresh_link_cancel (rlh); -} - - -/** - * Submit a link request to the exchange and get the exchange's response. - * - * This API is typically not used by anyone, it is more a threat - * against those trying to receive a funds transfer by abusing the - * /refresh protocol. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param coin_priv private key to request link data for - * @param link_cb the callback to call with the useful result of the - * refresh operation the @a coin_priv was involved in (if any) - * @param link_cb_cls closure for @a link_cb - * @return a handle for this request - */ -struct TALER_EXCHANGE_RefreshLinkHandle * -TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_CoinSpendPrivateKeyP *coin_priv, - TALER_EXCHANGE_RefreshLinkCallback link_cb, - void *link_cb_cls) -{ - struct TALER_EXCHANGE_RefreshLinkHandle *rlh; - CURL *eh; - struct GNUNET_CURL_Context *ctx; - struct TALER_CoinSpendPublicKeyP coin_pub; - char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - - GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, - &coin_pub.eddsa_pub); - { - char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (&coin_pub, - sizeof (struct - TALER_CoinSpendPublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "/coins/%s/link", - pub_str); - } - rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle); - rlh->exchange = exchange; - rlh->link_cb = link_cb; - rlh->link_cb_cls = link_cb_cls; - rlh->coin_priv = *coin_priv; - rlh->url = TEAH_path_to_url (exchange, - arg_str); - eh = TEL_curl_easy_get (rlh->url); - ctx = TEAH_handle_to_context (exchange); - rlh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_refresh_link_finished, - rlh); - return rlh; -} - - -/** - * Cancel a refresh link request. This function cannot be used - * on a request handle if the callback was already invoked. - * - * @param rlh the refresh link handle - */ -void -TALER_EXCHANGE_refresh_link_cancel (struct - TALER_EXCHANGE_RefreshLinkHandle *rlh) -{ - if (NULL != rlh->job) - { - GNUNET_CURL_job_cancel (rlh->job); - rlh->job = NULL; - } - GNUNET_free (rlh->url); - GNUNET_free (rlh); -} - - -/* end of exchange_api_refresh_link.c */ diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c new file mode 100644 index 000000000..96aafbda8 --- /dev/null +++ b/src/lib/exchange_api_refreshes_reveal.c @@ -0,0 +1,512 @@ +/* + This file is part of TALER + Copyright (C) 2015-2020 Taler Systems SA + + 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 + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/exchange_api_refreshes_reveal.c + * @brief Implementation of the /refreshes/$RCH/reveal requests + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include /* just for HTTP status codes */ +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "exchange_api_refresh_common.h" + + +/** + * @brief A /refreshes/$RCH/reveal Handle + */ +struct TALER_EXCHANGE_RefreshesRevealHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefreshesRevealCallback reveal_cb; + + /** + * Closure for @e reveal_cb. + */ + void *reveal_cb_cls; + + /** + * Actual information about the melt operation. + */ + struct MeltData *md; + + /** + * The index selected by the exchange in cut-and-choose to not be revealed. + */ + uint16_t noreveal_index; + +}; + + +/** + * We got a 200 OK response for the /refreshes/$RCH/reveal operation. + * Extract the coin signatures and return them to the caller. + * The signatures we get from the exchange is for the blinded value. + * Thus, we first must unblind them and then should verify their + * validity. + * + * If everything checks out, we return the unblinded signatures + * to the application via the callback. + * + * @param rrh operation handle + * @param json reply from the exchange + * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh, + const json_t *json, + struct TALER_DenominationSignature *sigs) +{ + json_t *jsona; + struct GNUNET_JSON_Specification outer_spec[] = { + GNUNET_JSON_spec_json ("ev_sigs", &jsona), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + outer_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (jsona)) + { + /* We expected an array of coins */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + if (rrh->md->num_fresh_coins != json_array_size (jsona)) + { + /* Number of coins generated does not match our expectation */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; imd->num_fresh_coins; i++) + { + const struct TALER_PlanchetSecretsP *fc; + struct TALER_DenominationPublicKey *pk; + json_t *jsonai; + struct GNUNET_CRYPTO_RsaSignature *blind_sig; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode coin_hash; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig), + GNUNET_JSON_spec_end () + }; + struct TALER_FreshCoin coin; + + fc = &rrh->md->fresh_coins[rrh->noreveal_index][i]; + pk = &rrh->md->fresh_pks[i]; + jsonai = json_array_get (jsona, i); + GNUNET_assert (NULL != jsonai); + + if (GNUNET_OK != + GNUNET_JSON_parse (jsonai, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + + /* needed to verify the signature, and we didn't store it earlier, + hence recomputing it here... */ + GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + &coin_hash); + if (GNUNET_OK != + TALER_planchet_to_coin (pk, + blind_sig, + fc, + &coin_hash, + &coin)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_rsa_signature_free (blind_sig); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_rsa_signature_free (blind_sig); + sigs[i] = coin.sig; + } + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refreshes/$RCH/reveal request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refresh_reveal_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefreshesRevealHandle *rrh = cls; + const json_t *j = response; + + rrh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins]; + int ret; + + memset (sigs, 0, sizeof (sigs)); + ret = refresh_reveal_ok (rrh, + j, + sigs); + if (GNUNET_OK != ret) + { + response_code = 0; + } + else + { + rrh->reveal_cb (rrh->reveal_cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + rrh->md->num_fresh_coins, + rrh->md->fresh_coins[rrh->noreveal_index], + sigs, + j); + rrh->reveal_cb = NULL; + } + for (unsigned int i = 0; imd->num_fresh_coins; i++) + if (NULL != sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + /* Nothing really to verify, exchange says our reveal is inconsitent + with our commitment, so either side is buggy; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rrh->reveal_cb) + rrh->reveal_cb (rrh->reveal_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + 0, + NULL, + NULL, + j); + TALER_EXCHANGE_refreshes_reveal_cancel (rrh); +} + + +/** + * Submit a /refresh/reval request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, the provided + * arguments should have been committed to persistent storage + * prior to calling this function. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param refresh_data_length size of the @a refresh_data (returned + * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) + * @param refresh_data the refresh data as returned from + #TALER_EXCHANGE_refresh_prepare()) + * @param noreveal_index response from the exchange to the + * #TALER_EXCHANGE_melt() invocation + * @param reveal_cb the callback to call with the final result of the + * refresh operation + * @param reveal_cb_cls closure for the above callback + * @return a handle for this request; NULL if the argument was invalid. + * In this case, neither callback will be called. + */ +struct TALER_EXCHANGE_RefreshesRevealHandle * +TALER_EXCHANGE_refreshes_reveal (struct TALER_EXCHANGE_Handle *exchange, + size_t refresh_data_length, + const char *refresh_data, + uint32_t noreveal_index, + TALER_EXCHANGE_RefreshesRevealCallback + reveal_cb, + void *reveal_cb_cls) +{ + struct TALER_EXCHANGE_RefreshesRevealHandle *rrh; + json_t *transfer_privs; + json_t *new_denoms_h; + json_t *coin_evs; + json_t *reveal_obj; + json_t *link_sigs; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct MeltData *md; + struct TALER_TransferPublicKeyP transfer_pub; + char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32]; + + if (noreveal_index >= TALER_CNC_KAPPA) + { + /* We check this here, as it would be really bad to below just + disclose all the transfer keys. Note that this error should + have been caught way earlier when the exchange replied, but maybe + we had some internal corruption that changed the value... */ + GNUNET_break (0); + return NULL; + } + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + md = TALER_EXCHANGE_deserialize_melt_data_ (refresh_data, + refresh_data_length); + if (NULL == md) + { + GNUNET_break (0); + return NULL; + } + + /* now transfer_pub */ + GNUNET_CRYPTO_ecdhe_key_get_public ( + &md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv, + &transfer_pub.ecdhe_pub); + + /* now new_denoms */ + GNUNET_assert (NULL != (new_denoms_h = json_array ())); + GNUNET_assert (NULL != (coin_evs = json_array ())); + GNUNET_assert (NULL != (link_sigs = json_array ())); + for (unsigned int i = 0; inum_fresh_coins; i++) + { + struct GNUNET_HashCode denom_hash; + struct TALER_PlanchetDetail pd; + + GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key, + &denom_hash); + GNUNET_assert (0 == + json_array_append_new (new_denoms_h, + GNUNET_JSON_from_data_auto ( + &denom_hash))); + + if (GNUNET_OK != + TALER_planchet_prepare (&md->fresh_pks[i], + &md->fresh_coins[noreveal_index][i], + &pd)) + { + /* This should have been noticed during the preparation stage. */ + GNUNET_break (0); + json_decref (new_denoms_h); + json_decref (coin_evs); + return NULL; + } + GNUNET_assert (0 == + json_array_append_new (coin_evs, + GNUNET_JSON_from_data (pd.coin_ev, + pd.coin_ev_size))); + + /* compute link signature */ + { + struct TALER_CoinSpendSignatureP link_sig; + struct TALER_LinkDataPS ldp; + + ldp.purpose.size = htonl (sizeof (ldp)); + ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK); + ldp.h_denom_pub = denom_hash; + GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv, + &ldp.old_coin_pub.eddsa_pub); + ldp.transfer_pub = transfer_pub; + GNUNET_CRYPTO_hash (pd.coin_ev, + pd.coin_ev_size, + &ldp.coin_envelope_hash); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign ( + &md->melted_coin.coin_priv.eddsa_priv, + &ldp.purpose, + &link_sig.eddsa_signature)); + GNUNET_assert (0 == + json_array_append_new (link_sigs, + GNUNET_JSON_from_data_auto ( + &link_sig))); + } + + GNUNET_free (pd.coin_ev); + } + + /* build array of transfer private keys */ + GNUNET_assert (NULL != (transfer_privs = json_array ())); + for (unsigned int j = 0; jmelted_coin.transfer_priv[j]))); + } + + /* build main JSON request */ + reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}", + "transfer_pub", + GNUNET_JSON_from_data_auto (&transfer_pub), + "transfer_privs", + transfer_privs, + "link_sigs", + link_sigs, + "new_denoms_h", + new_denoms_h, + "coin_evs", + coin_evs); + if (NULL == reveal_obj) + { + GNUNET_break (0); + return NULL; + } + + { + char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (&md->rc, + sizeof (struct + TALER_RefreshCommitmentP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "/refreshes/%s/reveal", + pub_str); + } + /* finally, we can actually issue the request */ + rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshesRevealHandle); + rrh->exchange = exchange; + rrh->noreveal_index = noreveal_index; + rrh->reveal_cb = reveal_cb; + rrh->reveal_cb_cls = reveal_cb_cls; + rrh->md = md; + rrh->url = TEAH_path_to_url (rrh->exchange, + arg_str); + + eh = TEL_curl_easy_get (rrh->url); + if (GNUNET_OK != + TALER_curl_easy_post (&rrh->ctx, + eh, + reveal_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (reveal_obj); + GNUNET_free (rrh->url); + GNUNET_free (rrh); + return NULL; + } + json_decref (reveal_obj); + ctx = TEAH_handle_to_context (rrh->exchange); + rrh->job = GNUNET_CURL_job_add2 (ctx, + eh, + rrh->ctx.headers, + &handle_refresh_reveal_finished, + rrh); + return rrh; +} + + +/** + * Cancel a refresh reveal request. This function cannot be used + * on a request handle if the callback was already invoked. + * + * @param rrh the refresh reval handle + */ +void +TALER_EXCHANGE_refreshes_reveal_cancel (struct + TALER_EXCHANGE_RefreshesRevealHandle * + rrh) +{ + if (NULL != rrh->job) + { + GNUNET_CURL_job_cancel (rrh->job); + rrh->job = NULL; + } + GNUNET_free (rrh->url); + TALER_curl_easy_post_finished (&rrh->ctx); + TALER_EXCHANGE_free_melt_data_ (rrh->md); /* does not free 'md' itself */ + GNUNET_free (rrh->md); + GNUNET_free (rrh); +} + + +/* exchange_api_refreshes_reveal.c */ diff --git a/src/lib/exchange_api_reserve.c b/src/lib/exchange_api_reserve.c deleted file mode 100644 index 95a4a90eb..000000000 --- a/src/lib/exchange_api_reserve.c +++ /dev/null @@ -1,1306 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA - - 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 - 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - -*/ -/** - * @file lib/exchange_api_reserve.c - * @brief Implementation of the /reserve requests of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "platform.h" -#include -#include /* just for HTTP status codes */ -#include -#include -#include -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/* ********************** /reserve/status ********************** */ - -/** - * @brief A Withdraw Status Handle - */ -struct TALER_EXCHANGE_ReservesGetHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReservesGetCallback cb; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * Parse history given in JSON format and return it in binary - * format. - * - * @param exchange connection to the exchange we can use - * @param history JSON array with the history - * @param reserve_pub public key of the reserve to inspect - * @param currency currency we expect the balance to be in - * @param[out] balance final balance - * @param history_length number of entries in @a history - * @param[out] rhistory array of length @a history_length, set to the - * parsed history entries - * @return #GNUNET_OK if history was valid and @a rhistory and @a balance - * were set, - * #GNUNET_SYSERR if there was a protocol violation in @a history - */ -static int -parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange, - const json_t *history, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *currency, - struct TALER_Amount *balance, - unsigned int history_length, - struct TALER_EXCHANGE_ReserveHistory *rhistory) -{ - struct GNUNET_HashCode uuid[history_length]; - unsigned int uuid_off; - struct TALER_Amount total_in; - struct TALER_Amount total_out; - size_t off; - - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_in)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_out)); - uuid_off = 0; - for (off = 0; offeddsa_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - /* check that withdraw fee matches expectations! */ - { - const struct TALER_EXCHANGE_Keys *key_state; - const struct TALER_EXCHANGE_DenomPublicKey *dki; - struct TALER_Amount fee; - - key_state = TALER_EXCHANGE_get_keys (exchange); - dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, - &withdraw_purpose. - h_denomination_pub); - TALER_amount_ntoh (&fee, - &withdraw_purpose.withdraw_fee); - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&fee, - &dki->fee_withdraw)) || - (0 != - TALER_amount_cmp (&fee, - &dki->fee_withdraw)) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - } - rhistory[off].details.out_authorization_sig - = json_object_get (transaction, - "signature"); - /* Check check that the same withdraw transaction - isn't listed twice by the exchange. We use the - "uuid" array to remember the hashes of all - purposes, and compare the hashes to find - duplicates. */// - GNUNET_CRYPTO_hash (&withdraw_purpose, - ntohl (withdraw_purpose.purpose.size), - &uuid[uuid_off]); - for (unsigned int i = 0; iexchange, - history, - &rsh->reserve_pub, - balance.currency, - &balance_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - free_rhistory (rhistory, - len); - return GNUNET_SYSERR; - } - if (0 != - TALER_amount_cmp (&balance_from_history, - &balance)) - { - /* exchange cannot add up balances!? */ - GNUNET_break_op (0); - free_rhistory (rhistory, - len); - return GNUNET_SYSERR; - } - if (NULL != rsh->cb) - { - rsh->cb (rsh->cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - j, - &balance, - len, - rhistory); - rsh->cb = NULL; - } - free_rhistory (rhistory, - len); - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserve/status request. - * - * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserve_status_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReservesGetHandle *rsh = cls; - const json_t *j = response; - - rsh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserve_status_ok (rsh, - j)) - response_code = 0; - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rsh->cb) - { - rsh->cb (rsh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - j, - NULL, - 0, NULL); - rsh->cb = NULL; - } - TALER_EXCHANGE_reserves_get_cancel (rsh); -} - - -/** - * Submit a request to obtain the transaction history of a reserve - * from the exchange. Note that while we return the full response to the - * caller for further processing, we do already verify that the - * response is well-formed (i.e. that signatures included in the - * response are all valid and add up to the balance). If the exchange's - * reply is not well-formed, we return an HTTP status code of zero to - * @a cb. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param reserve_pub public key of the reserve to inspect - * @param cb the callback to call when a reply for this request is available - * @param cb_cls closure for the above callback - * @return a handle for this request; NULL if the inputs are invalid (i.e. - * signatures fail to verify). In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReservesGetHandle * -TALER_EXCHANGE_reserves_get (struct TALER_EXCHANGE_Handle *exchange, - const struct - TALER_ReservePublicKeyP *reserve_pub, - TALER_EXCHANGE_ReservesGetCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReservesGetHandle *rsh; - struct GNUNET_CURL_Context *ctx; - CURL *eh; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16]; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (reserve_pub, - sizeof (struct - TALER_ReservePublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "/reserves/%s", - pub_str); - } - rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle); - rsh->exchange = exchange; - rsh->cb = cb; - rsh->cb_cls = cb_cls; - rsh->reserve_pub = *reserve_pub; - rsh->url = TEAH_path_to_url (exchange, - arg_str); - eh = TEL_curl_easy_get (rsh->url); - ctx = TEAH_handle_to_context (exchange); - rsh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_NO, - &handle_reserve_status_finished, - rsh); - return rsh; -} - - -/** - * Cancel a reserve status request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param rsh the reserve status request handle - */ -void -TALER_EXCHANGE_reserves_get_cancel (struct - TALER_EXCHANGE_ReservesGetHandle *rsh) -{ - if (NULL != rsh->job) - { - GNUNET_CURL_job_cancel (rsh->job); - rsh->job = NULL; - } - GNUNET_free (rsh->url); - GNUNET_free (rsh); -} - - -/* ********************** /reserve/withdraw ********************** */ - -/** - * @brief A Withdraw Sign Handle - */ -struct TALER_EXCHANGE_WithdrawHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_WithdrawCallback cb; - - /** - * Secrets of the planchet. - */ - struct TALER_PlanchetSecretsP ps; - - /** - * Denomination key we are withdrawing. - */ - struct TALER_EXCHANGE_DenomPublicKey pk; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Hash of the public key of the coin we are signing. - */ - struct GNUNET_HashCode c_hash; - - /** - * Public key of the reserve we are withdrawing from. - */ - struct TALER_ReservePublicKeyP reserve_pub; - -}; - - -/** - * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation. - * Extract the coin's signature and return it to the caller. The signature we - * get from the exchange is for the blinded value. Thus, we first must - * unblind it and then should verify its validity against our coin's hash. - * - * If everything checks out, we return the unblinded signature - * to the application via the callback. - * - * @param wsh operation handle - * @param json reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static int -reserve_withdraw_ok (struct TALER_EXCHANGE_WithdrawHandle *wsh, - const json_t *json) -{ - struct GNUNET_CRYPTO_RsaSignature *blind_sig; - struct TALER_FreshCoin fc; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_rsa_signature ("ev_sig", - &blind_sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_planchet_to_coin (&wsh->pk.key, - blind_sig, - &wsh->ps, - &wsh->c_hash, - &fc)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_JSON_parse_free (spec); - - /* signature is valid, return it to the application */ - wsh->cb (wsh->cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - &fc.sig, - json); - /* make sure callback isn't called again after return */ - wsh->cb = NULL; - GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature); - return GNUNET_OK; -} - - -/** - * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw operation. - * Check the signatures on the withdraw transactions in the provided - * history and that the balances add up. We don't do anything directly - * with the information, as the JSON will be returned to the application. - * However, our job is ensuring that the exchange followed the protocol, and - * this in particular means checking all of the signatures in the history. - * - * @param wsh operation handle - * @param json reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static int -reserve_withdraw_payment_required (struct - TALER_EXCHANGE_WithdrawHandle *wsh, - const json_t *json) -{ - struct TALER_Amount balance; - struct TALER_Amount balance_from_history; - struct TALER_Amount requested_amount; - json_t *history; - size_t len; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("balance", &balance), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - history = json_object_get (json, - "history"); - if (NULL == history) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* go over transaction history and compute - total incoming and outgoing amounts */ - len = json_array_size (history); - { - struct TALER_EXCHANGE_ReserveHistory *rhistory; - - /* Use heap allocation as "len" may be very big and thus this may - not fit on the stack. Use "GNUNET_malloc_large" as a malicious - exchange may theoretically try to crash us by giving a history - that does not fit into our memory. */ - rhistory = GNUNET_malloc_large (sizeof (struct - TALER_EXCHANGE_ReserveHistory) - * len); - if (NULL == rhistory) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - parse_reserve_history (wsh->exchange, - history, - &wsh->reserve_pub, - balance.currency, - &balance_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - free_rhistory (rhistory, - len); - return GNUNET_SYSERR; - } - free_rhistory (rhistory, - len); - } - - if (0 != - TALER_amount_cmp (&balance_from_history, - &balance)) - { - /* exchange cannot add up balances!? */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* Compute how much we expected to charge to the reserve */ - if (GNUNET_OK != - TALER_amount_add (&requested_amount, - &wsh->pk.value, - &wsh->pk.fee_withdraw)) - { - /* Overflow here? Very strange, our CPU must be fried... */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* Check that funds were really insufficient */ - if (0 >= TALER_amount_cmp (&requested_amount, - &balance)) - { - /* Requested amount is smaller or equal to reported balance, - so this should not have failed. */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserves/$RESERVE_PUB/withdraw request. - * - * @param cls the `struct TALER_EXCHANGE_WithdrawHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserve_withdraw_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_WithdrawHandle *wsh = cls; - const json_t *j = response; - - wsh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - reserve_withdraw_ok (wsh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - /* The exchange says that the reserve has insufficient funds; - check the signatures in the history... */ - if (GNUNET_OK != - reserve_withdraw_payment_required (wsh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_FORBIDDEN: - GNUNET_break (0); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, the exchange basically just says - that it doesn't know this reserve. Can happen if we - query before the wire transfer went through. - We should simply pass the JSON reply to the application. */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != wsh->cb) - { - wsh->cb (wsh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - NULL, - j); - wsh->cb = NULL; - } - TALER_EXCHANGE_withdraw_cancel (wsh); -} - - -/** - * Helper function for #TALER_EXCHANGE_withdraw2() and - * #TALER_EXCHANGE_withdraw(). - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_sig signature from the reserve authorizing the withdrawal - * @param reserve_pub public key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param pd planchet details matching @a ps - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for @a res_cb - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_WithdrawHandle * -reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_ReserveSignatureP *reserve_sig, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_PlanchetSecretsP *ps, - const struct TALER_PlanchetDetail *pd, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawHandle *wsh; - struct GNUNET_CURL_Context *ctx; - json_t *withdraw_obj; - CURL *eh; - struct GNUNET_HashCode h_denom_pub; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (reserve_pub, - sizeof (struct - TALER_ReservePublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "/reserves/%s/withdraw", - pub_str); - } - wsh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); - wsh->exchange = exchange; - wsh->cb = res_cb; - wsh->cb_cls = res_cb_cls; - wsh->pk = *pk; - wsh->pk.key.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key); - wsh->reserve_pub = *reserve_pub; - wsh->c_hash = pd->c_hash; - GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key, - &h_denom_pub); - withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub_hash and coin_ev */ - " s:o}",/* reserve_pub and reserve_sig */ - "denom_pub_hash", GNUNET_JSON_from_data_auto ( - &h_denom_pub), - "coin_ev", GNUNET_JSON_from_data (pd->coin_ev, - pd->coin_ev_size), - "reserve_sig", GNUNET_JSON_from_data_auto ( - reserve_sig)); - if (NULL == withdraw_obj) - { - GNUNET_break (0); - GNUNET_CRYPTO_rsa_public_key_free (wsh->pk.key.rsa_public_key); - GNUNET_free (wsh); - return NULL; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Attempting to withdraw from reserve %s\n", - TALER_B2S (reserve_pub)); - wsh->ps = *ps; - wsh->url = TEAH_path_to_url (exchange, - arg_str); - eh = TEL_curl_easy_get (wsh->url); - if (GNUNET_OK != - TALER_curl_easy_post (&wsh->ctx, - eh, - withdraw_obj)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (withdraw_obj); - GNUNET_free (wsh->url); - GNUNET_CRYPTO_rsa_public_key_free (wsh->pk.key.rsa_public_key); - GNUNET_free (wsh); - return NULL; - } - json_decref (withdraw_obj); - ctx = TEAH_handle_to_context (exchange); - wsh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wsh->ctx.headers, - &handle_reserve_withdraw_finished, - wsh); - return wsh; -} - - -/** - * Withdraw a coin from the exchange using a /reserve/withdraw request. Note - * that to ensure that no money is lost in case of hardware failures, - * the caller must have committed (most of) the arguments to disk - * before calling, and be ready to repeat the request with the same - * arguments in case of failures. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_priv private key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for the above callback - * @return handle for the operation on success, NULL on error, i.e. - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_WithdrawHandle * -TALER_EXCHANGE_withdraw (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct - TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_PlanchetSecretsP *ps, - TALER_EXCHANGE_WithdrawCallback - res_cb, - void *res_cb_cls) -{ - struct TALER_Amount amount_with_fee; - struct TALER_ReserveSignatureP reserve_sig; - struct TALER_WithdrawRequestPS req; - struct TALER_PlanchetDetail pd; - struct TALER_EXCHANGE_WithdrawHandle *wsh; - - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &req.reserve_pub.eddsa_pub); - req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); - req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); - if (GNUNET_OK != - TALER_amount_add (&amount_with_fee, - &pk->fee_withdraw, - &pk->value)) - { - /* exchange gave us denomination keys that overflow like this!? */ - GNUNET_break_op (0); - return NULL; - } - TALER_amount_hton (&req.amount_with_fee, - &amount_with_fee); - TALER_amount_hton (&req.withdraw_fee, - &pk->fee_withdraw); - if (GNUNET_OK != - TALER_planchet_prepare (&pk->key, - ps, - &pd)) - { - GNUNET_break_op (0); - return NULL; - } - req.h_denomination_pub = pd.denom_pub_hash; - GNUNET_CRYPTO_hash (pd.coin_ev, - pd.coin_ev_size, - &req.h_coin_envelope); - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, - &req.purpose, - &reserve_sig.eddsa_signature)); - wsh = reserve_withdraw_internal (exchange, - pk, - &reserve_sig, - &req.reserve_pub, - ps, - &pd, - res_cb, - res_cb_cls); - GNUNET_free (pd.coin_ev); - return wsh; -} - - -/** - * Withdraw a coin from the exchange using a /reserve/withdraw - * request. This API is typically used by a wallet to withdraw a tip - * where the reserve's signature was created by the merchant already. - * - * Note that to ensure that no money is lost in case of hardware - * failures, the caller must have committed (most of) the arguments to - * disk before calling, and be ready to repeat the request with the - * same arguments in case of failures. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_sig signature from the reserve authorizing the withdrawal - * @param reserve_pub public key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for @a res_cb - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_WithdrawHandle * -TALER_EXCHANGE_withdraw2 (struct TALER_EXCHANGE_Handle *exchange, - const struct - TALER_EXCHANGE_DenomPublicKey *pk, - const struct - TALER_ReserveSignatureP *reserve_sig, - const struct - TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_PlanchetSecretsP *ps, - TALER_EXCHANGE_WithdrawCallback - res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawHandle *wsh; - struct TALER_PlanchetDetail pd; - - if (GNUNET_OK != - TALER_planchet_prepare (&pk->key, - ps, - &pd)) - { - GNUNET_break_op (0); - return NULL; - } - wsh = reserve_withdraw_internal (exchange, - pk, - reserve_sig, - reserve_pub, - ps, - &pd, - res_cb, - res_cb_cls); - GNUNET_free (pd.coin_ev); - return wsh; -} - - -/** - * Cancel a withdraw status request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param sign the withdraw sign request handle - */ -void -TALER_EXCHANGE_withdraw_cancel (struct - TALER_EXCHANGE_WithdrawHandle * - sign) -{ - if (NULL != sign->job) - { - GNUNET_CURL_job_cancel (sign->job); - sign->job = NULL; - } - GNUNET_free (sign->url); - TALER_curl_easy_post_finished (&sign->ctx); - GNUNET_CRYPTO_rsa_public_key_free (sign->pk.key.rsa_public_key); - GNUNET_free (sign); -} - - -/* end of exchange_api_reserve.c */ diff --git a/src/lib/exchange_api_reserves_get.c b/src/lib/exchange_api_reserves_get.c new file mode 100644 index 000000000..62e28f05e --- /dev/null +++ b/src/lib/exchange_api_reserves_get.c @@ -0,0 +1,308 @@ +/* + This file is part of TALER + Copyright (C) 2014-2020 Taler Systems SA + + 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 + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/exchange_api_reserves_get.c + * @brief Implementation of the GET /reserves/$RESERVE_PUB requests + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include /* just for HTTP status codes */ +#include +#include +#include +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /reserves/ GET Handle + */ +struct TALER_EXCHANGE_ReservesGetHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReservesGetCallback cb; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * We received an #MHD_HTTP_OK status code. Handle the JSON + * response. + * + * @param rgh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static int +handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh, + const json_t *j) +{ + json_t *history; + unsigned int len; + struct TALER_Amount balance; + struct TALER_Amount balance_from_history; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("balance", &balance), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + history = json_object_get (j, + "history"); + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + len = json_array_size (history); + { + struct TALER_EXCHANGE_ReserveHistory *rhistory; + + rhistory = GNUNET_new_array (len, + struct TALER_EXCHANGE_ReserveHistory); + if (GNUNET_OK != + TALER_EXCHANGE_parse_reserve_history (rgh->exchange, + history, + &rgh->reserve_pub, + balance.currency, + &balance_from_history, + len, + rhistory)) + { + GNUNET_break_op (0); + TALER_EXCHANGE_free_reserve_history (rhistory, + len); + return GNUNET_SYSERR; + } + if (0 != + TALER_amount_cmp (&balance_from_history, + &balance)) + { + /* exchange cannot add up balances!? */ + GNUNET_break_op (0); + TALER_EXCHANGE_free_reserve_history (rhistory, + len); + return GNUNET_SYSERR; + } + if (NULL != rgh->cb) + { + rgh->cb (rgh->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + j, + &balance, + len, + rhistory); + rgh->cb = NULL; + } + TALER_EXCHANGE_free_reserve_history (rhistory, + len); + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/ GET request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_get_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls; + const json_t *j = response; + + rgh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_get_ok (rgh, + j)) + response_code = 0; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rgh->cb) + { + rgh->cb (rgh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + j, + NULL, + 0, NULL); + rgh->cb = NULL; + } + TALER_EXCHANGE_reserves_get_cancel (rgh); +} + + +/** + * Submit a request to obtain the transaction history of a reserve + * from the exchange. Note that while we return the full response to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid and add up to the balance). If the exchange's + * reply is not well-formed, we return an HTTP status code of zero to + * @a cb. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param reserve_pub public key of the reserve to inspect + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReservesGetHandle * +TALER_EXCHANGE_reserves_get (struct TALER_EXCHANGE_Handle *exchange, + const struct + TALER_ReservePublicKeyP *reserve_pub, + TALER_EXCHANGE_ReservesGetCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReservesGetHandle *rgh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16]; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (reserve_pub, + sizeof (struct + TALER_ReservePublicKeyP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "/reserves/%s", + pub_str); + } + rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle); + rgh->exchange = exchange; + rgh->cb = cb; + rgh->cb_cls = cb_cls; + rgh->reserve_pub = *reserve_pub; + rgh->url = TEAH_path_to_url (exchange, + arg_str); + eh = TEL_curl_easy_get (rgh->url); + ctx = TEAH_handle_to_context (exchange); + rgh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_NO, + &handle_reserves_get_finished, + rgh); + return rgh; +} + + +/** + * Cancel a reserve status request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param rgh the reserve status request handle + */ +void +TALER_EXCHANGE_reserves_get_cancel (struct + TALER_EXCHANGE_ReservesGetHandle *rgh) +{ + if (NULL != rgh->job) + { + GNUNET_CURL_job_cancel (rgh->job); + rgh->job = NULL; + } + GNUNET_free (rgh->url); + GNUNET_free (rgh); +} + + +/* end of exchange_api_reserve.c */ diff --git a/src/lib/exchange_api_track_transaction.c b/src/lib/exchange_api_track_transaction.c deleted file mode 100644 index 636b986f9..000000000 --- a/src/lib/exchange_api_track_transaction.c +++ /dev/null @@ -1,402 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA - - 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 - 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - -*/ -/** - * @file lib/exchange_api_track_transaction.c - * @brief Implementation of the /track/transaction request of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "platform.h" -#include -#include /* just for HTTP status codes */ -#include -#include -#include -#include "taler_json_lib.h" -#include "taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Deposit Wtid Handle - */ -struct TALER_EXCHANGE_DepositGetHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_DepositGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Information the exchange should sign in response. - * (with pre-filled fields from the request). - */ - struct TALER_ConfirmWirePS depconf; - -}; - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. - * - * @param dwh deposit wtid handle - * @param json json reply with the signature - * @param[out] exchange_pub set to the exchange's public key - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static int -verify_deposit_wtid_signature_ok (const struct - TALER_EXCHANGE_DepositGetHandle *dwh, - const json_t *json, - struct TALER_ExchangePublicKeyP *exchange_pub) -{ - struct TALER_ExchangeSignatureP exchange_sig; - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = TALER_EXCHANGE_get_keys (dwh->exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE, - &dwh->depconf.purpose, - &exchange_sig.eddsa_signature, - &exchange_pub->eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /track/transaction request. - * - * @param cls the `struct TALER_EXCHANGE_DepositGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_deposit_wtid_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_DepositGetHandle *dwh = cls; - const struct TALER_WireTransferIdentifierRawP *wtid = NULL; - struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS; - const struct TALER_Amount *coin_contribution = NULL; - struct TALER_Amount coin_contribution_s; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangePublicKeyP *ep = NULL; - const json_t *j = response; - - dwh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("wtid", &dwh->depconf.wtid), - GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), - TALER_JSON_spec_amount ("coin_contribution", &coin_contribution_s), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - response_code = 0; - break; - } - wtid = &dwh->depconf.wtid; - dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time); - TALER_amount_hton (&dwh->depconf.coin_contribution, - &coin_contribution_s); - coin_contribution = &coin_contribution_s; - if (GNUNET_OK != - verify_deposit_wtid_signature_ok (dwh, - j, - &exchange_pub)) - { - GNUNET_break_op (0); - response_code = 0; - } - else - { - ep = &exchange_pub; - } - } - break; - case MHD_HTTP_ACCEPTED: - { - /* Transaction known, but not executed yet */ - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - response_code = 0; - break; - } - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Exchange does not know about transaction; - we should pass the reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - dwh->cb (dwh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - ep, - j, - wtid, - execution_time, - coin_contribution); - TALER_EXCHANGE_deposits_get_cancel (dwh); -} - - -/** - * Obtain wire transfer details about an existing deposit operation. - * - * @param exchange the exchange to query - * @param merchant_priv the merchant's private key - * @param h_wire hash of merchant's wire transfer details - * @param h_contract_terms hash of the proposal data from the contract - * between merchant and customer - * @param coin_pub public key of the coin - * @param cb function to call with the result - * @param cb_cls closure for @a cb - * @return handle to abort request - */ -struct TALER_EXCHANGE_DepositGetHandle * -TALER_EXCHANGE_deposits_get (struct TALER_EXCHANGE_Handle *exchange, - const struct - TALER_MerchantPrivateKeyP *merchant_priv, - const struct GNUNET_HashCode *h_wire, - const struct - GNUNET_HashCode *h_contract_terms, - const struct - TALER_CoinSpendPublicKeyP *coin_pub, - TALER_EXCHANGE_DepositGetCallback cb, - void *cb_cls) -{ - struct TALER_DepositTrackPS dtp; - struct TALER_MerchantSignatureP merchant_sig; - struct TALER_EXCHANGE_DepositGetHandle *dwh; - struct GNUNET_CURL_Context *ctx; - CURL *eh; - char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP) - + sizeof (struct GNUNET_HashCode) - + sizeof (struct TALER_MerchantPublicKeyP) - + sizeof (struct GNUNET_HashCode) - + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48]; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION); - dtp.purpose.size = htonl (sizeof (dtp)); - dtp.h_contract_terms = *h_contract_terms; - dtp.h_wire = *h_wire; - GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, - &dtp.merchant.eddsa_pub); - - dtp.coin_pub = *coin_pub; - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, - &dtp.purpose, - &merchant_sig.eddsa_sig)); - { - char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; - char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2]; - char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2]; - char chash_str[sizeof (struct GNUNET_HashCode) * 2]; - char whash_str[sizeof (struct GNUNET_HashCode) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (h_wire, - sizeof (struct - GNUNET_HashCode), - whash_str, - sizeof (whash_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (&dtp.merchant, - sizeof (struct - TALER_MerchantPublicKeyP), - mpub_str, - sizeof (mpub_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (h_contract_terms, - sizeof (struct - GNUNET_HashCode), - chash_str, - sizeof (chash_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (coin_pub, - sizeof (struct - TALER_CoinSpendPublicKeyP), - cpub_str, - sizeof (cpub_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (&merchant_sig, - sizeof (struct - TALER_MerchantSignatureP), - msig_str, - sizeof (msig_str)); - *end = '\0'; - - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "/deposits/%s/%s/%s/%s?merchant_sig=%s", - whash_str, - mpub_str, - chash_str, - cpub_str, - msig_str); - } - - dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle); - dwh->exchange = exchange; - dwh->cb = cb; - dwh->cb_cls = cb_cls; - dwh->url = TEAH_path_to_url (exchange, - arg_str); - dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS)); - dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE); - dwh->depconf.h_wire = *h_wire; - dwh->depconf.h_contract_terms = *h_contract_terms; - dwh->depconf.coin_pub = *coin_pub; - - eh = TEL_curl_easy_get (dwh->url); - ctx = TEAH_handle_to_context (exchange); - dwh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_NO, - &handle_deposit_wtid_finished, - dwh); - return dwh; -} - - -/** - * Cancel deposit wtid request. This function cannot be used on a request - * handle if a response is already served for it. - * - * @param dwh the wire deposits request handle - */ -void -TALER_EXCHANGE_deposits_get_cancel (struct - TALER_EXCHANGE_DepositGetHandle * - dwh) -{ - if (NULL != dwh->job) - { - GNUNET_CURL_job_cancel (dwh->job); - dwh->job = NULL; - } - GNUNET_free (dwh->url); - TALER_curl_easy_post_finished (&dwh->ctx); - GNUNET_free (dwh); -} - - -/* end of exchange_api_track_transaction.c */ diff --git a/src/lib/exchange_api_track_transfer.c b/src/lib/exchange_api_track_transfer.c deleted file mode 100644 index c5d484d23..000000000 --- a/src/lib/exchange_api_track_transfer.c +++ /dev/null @@ -1,400 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015, 2016 Taler Systems SA - - 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 - 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 General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - -*/ -/** - * @file lib/exchange_api_track_transfer.c - * @brief Implementation of the /track/transfer request of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "platform.h" -#include -#include /* just for HTTP status codes */ -#include -#include -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /track/transfer Handle - */ -struct TALER_EXCHANGE_TransfersGetHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_TransfersGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * We got a #MHD_HTTP_OK response for the /track/transfer request. - * Check that the response is well-formed and if it is, call the - * callback. If not, return an error code. - * - * This code is very similar to - * merchant_api_track_transfer.c::check_track_transfer_response_ok. - * Any changes should likely be reflected there as well. - * - * @param wdh handle to the operation - * @param json response we got - * @return #GNUNET_OK if we are done and all is well, - * #GNUNET_SYSERR if the response was bogus - */ -static int -check_track_transfer_response_ok (struct - TALER_EXCHANGE_TransfersGetHandle *wdh, - const json_t *json) -{ - json_t *details_j; - struct GNUNET_HashCode h_wire; - struct GNUNET_TIME_Absolute exec_time; - struct TALER_Amount total_amount; - struct TALER_Amount total_expected; - struct TALER_Amount wire_fee; - struct TALER_MerchantPublicKeyP merchant_pub; - unsigned int num_details; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("total", &total_amount), - TALER_JSON_spec_amount ("wire_fee", &wire_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), - GNUNET_JSON_spec_fixed_auto ("h_wire", &h_wire), - GNUNET_JSON_spec_absolute_time ("execution_time", &exec_time), - GNUNET_JSON_spec_json ("deposits", &details_j), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_get_zero (total_amount.currency, - &total_expected)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - num_details = json_array_size (details_j); - { - struct TALER_TrackTransferDetails details[num_details]; - unsigned int i; - struct GNUNET_HashContext *hash_context; - struct TALER_WireDepositDetailP dd; - struct TALER_WireDepositDataPS wdp; - - hash_context = GNUNET_CRYPTO_hash_context_start (); - for (i = 0; ih_contract_terms), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), - TALER_JSON_spec_amount ("deposit_value", &detail->coin_value), - TALER_JSON_spec_amount ("deposit_fee", &detail->coin_fee), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (detail_j, - spec_detail, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_CRYPTO_hash_context_abort (hash_context); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - /* build up big hash for signature checking later */ - dd.h_contract_terms = detail->h_contract_terms; - dd.execution_time = GNUNET_TIME_absolute_hton (exec_time); - dd.coin_pub = detail->coin_pub; - TALER_amount_hton (&dd.deposit_value, - &detail->coin_value); - TALER_amount_hton (&dd.deposit_fee, - &detail->coin_fee); - if ( (GNUNET_OK != - TALER_amount_add (&total_expected, - &total_expected, - &detail->coin_value)) || - (GNUNET_OK != - TALER_amount_subtract (&total_expected, - &total_expected, - &detail->coin_fee)) ) - { - GNUNET_break_op (0); - GNUNET_CRYPTO_hash_context_abort (hash_context); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_CRYPTO_hash_context_read (hash_context, - &dd, - sizeof (struct - TALER_WireDepositDetailP)); - } - /* Check signature */ - wdp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT); - wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS)); - TALER_amount_hton (&wdp.total, - &total_amount); - TALER_amount_hton (&wdp.wire_fee, - &wire_fee); - wdp.merchant_pub = merchant_pub; - wdp.h_wire = h_wire; - GNUNET_CRYPTO_hash_context_finish (hash_context, - &wdp.h_details); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (TALER_EXCHANGE_get_keys ( - wdh->exchange), - &exchange_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify - (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT, - &wdp.purpose, - &exchange_sig.eddsa_signature, - &exchange_pub.eddsa_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - TALER_amount_subtract (&total_expected, - &total_expected, - &wire_fee)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - if (0 != - TALER_amount_cmp (&total_expected, - &total_amount)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - wdh->cb (wdh->cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - &exchange_pub, - json, - &h_wire, - exec_time, - &total_amount, - &wire_fee, - num_details, - details); - } - GNUNET_JSON_parse_free (spec); - TALER_EXCHANGE_transfers_get_cancel (wdh); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /track/transfer request. - * - * @param cls the `struct TALER_EXCHANGE_TransfersGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_track_transfer_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls; - const json_t *j = response; - - wdh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK == - check_track_transfer_response_ok (wdh, - j)) - return; - GNUNET_break_op (0); - response_code = 0; - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Exchange does not know about transaction; - we should pass the reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - wdh->cb (wdh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - NULL, - j, - NULL, - GNUNET_TIME_UNIT_ZERO_ABS, - NULL, - NULL, - 0, NULL); - TALER_EXCHANGE_transfers_get_cancel (wdh); -} - - -/** - * Query the exchange about which transactions were combined - * to create a wire transfer. - * - * @param exchange exchange to query - * @param wtid raw wire transfer identifier to get information about - * @param cb callback to call - * @param cb_cls closure for @a cb - * @return handle to cancel operation - */ -struct TALER_EXCHANGE_TransfersGetHandle * -TALER_EXCHANGE_transfers_get (struct TALER_EXCHANGE_Handle *exchange, - const struct - TALER_WireTransferIdentifierRawP *wtid, - TALER_EXCHANGE_TransfersGetCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_TransfersGetHandle *wdh; - struct GNUNET_CURL_Context *ctx; - CURL *eh; - char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32]; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - - wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle); - wdh->exchange = exchange; - wdh->cb = cb; - wdh->cb_cls = cb_cls; - - { - char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (wtid, - sizeof (struct - TALER_WireTransferIdentifierRawP), - wtid_str, - sizeof (wtid_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "/transfers/%s", - wtid_str); - } - wdh->url = TEAH_path_to_url (wdh->exchange, - arg_str); - eh = TEL_curl_easy_get (wdh->url); - ctx = TEAH_handle_to_context (exchange); - wdh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_track_transfer_finished, - wdh); - return wdh; -} - - -/** - * Cancel wire deposits request. This function cannot be used on a request - * handle if a response is already served for it. - * - * @param wdh the wire deposits request handle - */ -void -TALER_EXCHANGE_transfers_get_cancel (struct - TALER_EXCHANGE_TransfersGetHandle *wdh) -{ - if (NULL != wdh->job) - { - GNUNET_CURL_job_cancel (wdh->job); - wdh->job = NULL; - } - GNUNET_free (wdh->url); - GNUNET_free (wdh); -} - - -/* end of exchange_api_wire_deposits.c */ diff --git a/src/lib/exchange_api_transfers_get.c b/src/lib/exchange_api_transfers_get.c new file mode 100644 index 000000000..8ea8918ca --- /dev/null +++ b/src/lib/exchange_api_transfers_get.c @@ -0,0 +1,400 @@ +/* + This file is part of TALER + Copyright (C) 2014-2020 Taler Systems SA + + 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 + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/exchange_api_transfers_get.c + * @brief Implementation of the GET /transfers/ request + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include /* just for HTTP status codes */ +#include +#include +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /transfers/ GET Handle + */ +struct TALER_EXCHANGE_TransfersGetHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_TransfersGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * We got a #MHD_HTTP_OK response for the /transfers/ request. + * Check that the response is well-formed and if it is, call the + * callback. If not, return an error code. + * + * This code is very similar to + * merchant_api_track_transfer.c::check_transfers_get_response_ok. + * Any changes should likely be reflected there as well. + * + * @param wdh handle to the operation + * @param json response we got + * @return #GNUNET_OK if we are done and all is well, + * #GNUNET_SYSERR if the response was bogus + */ +static int +check_transfers_get_response_ok (struct + TALER_EXCHANGE_TransfersGetHandle *wdh, + const json_t *json) +{ + json_t *details_j; + struct GNUNET_HashCode h_wire; + struct GNUNET_TIME_Absolute exec_time; + struct TALER_Amount total_amount; + struct TALER_Amount total_expected; + struct TALER_Amount wire_fee; + struct TALER_MerchantPublicKeyP merchant_pub; + unsigned int num_details; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("total", &total_amount), + TALER_JSON_spec_amount ("wire_fee", &wire_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), + GNUNET_JSON_spec_fixed_auto ("h_wire", &h_wire), + GNUNET_JSON_spec_absolute_time ("execution_time", &exec_time), + GNUNET_JSON_spec_json ("deposits", &details_j), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_get_zero (total_amount.currency, + &total_expected)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + num_details = json_array_size (details_j); + { + struct TALER_TrackTransferDetails details[num_details]; + unsigned int i; + struct GNUNET_HashContext *hash_context; + struct TALER_WireDepositDetailP dd; + struct TALER_WireDepositDataPS wdp; + + hash_context = GNUNET_CRYPTO_hash_context_start (); + for (i = 0; ih_contract_terms), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), + TALER_JSON_spec_amount ("deposit_value", &detail->coin_value), + TALER_JSON_spec_amount ("deposit_fee", &detail->coin_fee), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (detail_j, + spec_detail, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_hash_context_abort (hash_context); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + /* build up big hash for signature checking later */ + dd.h_contract_terms = detail->h_contract_terms; + dd.execution_time = GNUNET_TIME_absolute_hton (exec_time); + dd.coin_pub = detail->coin_pub; + TALER_amount_hton (&dd.deposit_value, + &detail->coin_value); + TALER_amount_hton (&dd.deposit_fee, + &detail->coin_fee); + if ( (GNUNET_OK != + TALER_amount_add (&total_expected, + &total_expected, + &detail->coin_value)) || + (GNUNET_OK != + TALER_amount_subtract (&total_expected, + &total_expected, + &detail->coin_fee)) ) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_hash_context_abort (hash_context); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash_context_read (hash_context, + &dd, + sizeof (struct + TALER_WireDepositDetailP)); + } + /* Check signature */ + wdp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT); + wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS)); + TALER_amount_hton (&wdp.total, + &total_amount); + TALER_amount_hton (&wdp.wire_fee, + &wire_fee); + wdp.merchant_pub = merchant_pub; + wdp.h_wire = h_wire; + GNUNET_CRYPTO_hash_context_finish (hash_context, + &wdp.h_details); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (TALER_EXCHANGE_get_keys ( + wdh->exchange), + &exchange_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify + (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT, + &wdp.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_amount_subtract (&total_expected, + &total_expected, + &wire_fee)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + if (0 != + TALER_amount_cmp (&total_expected, + &total_amount)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + wdh->cb (wdh->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &exchange_pub, + json, + &h_wire, + exec_time, + &total_amount, + &wire_fee, + num_details, + details); + } + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_transfers_get_cancel (wdh); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /transfers/ request. + * + * @param cls the `struct TALER_EXCHANGE_TransfersGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_transfers_get_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls; + const json_t *j = response; + + wdh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK == + check_transfers_get_response_ok (wdh, + j)) + return; + GNUNET_break_op (0); + response_code = 0; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + wdh->cb (wdh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + NULL, + j, + NULL, + GNUNET_TIME_UNIT_ZERO_ABS, + NULL, + NULL, + 0, NULL); + TALER_EXCHANGE_transfers_get_cancel (wdh); +} + + +/** + * Query the exchange about which transactions were combined + * to create a wire transfer. + * + * @param exchange exchange to query + * @param wtid raw wire transfer identifier to get information about + * @param cb callback to call + * @param cb_cls closure for @a cb + * @return handle to cancel operation + */ +struct TALER_EXCHANGE_TransfersGetHandle * +TALER_EXCHANGE_transfers_get (struct TALER_EXCHANGE_Handle *exchange, + const struct + TALER_WireTransferIdentifierRawP *wtid, + TALER_EXCHANGE_TransfersGetCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_TransfersGetHandle *wdh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32]; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + + wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle); + wdh->exchange = exchange; + wdh->cb = cb; + wdh->cb_cls = cb_cls; + + { + char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (wtid, + sizeof (struct + TALER_WireTransferIdentifierRawP), + wtid_str, + sizeof (wtid_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "/transfers/%s", + wtid_str); + } + wdh->url = TEAH_path_to_url (wdh->exchange, + arg_str); + eh = TEL_curl_easy_get (wdh->url); + ctx = TEAH_handle_to_context (exchange); + wdh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_transfers_get_finished, + wdh); + return wdh; +} + + +/** + * Cancel wire deposits request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param wdh the wire deposits request handle + */ +void +TALER_EXCHANGE_transfers_get_cancel (struct + TALER_EXCHANGE_TransfersGetHandle *wdh) +{ + if (NULL != wdh->job) + { + GNUNET_CURL_job_cancel (wdh->job); + wdh->job = NULL; + } + GNUNET_free (wdh->url); + GNUNET_free (wdh); +} + + +/* end of exchange_api_wire_deposits.c */ diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c new file mode 100644 index 000000000..c63235377 --- /dev/null +++ b/src/lib/exchange_api_withdraw.c @@ -0,0 +1,611 @@ +/* + This file is part of TALER + Copyright (C) 2014-2020 Taler Systems SA + + 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 + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/exchange_api_withdraw.c + * @brief Implementation of the /reserves/$RESERVE_PUB/withdraw requests + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include /* just for HTTP status codes */ +#include +#include +#include +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Withdraw Handle + */ +struct TALER_EXCHANGE_WithdrawHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_WithdrawCallback cb; + + /** + * Secrets of the planchet. + */ + struct TALER_PlanchetSecretsP ps; + + /** + * Denomination key we are withdrawing. + */ + struct TALER_EXCHANGE_DenomPublicKey pk; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Hash of the public key of the coin we are signing. + */ + struct GNUNET_HashCode c_hash; + + /** + * Public key of the reserve we are withdrawing from. + */ + struct TALER_ReservePublicKeyP reserve_pub; + +}; + + +/** + * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation. + * Extract the coin's signature and return it to the caller. The signature we + * get from the exchange is for the blinded value. Thus, we first must + * unblind it and then should verify its validity against our coin's hash. + * + * If everything checks out, we return the unblinded signature + * to the application via the callback. + * + * @param wh operation handle + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +reserve_withdraw_ok (struct TALER_EXCHANGE_WithdrawHandle *wh, + const json_t *json) +{ + struct GNUNET_CRYPTO_RsaSignature *blind_sig; + struct TALER_FreshCoin fc; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_signature ("ev_sig", + &blind_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_planchet_to_coin (&wh->pk.key, + blind_sig, + &wh->ps, + &wh->c_hash, + &fc)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (spec); + + /* signature is valid, return it to the application */ + wh->cb (wh->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &fc.sig, + json); + /* make sure callback isn't called again after return */ + wh->cb = NULL; + GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature); + return GNUNET_OK; +} + + +/** + * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw operation. + * Check the signatures on the withdraw transactions in the provided + * history and that the balances add up. We don't do anything directly + * with the information, as the JSON will be returned to the application. + * However, our job is ensuring that the exchange followed the protocol, and + * this in particular means checking all of the signatures in the history. + * + * @param wh operation handle + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +reserve_withdraw_payment_required (struct + TALER_EXCHANGE_WithdrawHandle *wh, + const json_t *json) +{ + struct TALER_Amount balance; + struct TALER_Amount balance_from_history; + struct TALER_Amount requested_amount; + json_t *history; + size_t len; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("balance", &balance), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + history = json_object_get (json, + "history"); + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* go over transaction history and compute + total incoming and outgoing amounts */ + len = json_array_size (history); + { + struct TALER_EXCHANGE_ReserveHistory *rhistory; + + /* Use heap allocation as "len" may be very big and thus this may + not fit on the stack. Use "GNUNET_malloc_large" as a malicious + exchange may theoretically try to crash us by giving a history + that does not fit into our memory. */ + rhistory = GNUNET_malloc_large (sizeof (struct + TALER_EXCHANGE_ReserveHistory) + * len); + if (NULL == rhistory) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_EXCHANGE_parse_reserve_history (wh->exchange, + history, + &wh->reserve_pub, + balance.currency, + &balance_from_history, + len, + rhistory)) + { + GNUNET_break_op (0); + TALER_EXCHANGE_free_reserve_history (rhistory, + len); + return GNUNET_SYSERR; + } + TALER_EXCHANGE_free_reserve_history (rhistory, + len); + } + + if (0 != + TALER_amount_cmp (&balance_from_history, + &balance)) + { + /* exchange cannot add up balances!? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* Compute how much we expected to charge to the reserve */ + if (GNUNET_OK != + TALER_amount_add (&requested_amount, + &wh->pk.value, + &wh->pk.fee_withdraw)) + { + /* Overflow here? Very strange, our CPU must be fried... */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* Check that funds were really insufficient */ + if (0 >= TALER_amount_cmp (&requested_amount, + &balance)) + { + /* Requested amount is smaller or equal to reported balance, + so this should not have failed. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RESERVE_PUB/withdraw request. + * + * @param cls the `struct TALER_EXCHANGE_WithdrawHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserve_withdraw_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_WithdrawHandle *wh = cls; + const json_t *j = response; + + wh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + reserve_withdraw_ok (wh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + /* The exchange says that the reserve has insufficient funds; + check the signatures in the history... */ + if (GNUNET_OK != + reserve_withdraw_payment_required (wh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_FORBIDDEN: + GNUNET_break (0); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, the exchange basically just says + that it doesn't know this reserve. Can happen if we + query before the wire transfer went through. + We should simply pass the JSON reply to the application. */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + NULL, + j); + wh->cb = NULL; + } + TALER_EXCHANGE_withdraw_cancel (wh); +} + + +/** + * Helper function for #TALER_EXCHANGE_withdraw2() and + * #TALER_EXCHANGE_withdraw(). + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_sig signature from the reserve authorizing the withdrawal + * @param reserve_pub public key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param pd planchet details matching @a ps + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for @a res_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_WithdrawHandle * +reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_PlanchetSecretsP *ps, + const struct TALER_PlanchetDetail *pd, + TALER_EXCHANGE_WithdrawCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_WithdrawHandle *wh; + struct GNUNET_CURL_Context *ctx; + json_t *withdraw_obj; + CURL *eh; + struct GNUNET_HashCode h_denom_pub; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; + + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (reserve_pub, + sizeof (struct + TALER_ReservePublicKeyP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "/reserves/%s/withdraw", + pub_str); + } + wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); + wh->exchange = exchange; + wh->cb = res_cb; + wh->cb_cls = res_cb_cls; + wh->pk = *pk; + wh->pk.key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key); + wh->reserve_pub = *reserve_pub; + wh->c_hash = pd->c_hash; + GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key, + &h_denom_pub); + withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub_hash and coin_ev */ + " s:o}",/* reserve_pub and reserve_sig */ + "denom_pub_hash", GNUNET_JSON_from_data_auto ( + &h_denom_pub), + "coin_ev", GNUNET_JSON_from_data (pd->coin_ev, + pd->coin_ev_size), + "reserve_sig", GNUNET_JSON_from_data_auto ( + reserve_sig)); + if (NULL == withdraw_obj) + { + GNUNET_break (0); + GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key); + GNUNET_free (wh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Attempting to withdraw from reserve %s\n", + TALER_B2S (reserve_pub)); + wh->ps = *ps; + wh->url = TEAH_path_to_url (exchange, + arg_str); + eh = TEL_curl_easy_get (wh->url); + if (GNUNET_OK != + TALER_curl_easy_post (&wh->ctx, + eh, + withdraw_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (withdraw_obj); + GNUNET_free (wh->url); + GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key); + GNUNET_free (wh); + return NULL; + } + json_decref (withdraw_obj); + ctx = TEAH_handle_to_context (exchange); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->ctx.headers, + &handle_reserve_withdraw_finished, + wh); + return wh; +} + + +/** + * Withdraw a coin from the exchange using a /reserve/withdraw request. Note + * that to ensure that no money is lost in case of hardware failures, + * the caller must have committed (most of) the arguments to disk + * before calling, and be ready to repeat the request with the same + * arguments in case of failures. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_priv private key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return handle for the operation on success, NULL on error, i.e. + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_WithdrawHandle * +TALER_EXCHANGE_withdraw (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct + TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_PlanchetSecretsP *ps, + TALER_EXCHANGE_WithdrawCallback + res_cb, + void *res_cb_cls) +{ + struct TALER_Amount amount_with_fee; + struct TALER_ReserveSignatureP reserve_sig; + struct TALER_WithdrawRequestPS req; + struct TALER_PlanchetDetail pd; + struct TALER_EXCHANGE_WithdrawHandle *wh; + + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &req.reserve_pub.eddsa_pub); + req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); + req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); + if (GNUNET_OK != + TALER_amount_add (&amount_with_fee, + &pk->fee_withdraw, + &pk->value)) + { + /* exchange gave us denomination keys that overflow like this!? */ + GNUNET_break_op (0); + return NULL; + } + TALER_amount_hton (&req.amount_with_fee, + &amount_with_fee); + TALER_amount_hton (&req.withdraw_fee, + &pk->fee_withdraw); + if (GNUNET_OK != + TALER_planchet_prepare (&pk->key, + ps, + &pd)) + { + GNUNET_break_op (0); + return NULL; + } + req.h_denomination_pub = pd.denom_pub_hash; + GNUNET_CRYPTO_hash (pd.coin_ev, + pd.coin_ev_size, + &req.h_coin_envelope); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &req.purpose, + &reserve_sig.eddsa_signature)); + wh = reserve_withdraw_internal (exchange, + pk, + &reserve_sig, + &req.reserve_pub, + ps, + &pd, + res_cb, + res_cb_cls); + GNUNET_free (pd.coin_ev); + return wh; +} + + +/** + * Withdraw a coin from the exchange using a /reserve/withdraw + * request. This API is typically used by a wallet to withdraw a tip + * where the reserve's signature was created by the merchant already. + * + * Note that to ensure that no money is lost in case of hardware + * failures, the caller must have committed (most of) the arguments to + * disk before calling, and be ready to repeat the request with the + * same arguments in case of failures. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_sig signature from the reserve authorizing the withdrawal + * @param reserve_pub public key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for @a res_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_WithdrawHandle * +TALER_EXCHANGE_withdraw2 (struct TALER_EXCHANGE_Handle *exchange, + const struct + TALER_EXCHANGE_DenomPublicKey *pk, + const struct + TALER_ReserveSignatureP *reserve_sig, + const struct + TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_PlanchetSecretsP *ps, + TALER_EXCHANGE_WithdrawCallback + res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_WithdrawHandle *wh; + struct TALER_PlanchetDetail pd; + + if (GNUNET_OK != + TALER_planchet_prepare (&pk->key, + ps, + &pd)) + { + GNUNET_break_op (0); + return NULL; + } + wh = reserve_withdraw_internal (exchange, + pk, + reserve_sig, + reserve_pub, + ps, + &pd, + res_cb, + res_cb_cls); + GNUNET_free (pd.coin_ev); + return wh; +} + + +/** + * Cancel a withdraw status request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param sign the withdraw sign request handle + */ +void +TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + GNUNET_free (wh->url); + TALER_curl_easy_post_finished (&wh->ctx); + GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key); + GNUNET_free (wh); +} diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c index 144f004d6..a2d7e00ab 100644 --- a/src/testing/test_auditor_api.c +++ b/src/testing/test_auditor_api.c @@ -190,10 +190,10 @@ run (void *cls, /** * Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x * EUR:0.13) */ - TALER_TESTING_cmd_refresh_melt_double ("refresh-melt-1", - "refresh-withdraw-coin-1", - MHD_HTTP_OK, - NULL), + TALER_TESTING_cmd_melt_double ("refresh-melt-1", + "refresh-withdraw-coin-1", + MHD_HTTP_OK, + NULL), /** * Complete (successful) melt operation, and withdraw the coins */ diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index 38d0c9212..ba655d9b8 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -239,10 +239,10 @@ run (void *cls, /** * Melt the rest of the coin's value * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ - TALER_TESTING_cmd_refresh_melt_double ("refresh-melt-1", - "refresh-withdraw-coin-1", - MHD_HTTP_OK, - NULL), + TALER_TESTING_cmd_melt_double ("refresh-melt-1", + "refresh-withdraw-coin-1", + MHD_HTTP_OK, + NULL), /** * Complete (successful) melt operation, and * withdraw the coins @@ -286,16 +286,16 @@ run (void *cls, MHD_HTTP_OK), /* Test running a failing melt operation (same operation * again must fail) */ - TALER_TESTING_cmd_refresh_melt ("refresh-melt-failing", - "refresh-withdraw-coin-1", - MHD_HTTP_CONFLICT, - NULL), + TALER_TESTING_cmd_melt ("refresh-melt-failing", + "refresh-withdraw-coin-1", + MHD_HTTP_CONFLICT, + NULL), /* Test running a failing melt operation (on a coin that was itself revealed and subsequently deposited) */ - TALER_TESTING_cmd_refresh_melt ("refresh-melt-failing-2", - "refresh-reveal-1", - MHD_HTTP_CONFLICT, - NULL), + TALER_TESTING_cmd_melt ("refresh-melt-failing-2", + "refresh-reveal-1", + MHD_HTTP_CONFLICT, + NULL), TALER_TESTING_cmd_end () }; diff --git a/src/testing/test_exchange_api_revocation.c b/src/testing/test_exchange_api_revocation.c index 45463dedf..6a9cdefe6 100644 --- a/src/testing/test_exchange_api_revocation.c +++ b/src/testing/test_exchange_api_revocation.c @@ -98,10 +98,10 @@ run (void *cls, /** * Melt SOME of the rest of the coin's value * (EUR:3.17 = 3x EUR:1.03 + 7x EUR:0.13) */ - TALER_TESTING_cmd_refresh_melt ("refresh-melt-1", - "withdraw-revocation-coin-1", - MHD_HTTP_OK, - NULL), + TALER_TESTING_cmd_melt ("refresh-melt-1", + "withdraw-revocation-coin-1", + MHD_HTTP_OK, + NULL), /** * Complete (successful) melt operation, and withdraw the coins */ @@ -133,11 +133,11 @@ run (void *cls, /* Melt original coin AGAIN, but only create one 0.1 EUR coin; This costs EUR:0.03 in refresh and EUR:01 in withdraw fees, leaving EUR:3.69. */ - TALER_TESTING_cmd_refresh_melt ("refresh-melt-2", - "withdraw-revocation-coin-1", - MHD_HTTP_OK, - "EUR:0.1", - NULL), + TALER_TESTING_cmd_melt ("refresh-melt-2", + "withdraw-revocation-coin-1", + MHD_HTTP_OK, + "EUR:0.1", + NULL), /** * Complete (successful) melt operation, and withdraw the coins */ diff --git a/src/testing/test_exchange_api_twisted.c b/src/testing/test_exchange_api_twisted.c index a3a038d40..146e28de6 100644 --- a/src/testing/test_exchange_api_twisted.c +++ b/src/testing/test_exchange_api_twisted.c @@ -140,7 +140,7 @@ run (void *cls, /** * Melt the rest of the coin's value * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ - TALER_TESTING_cmd_refresh_melt + TALER_TESTING_cmd_melt ("refresh-melt", "refresh-withdraw-coin", MHD_HTTP_OK, diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c index 73b74dafb..dd861fae1 100644 --- a/src/testing/testing_api_cmd_refresh.c +++ b/src/testing/testing_api_cmd_refresh.c @@ -87,7 +87,7 @@ struct RefreshMeltState /** * Melt handle while operation is running. */ - struct TALER_EXCHANGE_RefreshMeltHandle *rmh; + struct TALER_EXCHANGE_MeltHandle *rmh; /** * Interpreter state. @@ -173,7 +173,7 @@ struct RefreshRevealState /** * Reveal handle while operation is running. */ - struct TALER_EXCHANGE_RefreshRevealHandle *rrh; + struct TALER_EXCHANGE_RefreshesRevealHandle *rrh; /** * Convenience struct to keep in one place all the @@ -230,7 +230,7 @@ struct RefreshLinkState /** * Handle to the ongoing operation. */ - struct TALER_EXCHANGE_RefreshLinkHandle *rlh; + struct TALER_EXCHANGE_LinkHandle *rlh; /** * Interpreter state. @@ -423,7 +423,7 @@ refresh_reveal_run (void *cls, return; } rms = melt_cmd->cls; - rrs->rrh = TALER_EXCHANGE_refresh_reveal + rrs->rrh = TALER_EXCHANGE_refreshes_reveal (is->exchange, rms->refresh_data_length, rms->refresh_data, @@ -459,7 +459,7 @@ refresh_reveal_cleanup (void *cls, rrs->is->ip, cmd->label); - TALER_EXCHANGE_refresh_reveal_cancel (rrs->rrh); + TALER_EXCHANGE_refreshes_reveal_cancel (rrs->rrh); rrs->rrh = NULL; } if (NULL != rrs->retry_task) @@ -730,7 +730,7 @@ refresh_link_run (void *cls, } /* finally, use private key from withdraw sign command */ - rls->rlh = TALER_EXCHANGE_refresh_link + rls->rlh = TALER_EXCHANGE_link (is->exchange, coin_priv, &link_cb, rls); if (NULL == rls->rlh) @@ -762,7 +762,7 @@ refresh_link_cleanup (void *cls, "Command %u (%s) did not complete\n", rls->is->ip, cmd->label); - TALER_EXCHANGE_refresh_link_cancel (rls->rlh); + TALER_EXCHANGE_link_cancel (rls->rlh); rls->rlh = NULL; } if (NULL != rls->retry_task) @@ -782,13 +782,13 @@ refresh_link_cleanup (void *cls, * @param is the interpreter state. */ static void -refresh_melt_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is); +melt_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); /** - * Task scheduled to re-try #refresh_melt_run. + * Task scheduled to re-try #melt_run. * * @param cls a `struct RefreshMeltState` */ @@ -798,9 +798,9 @@ do_melt_retry (void *cls) struct RefreshMeltState *rms = cls; rms->retry_task = NULL; - refresh_melt_run (rms, - NULL, - rms->is); + melt_run (rms, + NULL, + rms->is); } @@ -870,7 +870,7 @@ melt_cb (void *cls, { TALER_LOG_DEBUG ("Doubling the melt (%s)\n", rms->is->commands[rms->is->ip].label); - rms->rmh = TALER_EXCHANGE_refresh_melt + rms->rmh = TALER_EXCHANGE_melt (rms->is->exchange, rms->refresh_data_length, rms->refresh_data, &melt_cb, rms); rms->double_melt = GNUNET_NO; @@ -888,9 +888,9 @@ melt_cb (void *cls, * @param is the interpreter state. */ static void -refresh_melt_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is) +melt_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) { struct RefreshMeltState *rms = cls; unsigned int num_fresh_coins; @@ -1006,11 +1006,11 @@ refresh_melt_run (void *cls, TALER_TESTING_interpreter_fail (rms->is); return; } - rms->rmh = TALER_EXCHANGE_refresh_melt (is->exchange, - rms->refresh_data_length, - rms->refresh_data, - &melt_cb, - rms); + rms->rmh = TALER_EXCHANGE_melt (is->exchange, + rms->refresh_data_length, + rms->refresh_data, + &melt_cb, + rms); if (NULL == rms->rmh) { @@ -1030,8 +1030,8 @@ refresh_melt_run (void *cls, * @param cmd the command which is being cleaned up. */ static void -refresh_melt_cleanup (void *cls, - const struct TALER_TESTING_Command *cmd) +melt_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) { struct RefreshMeltState *rms = cls; @@ -1040,7 +1040,7 @@ refresh_melt_cleanup (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Command %u (%s) did not complete\n", rms->is->ip, rms->is->commands[rms->is->ip].label); - TALER_EXCHANGE_refresh_melt_cancel (rms->rmh); + TALER_EXCHANGE_melt_cancel (rms->rmh); rms->rmh = NULL; } if (NULL != rms->retry_task) @@ -1073,10 +1073,10 @@ refresh_melt_cleanup (void *cls, * @return #GNUNET_OK on success. */ static int -refresh_melt_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) +melt_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) { struct RefreshMeltState *rms = cls; @@ -1161,10 +1161,10 @@ parse_amounts (struct RefreshMeltState *rms, * @return the command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_refresh_melt (const char *label, - const char *coin_reference, - unsigned int expected_response_code, - ...) +TALER_TESTING_cmd_melt (const char *label, + const char *coin_reference, + unsigned int expected_response_code, + ...) { struct RefreshMeltState *rms; va_list ap; @@ -1180,9 +1180,9 @@ TALER_TESTING_cmd_refresh_melt (const char *label, struct TALER_TESTING_Command cmd = { .label = label, .cls = rms, - .run = &refresh_melt_run, - .cleanup = &refresh_melt_cleanup, - .traits = &refresh_melt_traits + .run = &melt_run, + .cleanup = &melt_cleanup, + .traits = &melt_traits }; return cmd; @@ -1203,10 +1203,10 @@ TALER_TESTING_cmd_refresh_melt (const char *label, * @return the command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_refresh_melt_double (const char *label, - const char *coin_reference, - unsigned int expected_response_code, - ...) +TALER_TESTING_cmd_melt_double (const char *label, + const char *coin_reference, + unsigned int expected_response_code, + ...) { struct RefreshMeltState *rms; va_list ap; @@ -1223,9 +1223,9 @@ TALER_TESTING_cmd_refresh_melt_double (const char *label, struct TALER_TESTING_Command cmd = { .label = label, .cls = rms, - .run = &refresh_melt_run, - .cleanup = &refresh_melt_cleanup, - .traits = &refresh_melt_traits + .run = &melt_run, + .cleanup = &melt_cleanup, + .traits = &melt_traits }; return cmd; @@ -1240,11 +1240,11 @@ TALER_TESTING_cmd_refresh_melt_double (const char *label, * @return modified command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_refresh_melt_with_retry (struct TALER_TESTING_Command cmd) +TALER_TESTING_cmd_melt_with_retry (struct TALER_TESTING_Command cmd) { struct RefreshMeltState *rms; - GNUNET_assert (&refresh_melt_run == cmd.run); + GNUNET_assert (&melt_run == cmd.run); rms = cmd.cls; rms->do_retry = GNUNET_YES; return cmd; -- cgit v1.2.3