commit 0a2b049864c8dae0c53c203d46fca89e0e66849d
parent de9ab28ab9e55597baf2ca32194ec65b441f0f36
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 29 Feb 2020 16:42:10 +0100
big rename fest related to #6067 API renaming
Diffstat:
39 files changed, 6634 insertions(+), 6453 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#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_refresh_link.h b/src/exchange/taler-exchange-httpd_link.h
diff --git a/src/exchange/taler-exchange-httpd_melt.c 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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_melt.c
+ * @brief Handle /refresh/melt requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#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
@@ -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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#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
@@ -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 <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#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_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c
@@ -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 <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#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
@@ -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 <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#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
@@ -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 <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#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; i<num_newcoins; i++)
- rctx->ev_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; i<TALER_CNC_KAPPA; i++)
- {
- struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
-
- if (i == refresh_melt.session.noreveal_index)
- {
- /* Take these coin envelopes from the client */
- rce->transfer_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; j<rctx->num_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; i<TALER_CNC_KAPPA; i++)
- {
- struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
-
- if (i == refresh_melt.session.noreveal_index)
- continue; /* This offset is special... */
- for (unsigned int j = 0; j<rctx->num_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; i<rctx->num_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; i<rctx->num_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; i<num_fresh_coins; i++)
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL,
- &dki_h[i]),
- GNUNET_JSON_spec_end ()
- };
- unsigned int hc;
- enum TALER_ErrorCode ec;
-
- res = TALER_MHD_parse_json_array (connection,
- new_denoms_h_json,
- spec,
- i,
- -1);
- if (GNUNET_OK != res)
- {
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- }
- dkis[i] = TEH_KS_denomination_key_lookup_by_hash (key_state,
- &dki_h[i],
- TEH_KS_DKU_WITHDRAW,
- &ec,
- &hc);
- if (NULL == dkis[i])
- {
- return TALER_MHD_reply_with_error (connection,
- hc,
- ec,
- "failed to find denomination key");
- }
- GNUNET_assert (NULL != dkis[i]->denom_priv.rsa_private_key);
- }
-
- /* Parse coin envelopes */
- for (unsigned int i = 0; i<num_fresh_coins; i++)
- {
- struct TALER_RefreshCoinData *rcd = &rcds[i];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_varsize (NULL,
- (void **) &rcd->coin_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; j<i; j++)
- GNUNET_free_non_null (rcds[j].coin_ev);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- }
- rcd->dk = &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; i<num_fresh_coins; i++)
- {
- struct GNUNET_JSON_Specification link_spec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL, &link_sigs[i]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_array (connection,
- link_sigs_json,
- link_spec,
- i,
- -1);
- if (GNUNET_OK != res)
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- /* Check link_sigs[i] signature */
- {
- struct TALER_LinkDataPS ldp;
-
- ldp.purpose.size = htonl (sizeof (ldp));
- ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
- ldp.h_denom_pub = dki_h[i];
- ldp.old_coin_pub = refresh_melt.session.coin.coin_pub;
- ldp.transfer_pub = rctx->gamma_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; i<rctx->num_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; i<num_fresh_coins; i++)
- if (NULL != rctx->ev_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<num_fresh_coins; i++)
- GNUNET_free_non_null (rcds[i].coin_ev);
- return res;
-}
-
-
-/**
- * Handle a "/refresh/reveal" request. Parses the given JSON
- * transfer private keys and if successful, passes everything to
- * #resolve_refresh_reveal_denominations() which will verify that the
- * revealed information is valid then returns the signed refreshed
- * coins.
- *
- * @param connection the MHD connection to handle
- * @param rctx context for the operation, partially built at this time
- * @param tp_json private transfer keys in JSON format
- * @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
-handle_refresh_reveal_json (struct MHD_Connection *connection,
- struct RevealContext *rctx,
- const json_t *tp_json,
- 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);
- unsigned int num_tprivs = json_array_size (tp_json);
-
- GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1);
- if ( (num_fresh_coins >= 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; i<num_tprivs; i++)
- {
- struct GNUNET_JSON_Specification trans_spec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL, &rctx->transfer_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.c 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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#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; i<num_newcoins; i++)
+ rctx->ev_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; i<TALER_CNC_KAPPA; i++)
+ {
+ struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
+
+ if (i == melt.session.noreveal_index)
+ {
+ /* Take these coin envelopes from the client */
+ rce->transfer_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; j<rctx->num_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; i<TALER_CNC_KAPPA; i++)
+ {
+ struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
+
+ if (i == melt.session.noreveal_index)
+ continue; /* This offset is special... */
+ for (unsigned int j = 0; j<rctx->num_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; i<rctx->num_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; i<rctx->num_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; i<num_fresh_coins; i++)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &dki_h[i]),
+ GNUNET_JSON_spec_end ()
+ };
+ unsigned int hc;
+ enum TALER_ErrorCode ec;
+
+ res = TALER_MHD_parse_json_array (connection,
+ new_denoms_h_json,
+ spec,
+ i,
+ -1);
+ if (GNUNET_OK != res)
+ {
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ dkis[i] = TEH_KS_denomination_key_lookup_by_hash (key_state,
+ &dki_h[i],
+ TEH_KS_DKU_WITHDRAW,
+ &ec,
+ &hc);
+ if (NULL == dkis[i])
+ {
+ return TALER_MHD_reply_with_error (connection,
+ hc,
+ ec,
+ "failed to find denomination key");
+ }
+ GNUNET_assert (NULL != dkis[i]->denom_priv.rsa_private_key);
+ }
+
+ /* Parse coin envelopes */
+ for (unsigned int i = 0; i<num_fresh_coins; i++)
+ {
+ struct TALER_RefreshCoinData *rcd = &rcds[i];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_varsize (NULL,
+ (void **) &rcd->coin_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; j<i; j++)
+ GNUNET_free_non_null (rcds[j].coin_ev);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ rcd->dk = &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; i<num_fresh_coins; i++)
+ {
+ struct GNUNET_JSON_Specification link_spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, &link_sigs[i]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_array (connection,
+ link_sigs_json,
+ link_spec,
+ i,
+ -1);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ /* Check link_sigs[i] signature */
+ {
+ struct TALER_LinkDataPS ldp;
+
+ ldp.purpose.size = htonl (sizeof (ldp));
+ ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
+ ldp.h_denom_pub = dki_h[i];
+ ldp.old_coin_pub = melt.session.coin.coin_pub;
+ ldp.transfer_pub = rctx->gamma_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; i<rctx->num_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; i<num_fresh_coins; i++)
+ if (NULL != rctx->ev_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<num_fresh_coins; i++)
+ GNUNET_free_non_null (rcds[i].coin_ev);
+ return res;
+}
+
+
+/**
+ * Handle a "/refresh/reveal" request. Parses the given JSON
+ * transfer private keys and if successful, passes everything to
+ * #resolve_refresh_reveal_denominations() which will verify that the
+ * revealed information is valid then returns the signed refreshed
+ * coins.
+ *
+ * @param connection the MHD connection to handle
+ * @param rctx context for the operation, partially built at this time
+ * @param tp_json private transfer keys in JSON format
+ * @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
+handle_refresh_reveal_json (struct MHD_Connection *connection,
+ struct RevealContext *rctx,
+ const json_t *tp_json,
+ 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);
+ unsigned int num_tprivs = json_array_size (tp_json);
+
+ GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1);
+ if ( (num_fresh_coins >= 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; i<num_tprivs; i++)
+ {
+ struct GNUNET_JSON_Specification trans_spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, &rctx->transfer_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_refreshes_reveal.h
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -27,6 +27,422 @@
/**
+ * 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; off<history_length; off++)
+ {
+ json_t *transaction;
+ struct TALER_Amount amount;
+ const char *type;
+ struct GNUNET_JSON_Specification hist_spec[] = {
+ GNUNET_JSON_spec_string ("type", &type),
+ TALER_JSON_spec_amount ("amount",
+ &amount),
+ /* 'wire' and 'signature' are optional depending on 'type'! */
+ GNUNET_JSON_spec_end ()
+ };
+
+ transaction = json_array_get (history,
+ off);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ hist_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].amount = amount;
+
+ if (0 == strcasecmp (type,
+ "DEPOSIT"))
+ {
+ const char *wire_url;
+ void *wire_reference;
+ size_t wire_reference_size;
+ struct GNUNET_TIME_Absolute timestamp;
+
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_varsize ("wire_reference",
+ &wire_reference,
+ &wire_reference_size),
+ GNUNET_JSON_spec_absolute_time ("timestamp",
+ ×tamp),
+ GNUNET_JSON_spec_string ("sender_account_url",
+ &wire_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT;
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_in,
+ &total_in,
+ &amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url);
+ rhistory[off].details.in_details.wire_reference = wire_reference;
+ rhistory[off].details.in_details.wire_reference_size =
+ wire_reference_size;
+ rhistory[off].details.in_details.timestamp = timestamp;
+ /* end type==DEPOSIT */
+ }
+ else if (0 == strcasecmp (type,
+ "WITHDRAW"))
+ {
+ struct TALER_ReserveSignatureP sig;
+ struct TALER_WithdrawRequestPS withdraw_purpose;
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &sig),
+ TALER_JSON_spec_amount_nbo ("withdraw_fee",
+ &withdraw_purpose.withdraw_fee),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &withdraw_purpose.h_denomination_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
+ &withdraw_purpose.h_coin_envelope),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ withdraw_purpose.purpose.size
+ = htonl (sizeof (withdraw_purpose));
+ withdraw_purpose.purpose.purpose
+ = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
+ withdraw_purpose.reserve_pub = *reserve_pub;
+ TALER_amount_hton (&withdraw_purpose.amount_with_fee,
+ &amount);
+ /* Check that the signature is a valid withdraw request */
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
+ &withdraw_purpose.purpose,
+ &sig.eddsa_signature,
+ &reserve_pub->eddsa_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<uuid_off; i++)
+ {
+ if (0 == GNUNET_memcmp (&uuid[uuid_off],
+ &uuid[i]))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ uuid_off++;
+
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_out,
+ &total_out,
+ &amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ /* end type==WITHDRAW */
+ }
+ else if (0 == strcasecmp (type,
+ "RECOUP"))
+ {
+ struct TALER_RecoupConfirmationPS pc;
+ struct GNUNET_TIME_Absolute timestamp;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification recoup_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &pc.coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rhistory[off].details.recoup_details.
+ exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rhistory[off].details.recoup_details.
+ exchange_pub),
+ GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
+ &pc.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_RECOUP;
+ rhistory[off].amount = amount;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ recoup_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].details.recoup_details.coin_pub = pc.coin_pub;
+ TALER_amount_hton (&pc.recoup_amount,
+ &amount);
+ pc.purpose.size = htonl (sizeof (pc));
+ pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP);
+ pc.reserve_pub = *reserve_pub;
+ timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp);
+ rhistory[off].details.recoup_details.timestamp = timestamp;
+
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &rhistory[off].details.
+ recoup_details.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
+ &pc.purpose,
+ &rhistory[off].details.recoup_details.
+ exchange_sig.eddsa_signature,
+ &rhistory[off].details.recoup_details.
+ exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_in,
+ &total_in,
+ &rhistory[off].amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* end type==RECOUP */
+ }
+ else if (0 == strcasecmp (type,
+ "CLOSING"))
+ {
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct TALER_ReserveCloseConfirmationPS rcc;
+ struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_JSON_Specification closing_spec[] = {
+ GNUNET_JSON_spec_string ("receiver_account_details",
+ &rhistory[off].details.close_details.
+ receiver_account_details),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &rhistory[off].details.close_details.wtid),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rhistory[off].details.close_details.
+ exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rhistory[off].details.close_details.
+ exchange_pub),
+ TALER_JSON_spec_amount_nbo ("closing_fee",
+ &rcc.closing_fee),
+ GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
+ &rcc.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE;
+ rhistory[off].amount = amount;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ closing_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_amount_hton (&rcc.closing_amount,
+ &amount);
+ GNUNET_CRYPTO_hash (
+ rhistory[off].details.close_details.receiver_account_details,
+ strlen (
+ rhistory[off].details.close_details.receiver_account_details) + 1,
+ &rcc.h_wire);
+ rcc.wtid = rhistory[off].details.close_details.wtid;
+ rcc.purpose.size = htonl (sizeof (rcc));
+ rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED);
+ rcc.reserve_pub = *reserve_pub;
+ timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp);
+ rhistory[off].details.close_details.timestamp = timestamp;
+
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &rhistory[off].details.close_details.
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
+ &rcc.purpose,
+ &rhistory[off].details.close_details.
+ exchange_sig.eddsa_signature,
+ &rhistory[off].details.close_details.
+ exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_out,
+ &total_out,
+ &rhistory[off].amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* end type==CLOSING */
+ }
+ else
+ {
+ /* unexpected 'type', protocol incompatibility, complain! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* check balance = total_in - total_out < withdraw-amount */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (balance,
+ &total_in,
+ &total_out))
+ {
+ /* total_in < total_out, why did the exchange ever allow this!? */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * 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)
+{
+ for (unsigned int i = 0; i<len; i++)
+ {
+ switch (rhistory[i].type)
+ {
+ case TALER_EXCHANGE_RTT_DEPOSIT:
+ GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference);
+ GNUNET_free_non_null (rhistory[i].details.in_details.sender_url);
+ break;
+ case TALER_EXCHANGE_RTT_WITHDRAWAL:
+ break;
+ case TALER_EXCHANGE_RTT_RECOUP:
+ break;
+ case TALER_EXCHANGE_RTT_CLOSE:
+ /* FIXME: should we free "receiver_account_details" ? */
+ break;
+ }
+ }
+ GNUNET_free (rhistory);
+}
+
+
+/**
* Verify a coins transaction history as returned by the exchange.
*
* @param dk fee structure for the coin, NULL to skip verifying fees
diff --git a/src/lib/exchange_api_deposits_get.c b/src/lib/exchange_api_deposits_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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_deposits_get.c
+ * @brief Implementation of the /deposits/ GET request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#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
@@ -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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_link.c
+ * @brief Implementation of the /coins/$COIN_PUB/link request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#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; session<json_array_size (json); session++)
+ {
+ json_t *jsona;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("new_coins", &jsona),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (json,
+ session),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (! json_is_array (jsona))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+
+ /* count all coins over all sessions */
+ num_coins += json_array_size (jsona);
+ GNUNET_JSON_parse_free (spec);
+ }
+ /* Now that we know how big the 1d array is, allocate
+ and fill it. */
+ {
+ unsigned int off_coin; /* index into 1d array */
+ unsigned int i;
+ struct TALER_CoinSpendPrivateKeyP coin_privs[GNUNET_NZL (num_coins)];
+ struct TALER_DenominationSignature sigs[GNUNET_NZL (num_coins)];
+ struct TALER_DenominationPublicKey pubs[GNUNET_NZL (num_coins)];
+
+ memset (sigs, 0, sizeof (sigs));
+ memset (pubs, 0, sizeof (pubs));
+ off_coin = 0;
+ for (session = 0; session<json_array_size (json); session++)
+ {
+ json_t *jsona;
+ struct TALER_TransferPublicKeyP trans_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("new_coins",
+ &jsona),
+ GNUNET_JSON_spec_fixed_auto ("transfer_pub",
+ &trans_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (json,
+ session),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (! json_is_array (jsona))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+
+ /* decode all coins */
+ for (i = 0; i<json_array_size (jsona); i++)
+ {
+ GNUNET_assert (i + off_coin < num_coins);
+ if (GNUNET_OK !=
+ parse_link_coin (lh,
+ json_array_get (jsona,
+ i),
+ i,
+ &trans_pub,
+ &coin_privs[i + off_coin],
+ &sigs[i + off_coin],
+ &pubs[i + off_coin]))
+ {
+ GNUNET_break_op (0);
+ break;
+ }
+ }
+ /* check if we really got all, then invoke callback */
+ off_coin += i;
+ if (i != json_array_size (jsona))
+ {
+ GNUNET_break_op (0);
+ ret = GNUNET_SYSERR;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ GNUNET_JSON_parse_free (spec);
+ } /* end of for (session) */
+
+ if (off_coin == num_coins)
+ {
+ lh->link_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; i<off_coin; i++)
+ {
+ if (NULL != sigs[i].rsa_signature)
+ GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
+ if (NULL != pubs[i].rsa_public_key)
+ GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key);
+ }
+ }
+ return ret;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /coins/$COIN_PUB/link request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_LinkHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_link_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_LinkHandle *lh = cls;
+ const json_t *j = response;
+
+ lh->job = 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
@@ -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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_melt.c
+ * @brief Implementation of the /coins/$COIN_PUB/melt request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#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
@@ -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
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#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; i<md->num_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; i<TALER_CNC_KAPPA; i++)
- GNUNET_free_non_null (md->fresh_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; i<TALER_CNC_KAPPA; i++)
- mcp.transfer_priv[i] = mc->transfer_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; i<TALER_CNC_KAPPA; i++)
- mc->transfer_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; i<md->num_fresh_coins; i++)
- size += serialize_denomination_key (&md->fresh_pks[i],
- buf,
- size);
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- for (unsigned int j = 0; j<md->num_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; i<TALER_CNC_KAPPA; i++)
- md->fresh_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; (i<md->num_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; i<TALER_CNC_KAPPA; i++)
- for (unsigned int j = 0; (j<md->num_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; i<fresh_pks_len; i++)
- {
- md.fresh_pks[i].rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key);
- if ( (GNUNET_OK !=
- TALER_amount_add (&total,
- &total,
- &fresh_pks[i].value)) ||
- (GNUNET_OK !=
- TALER_amount_add (&total,
- &total,
- &fresh_pks[i].fee_withdraw)) )
- {
- GNUNET_break (0);
- free_melt_data (&md);
- return NULL;
- }
- }
- /* verify that melt_amount is above total cost */
- if (1 ==
- TALER_amount_cmp (&total,
- melt_amount) )
- {
- /* Eh, this operation is more expensive than the
- @a melt_amount. This is not OK. */
- GNUNET_break (0);
- free_melt_data (&md);
- return NULL;
- }
-
- /* build up coins */
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- {
- struct GNUNET_CRYPTO_EcdhePrivateKey *tpk;
-
- tpk = GNUNET_CRYPTO_ecdhe_key_create ();
- md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk;
- GNUNET_free (tpk);
-
- GNUNET_CRYPTO_ecdhe_key_get_public (
- &md.melted_coin.transfer_priv[i].ecdhe_priv,
- &rce[i].transfer_pub.ecdhe_pub);
- TALER_link_derive_transfer_secret (melt_priv,
- &md.melted_coin.transfer_priv[i],
- &trans_sec[i]);
- md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len,
- struct TALER_PlanchetSecretsP);
- rce[i].new_coins = GNUNET_new_array (fresh_pks_len,
- struct TALER_RefreshCoinData);
- for (unsigned int j = 0; j<fresh_pks_len; j++)
- {
- struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j];
- struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j];
- struct TALER_PlanchetDetail pd;
-
- TALER_planchet_setup_refresh (&trans_sec[i],
- j,
- fc);
- if (GNUNET_OK !=
- TALER_planchet_prepare (&md.fresh_pks[j],
- fc,
- &pd))
- {
- GNUNET_break_op (0);
- free_melt_data (&md);
- return NULL;
- }
- rcd->dk = &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; i<rrh->md->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; i<rrh->md->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; i<md->num_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; j<TALER_CNC_KAPPA; j++)
- {
- if (j == noreveal_index)
- {
- /* This is crucial: exclude the transfer key for the
- noreval index! */
- continue;
- }
- GNUNET_assert (0 ==
- json_array_append_new (transfer_privs,
- GNUNET_JSON_from_data_auto (
- &md->melted_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
@@ -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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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; i<md->num_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; i<TALER_CNC_KAPPA; i++)
+ GNUNET_free_non_null (md->fresh_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; i<TALER_CNC_KAPPA; i++)
+ mcp.transfer_priv[i] = mc->transfer_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; i<TALER_CNC_KAPPA; i++)
+ mc->transfer_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; i<md->num_fresh_coins; i++)
+ size += serialize_denomination_key (&md->fresh_pks[i],
+ buf,
+ size);
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ for (unsigned int j = 0; j<md->num_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; i<TALER_CNC_KAPPA; i++)
+ md->fresh_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; (i<md->num_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; i<TALER_CNC_KAPPA; i++)
+ for (unsigned int j = 0; (j<md->num_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; i<fresh_pks_len; i++)
+ {
+ md.fresh_pks[i].rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key);
+ if ( (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &fresh_pks[i].value)) ||
+ (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &fresh_pks[i].fee_withdraw)) )
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ return NULL;
+ }
+ }
+ /* verify that melt_amount is above total cost */
+ if (1 ==
+ TALER_amount_cmp (&total,
+ melt_amount) )
+ {
+ /* Eh, this operation is more expensive than the
+ @a melt_amount. This is not OK. */
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ return NULL;
+ }
+
+ /* build up coins */
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ {
+ struct GNUNET_CRYPTO_EcdhePrivateKey *tpk;
+
+ tpk = GNUNET_CRYPTO_ecdhe_key_create ();
+ md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk;
+ GNUNET_free (tpk);
+
+ GNUNET_CRYPTO_ecdhe_key_get_public (
+ &md.melted_coin.transfer_priv[i].ecdhe_priv,
+ &rce[i].transfer_pub.ecdhe_pub);
+ TALER_link_derive_transfer_secret (melt_priv,
+ &md.melted_coin.transfer_priv[i],
+ &trans_sec[i]);
+ md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len,
+ struct TALER_PlanchetSecretsP);
+ rce[i].new_coins = GNUNET_new_array (fresh_pks_len,
+ struct TALER_RefreshCoinData);
+ for (unsigned int j = 0; j<fresh_pks_len; j++)
+ {
+ struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j];
+ struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j];
+ struct TALER_PlanchetDetail pd;
+
+ TALER_planchet_setup_refresh (&trans_sec[i],
+ j,
+ fc);
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&md.fresh_pks[j],
+ fc,
+ &pd))
+ {
+ GNUNET_break_op (0);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ return NULL;
+ }
+ rcd->dk = &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
@@ -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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <jansson.h>
+#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
@@ -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
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#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; session<json_array_size (json); session++)
- {
- json_t *jsona;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("new_coins", &jsona),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (json,
- session),
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (! json_is_array (jsona))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
-
- /* count all coins over all sessions */
- num_coins += json_array_size (jsona);
- GNUNET_JSON_parse_free (spec);
- }
- /* Now that we know how big the 1d array is, allocate
- and fill it. */
- {
- unsigned int off_coin; /* index into 1d array */
- unsigned int i;
- struct TALER_CoinSpendPrivateKeyP coin_privs[GNUNET_NZL (num_coins)];
- struct TALER_DenominationSignature sigs[GNUNET_NZL (num_coins)];
- struct TALER_DenominationPublicKey pubs[GNUNET_NZL (num_coins)];
-
- memset (sigs, 0, sizeof (sigs));
- memset (pubs, 0, sizeof (pubs));
- off_coin = 0;
- for (session = 0; session<json_array_size (json); session++)
- {
- json_t *jsona;
- struct TALER_TransferPublicKeyP trans_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("new_coins",
- &jsona),
- GNUNET_JSON_spec_fixed_auto ("transfer_pub",
- &trans_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (json,
- session),
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (! json_is_array (jsona))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
-
- /* decode all coins */
- for (i = 0; i<json_array_size (jsona); i++)
- {
- GNUNET_assert (i + off_coin < num_coins);
- if (GNUNET_OK !=
- parse_refresh_link_coin (rlh,
- json_array_get (jsona,
- i),
- i,
- &trans_pub,
- &coin_privs[i + off_coin],
- &sigs[i + off_coin],
- &pubs[i + off_coin]))
- {
- GNUNET_break_op (0);
- break;
- }
- }
- /* check if we really got all, then invoke callback */
- off_coin += i;
- if (i != json_array_size (jsona))
- {
- GNUNET_break_op (0);
- ret = GNUNET_SYSERR;
- GNUNET_JSON_parse_free (spec);
- break;
- }
- GNUNET_JSON_parse_free (spec);
- } /* end of for (session) */
-
- if (off_coin == num_coins)
- {
- rlh->link_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; i<off_coin; i++)
- {
- if (NULL != sigs[i].rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
- if (NULL != pubs[i].rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key);
- }
- }
- return ret;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /refresh/link request.
- *
- * @param cls the `struct TALER_EXCHANGE_RefreshLinkHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_refresh_link_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_RefreshLinkHandle *rlh = cls;
- const json_t *j = response;
-
- rlh->job = 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
@@ -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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_refreshes_reveal.c
+ * @brief Implementation of the /refreshes/$RCH/reveal requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#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; i<rrh->md->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; i<rrh->md->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; i<md->num_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; j<TALER_CNC_KAPPA; j++)
+ {
+ if (j == noreveal_index)
+ {
+ /* This is crucial: exclude the transfer key for the
+ noreval index! */
+ continue;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (transfer_privs,
+ GNUNET_JSON_from_data_auto (
+ &md->melted_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
@@ -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
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#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; off<history_length; off++)
- {
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification hist_spec[] = {
- GNUNET_JSON_spec_string ("type", &type),
- TALER_JSON_spec_amount ("amount",
- &amount),
- /* 'wire' and 'signature' are optional depending on 'type'! */
- GNUNET_JSON_spec_end ()
- };
-
- transaction = json_array_get (history,
- off);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- hist_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rhistory[off].amount = amount;
-
- if (0 == strcasecmp (type,
- "DEPOSIT"))
- {
- const char *wire_url;
- void *wire_reference;
- size_t wire_reference_size;
- struct GNUNET_TIME_Absolute timestamp;
-
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_varsize ("wire_reference",
- &wire_reference,
- &wire_reference_size),
- GNUNET_JSON_spec_absolute_time ("timestamp",
- ×tamp),
- GNUNET_JSON_spec_string ("sender_account_url",
- &wire_url),
- GNUNET_JSON_spec_end ()
- };
-
- rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT;
- if (GNUNET_OK !=
- TALER_amount_add (&total_in,
- &total_in,
- &amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- withdraw_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url);
- rhistory[off].details.in_details.wire_reference = wire_reference;
- rhistory[off].details.in_details.wire_reference_size =
- wire_reference_size;
- rhistory[off].details.in_details.timestamp = timestamp;
- /* end type==DEPOSIT */
- }
- else if (0 == strcasecmp (type,
- "WITHDRAW"))
- {
- struct TALER_ReserveSignatureP sig;
- struct TALER_WithdrawRequestPS withdraw_purpose;
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &sig),
- TALER_JSON_spec_amount_nbo ("withdraw_fee",
- &withdraw_purpose.withdraw_fee),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &withdraw_purpose.h_denomination_pub),
- GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
- &withdraw_purpose.h_coin_envelope),
- GNUNET_JSON_spec_end ()
- };
-
- rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- withdraw_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- withdraw_purpose.purpose.size
- = htonl (sizeof (withdraw_purpose));
- withdraw_purpose.purpose.purpose
- = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
- withdraw_purpose.reserve_pub = *reserve_pub;
- TALER_amount_hton (&withdraw_purpose.amount_with_fee,
- &amount);
- /* Check that the signature is a valid withdraw request */
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
- &withdraw_purpose.purpose,
- &sig.eddsa_signature,
- &reserve_pub->eddsa_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<uuid_off; i++)
- {
- if (0 == GNUNET_memcmp (&uuid[uuid_off],
- &uuid[i]))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- }
- uuid_off++;
-
- if (GNUNET_OK !=
- TALER_amount_add (&total_out,
- &total_out,
- &amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- /* end type==WITHDRAW */
- }
- else if (0 == strcasecmp (type,
- "RECOUP"))
- {
- struct TALER_RecoupConfirmationPS pc;
- struct GNUNET_TIME_Absolute timestamp;
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification recoup_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &pc.coin_pub),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rhistory[off].details.recoup_details.
- exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rhistory[off].details.recoup_details.
- exchange_pub),
- GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
- &pc.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- rhistory[off].type = TALER_EXCHANGE_RTT_RECOUP;
- rhistory[off].amount = amount;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- recoup_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rhistory[off].details.recoup_details.coin_pub = pc.coin_pub;
- TALER_amount_hton (&pc.recoup_amount,
- &amount);
- pc.purpose.size = htonl (sizeof (pc));
- pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP);
- pc.reserve_pub = *reserve_pub;
- timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp);
- rhistory[off].details.recoup_details.timestamp = timestamp;
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &rhistory[off].details.
- recoup_details.exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
- &pc.purpose,
- &rhistory[off].details.recoup_details.
- exchange_sig.eddsa_signature,
- &rhistory[off].details.recoup_details.
- exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&total_in,
- &total_in,
- &rhistory[off].amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* end type==RECOUP */
- }
- else if (0 == strcasecmp (type,
- "CLOSING"))
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- struct TALER_ReserveCloseConfirmationPS rcc;
- struct GNUNET_TIME_Absolute timestamp;
- struct GNUNET_JSON_Specification closing_spec[] = {
- GNUNET_JSON_spec_string ("receiver_account_details",
- &rhistory[off].details.close_details.
- receiver_account_details),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &rhistory[off].details.close_details.wtid),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rhistory[off].details.close_details.
- exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rhistory[off].details.close_details.
- exchange_pub),
- TALER_JSON_spec_amount_nbo ("closing_fee",
- &rcc.closing_fee),
- GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
- &rcc.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE;
- rhistory[off].amount = amount;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- closing_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- TALER_amount_hton (&rcc.closing_amount,
- &amount);
- GNUNET_CRYPTO_hash (
- rhistory[off].details.close_details.receiver_account_details,
- strlen (
- rhistory[off].details.close_details.receiver_account_details) + 1,
- &rcc.h_wire);
- rcc.wtid = rhistory[off].details.close_details.wtid;
- rcc.purpose.size = htonl (sizeof (rcc));
- rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED);
- rcc.reserve_pub = *reserve_pub;
- timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp);
- rhistory[off].details.close_details.timestamp = timestamp;
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &rhistory[off].details.close_details.
- exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
- &rcc.purpose,
- &rhistory[off].details.close_details.
- exchange_sig.eddsa_signature,
- &rhistory[off].details.close_details.
- exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&total_out,
- &total_out,
- &rhistory[off].amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* end type==CLOSING */
- }
- else
- {
- /* unexpected 'type', protocol incompatibility, complain! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
-
- /* check balance = total_in - total_out < withdraw-amount */
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (balance,
- &total_in,
- &total_out))
- {
- /* total_in < total_out, why did the exchange ever allow this!? */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Free memory (potentially) allocated by #parse_reserve_history().
- *
- * @param rhistory result to free
- * @param len number of entries in @a rhistory
- */
-static void
-free_rhistory (struct TALER_EXCHANGE_ReserveHistory *rhistory,
- unsigned int len)
-{
- for (unsigned int i = 0; i<len; i++)
- {
- switch (rhistory[i].type)
- {
- case TALER_EXCHANGE_RTT_DEPOSIT:
- GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference);
- GNUNET_free_non_null (rhistory[i].details.in_details.sender_url);
- break;
- case TALER_EXCHANGE_RTT_WITHDRAWAL:
- break;
- case TALER_EXCHANGE_RTT_RECOUP:
- break;
- case TALER_EXCHANGE_RTT_CLOSE:
- // should we free "receiver_account_details" ?
- break;
- }
- }
- GNUNET_free (rhistory);
-}
-
-
-/**
- * We received an #MHD_HTTP_OK status code. Handle the JSON
- * response.
- *
- * @param rsh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static int
-handle_reserve_status_ok (struct TALER_EXCHANGE_ReservesGetHandle *rsh,
- 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 !=
- parse_reserve_history (rsh->exchange,
- 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
@@ -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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_reserves_get.c
+ * @brief Implementation of the GET /reserves/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#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
@@ -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
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#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
@@ -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
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @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 <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#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; i<num_details; i++)
- {
- struct TALER_TrackTransferDetails *detail = &details[i];
- struct json_t *detail_j = json_array_get (details_j, i);
- struct GNUNET_JSON_Specification spec_detail[] = {
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &detail->h_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
@@ -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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_transfers_get.c
+ * @brief Implementation of the GET /transfers/ request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#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; i<num_details; i++)
+ {
+ struct TALER_TrackTransferDetails *detail = &details[i];
+ struct json_t *detail_j = json_array_get (details_j, i);
+ struct GNUNET_JSON_Specification spec_detail[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &detail->h_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
@@ -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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_withdraw.c
+ * @brief Implementation of the /reserves/$RESERVE_PUB/withdraw requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;