From 085e40bc562343221bceb6fc4dc9aba8e32a27e3 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 1 Jul 2022 07:08:13 +0200 Subject: [PATCH] -exchange_api_batch_deposit.c compiles --- contrib/gana | 2 +- doc/prebuilt | 2 +- src/auditor/taler-helper-auditor-reserves.c | 4 +- .../taler-exchange-httpd_common_deposit.c | 6 +- src/exchange/taler-exchange-httpd_keys.c | 4 +- .../taler-exchange-httpd_purses_create.c | 4 +- src/include/taler_exchange_service.h | 138 ++++ src/lib/Makefile.am | 1 + src/lib/exchange_api_batch_deposit.c | 647 ++++++++++++++++++ src/lib/exchange_api_common.c | 62 ++ src/lib/exchange_api_common.h | 20 + src/lib/exchange_api_deposit.c | 81 +-- src/lib/exchange_api_handle.c | 2 +- .../exchange_api_purse_create_with_deposit.c | 4 +- .../exchange_api_purse_create_with_merge.c | 4 +- 15 files changed, 889 insertions(+), 92 deletions(-) create mode 100644 src/lib/exchange_api_batch_deposit.c diff --git a/contrib/gana b/contrib/gana index ce57f1bb3..75c838e74 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit ce57f1bb32a657c0e479a13401339c9899b1c898 +Subproject commit 75c838e74c41bf9a6c02cdfe8109a444056bf26d diff --git a/doc/prebuilt b/doc/prebuilt index 1ed97b23f..74d9c44eb 160000 --- a/doc/prebuilt +++ b/doc/prebuilt @@ -1 +1 @@ -Subproject commit 1ed97b23f19c80fa84b21a5eb0c686d5491e8ec6 +Subproject commit 74d9c44ebc257a3d8b9c2c0a806508bd0cc5269a diff --git a/src/auditor/taler-helper-auditor-reserves.c b/src/auditor/taler-helper-auditor-reserves.c index 54d3db7c3..c6c4d3ad3 100644 --- a/src/auditor/taler-helper-auditor-reserves.c +++ b/src/auditor/taler-helper-auditor-reserves.c @@ -1283,8 +1283,8 @@ handle_purse_deposits ( struct ReserveContext *rc = cls; const char *base_url = (NULL == deposit->exchange_base_url) - ? TALER_ARL_exchange_url - : deposit->exchange_base_url; + ? TALER_ARL_exchange_url + : deposit->exchange_base_url; enum GNUNET_DB_QueryStatus qs; struct TALER_Amount amount_minus_fee; struct TALER_Amount new_balance; diff --git a/src/exchange/taler-exchange-httpd_common_deposit.c b/src/exchange/taler-exchange-httpd_common_deposit.c index cfa15fccb..694dfa411 100644 --- a/src/exchange/taler-exchange-httpd_common_deposit.c +++ b/src/exchange/taler-exchange-httpd_common_deposit.c @@ -214,8 +214,8 @@ TEH_common_deposit_check_purse_deposit ( MHD_HTTP_FORBIDDEN, TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID, TEH_base_url)) - ? GNUNET_NO - : GNUNET_SYSERR; + ? GNUNET_NO + : GNUNET_SYSERR; } /* Check and verify the age restriction. */ @@ -301,7 +301,7 @@ if (0 > MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_COMMIT_FAILED, "make_coin_known")) - ? GNUNET_NO : GNUNET_SYSERR; + ? GNUNET_NO : GNUNET_SYSERR; } if (qs < 0) return (MHD_YES == mhd_ret) ? GNUNET_NO : GNUNET_SYSERR; diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 65fe0f315..b1fa2cbc4 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -2216,10 +2216,10 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) switch (meta.cipher) { case TALER_DENOMINATION_RSA: - cipher = age_restricted ? "RSA+age_restricted": "RSA"; + cipher = age_restricted ? "RSA+age_restricted" : "RSA"; break; case TALER_DENOMINATION_CS: - cipher = age_restricted ? "CS+age_restricted": "CS"; + cipher = age_restricted ? "CS+age_restricted" : "CS"; break; default: GNUNET_assert (false); diff --git a/src/exchange/taler-exchange-httpd_purses_create.c b/src/exchange/taler-exchange-httpd_purses_create.c index b6eea05fa..9cabdb1a1 100644 --- a/src/exchange/taler-exchange-httpd_purses_create.c +++ b/src/exchange/taler-exchange-httpd_purses_create.c @@ -458,8 +458,8 @@ parse_coin (struct MHD_Connection *connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, "total deposit contribution")) - ? GNUNET_NO - : GNUNET_SYSERR; + ? GNUNET_NO + : GNUNET_SYSERR; } return GNUNET_OK; } diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index bd2a5bcaa..6c3ad7a01 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -1047,6 +1047,144 @@ void TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit); +/** + * @brief A Batch Deposit Handle + */ +struct TALER_EXCHANGE_BatchDepositHandle; + + +/** + * Structure with information about a batch deposit + * operation's result. + */ +struct TALER_EXCHANGE_BatchDepositResult +{ + /** + * HTTP response data + */ + struct TALER_EXCHANGE_HttpResponse hr; + + union + { + + /** + * Information returned if the HTTP status is + * #MHD_HTTP_OK. + */ + struct + { + /** + * Time when the exchange generated the batch deposit confirmation + */ + struct GNUNET_TIME_Timestamp deposit_timestamp; + + /** + * Array of signatures provided by the exchange + */ + const struct TALER_ExchangeSignatureP *exchange_sigs; + + /** + * exchange key used to sign @a exchange_sig. + */ + const struct TALER_ExchangePublicKeyP *exchange_pub; + + /** + * Base URL for looking up wire transfers, or + * NULL to use the default base URL. + */ + const char *transaction_base_url; + + /** + * Length of the @e exchange_sigs array. + */ + unsigned int num_signatures; + + } success; + + /** + * Information returned if the HTTP status is + * #MHD_HTTP_CONFLICT. + */ + struct + { + /* TODO: returning full details is not implemented */ + } conflict; + + } details; +}; + + +/** + * Callbacks of this type are used to serve the result of submitting a + * deposit permission request to a exchange. + * + * @param cls closure + * @param dr deposit response details + */ +typedef void +(*TALER_EXCHANGE_BatchDepositResultCallback) ( + void *cls, + const struct TALER_EXCHANGE_BatchDepositResult *dr); + + +/** + * Submit a batch of deposit permissions to the exchange and get the + * exchange's response. This API is typically used by a merchant. Note that + * while we return the response verbatim 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). If the exchange's reply is not + * well-formed, we return an HTTP status code of zero to @a cb. + * + * We also verify that the @a cdds.coin_sig are valid for this deposit + * request, and that the @a cdds.ub_sig are a valid signatures for @a + * coin_pub. Also, the @a exchange must be ready to operate (i.e. have + * finished processing the /keys reply). If either check fails, we do + * NOT initiate the transaction with the exchange and instead return NULL. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param dcd details about the contract the deposit is for + * @param num_cdds length of the @a cdds array + * @param cdds array with details about the coins to be deposited + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @param[out] ec if NULL is returned, set to the error code explaining why the operation failed + * @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_BatchDepositHandle * +TALER_EXCHANGE_batch_deposit ( + struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DepositContractDetail *dcd, + unsigned int num_cdds, + const struct TALER_EXCHANGE_CoinDepositDetail *cdds, + TALER_EXCHANGE_BatchDepositResultCallback cb, + void *cb_cls, + enum TALER_ErrorCode *ec); + + +/** + * Change the chance that our deposit confirmation will be given to the + * auditor to 100%. + * + * @param deposit the batch deposit permission request handle + */ +void +TALER_EXCHANGE_batch_deposit_force_dc (struct + TALER_EXCHANGE_BatchDepositHandle * + deposit); + + +/** + * Cancel a batch deposit permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param deposit the deposit permission request handle + */ +void +TALER_EXCHANGE_batch_deposit_cancel (struct + TALER_EXCHANGE_BatchDepositHandle *deposit); + + /* ********************* /coins/$COIN_PUB/refund *********************** */ /** diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 844ca52fc..76157c43a 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -22,6 +22,7 @@ libtalerexchange_la_LDFLAGS = \ -no-undefined libtalerexchange_la_SOURCES = \ exchange_api_auditor_add_denomination.c \ + exchange_api_batch_deposit.c \ exchange_api_batch_withdraw.c \ exchange_api_batch_withdraw2.c \ exchange_api_curl_defaults.c exchange_api_curl_defaults.h \ diff --git a/src/lib/exchange_api_batch_deposit.c b/src/lib/exchange_api_batch_deposit.c new file mode 100644 index 000000000..be77f682b --- /dev/null +++ b/src/lib/exchange_api_batch_deposit.c @@ -0,0 +1,647 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + + */ +/** + * @file lib/exchange_api_batch_deposit.c + * @brief Implementation of the /batch-deposit request of the exchange's HTTP API + * @author Sree Harsha Totakura + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include /* just for HTTP status codes */ +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_auditor_service.h" +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * 1:#AUDITOR_CHANCE is the probability that we report deposits + * to the auditor. + * + * 20==5% of going to auditor. This is possibly still too high, but set + * deliberately this high for testing + */ +#define AUDITOR_CHANCE 20 + +/** + * @brief A Deposit Handle + */ +struct TALER_EXCHANGE_BatchDepositHandle +{ + + /** + * 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_BatchDepositResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Details about the contract. + */ + struct TALER_EXCHANGE_DepositContractDetail dcd; + + /** + * Array with details about the coins. + */ + struct TALER_EXCHANGE_CoinDepositDetail *cdds; + + /** + * Hash of the merchant's wire details. + */ + struct TALER_MerchantWireHashP h_wire; + + /** + * Hash over the extensions, or all zero. + */ + struct TALER_ExtensionContractHashP h_extensions; + + /** + * Time when this confirmation was generated / when the exchange received + * the deposit request. + */ + struct GNUNET_TIME_Timestamp exchange_timestamp; + + /** + * Exchange signatures, set for #auditor_cb. + */ + struct TALER_ExchangeSignatureP *exchange_sigs; + + /** + * Exchange signing public key, set for #auditor_cb. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * Chance that we will inform the auditor about the deposit + * is 1:n, where the value of this field is "n". + */ + unsigned int auditor_chance; + + /** + * Length of the @e cdds array. + */ + unsigned int num_cdds; + +}; + + +/** + * Function called for each auditor to give us a chance to possibly + * launch a deposit confirmation interaction. + * + * @param cls closure + * @param ah handle to the auditor + * @param auditor_pub public key of the auditor + * @return NULL if no deposit confirmation interaction was launched + */ +static struct TEAH_AuditorInteractionEntry * +auditor_cb (void *cls, + struct TALER_AUDITOR_Handle *ah, + const struct TALER_AuditorPublicKeyP *auditor_pub) +{ + struct TALER_EXCHANGE_BatchDepositHandle *dh = cls; + const struct TALER_EXCHANGE_Keys *key_state; + const struct TALER_EXCHANGE_SigningPublicKey *spk; + struct TEAH_AuditorInteractionEntry *aie; + struct TALER_Amount amount_without_fee; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + unsigned int coin; + + if (0 != + GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + dh->auditor_chance)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Not providing deposit confirmation to auditor\n"); + return NULL; + } + coin = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + dh->num_cdds); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Will provide deposit confirmation to auditor `%s'\n", + TALER_B2S (auditor_pub)); + key_state = TALER_EXCHANGE_get_keys (dh->exchange); + dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, + &dh->cdds[coin].h_denom_pub); + GNUNET_assert (NULL != dki); + spk = TALER_EXCHANGE_get_signing_key_info (key_state, + &dh->exchange_pub); + if (NULL == spk) + { + GNUNET_break_op (0); + return NULL; + } + GNUNET_assert (0 <= + TALER_amount_subtract (&amount_without_fee, + &dh->cdds[coin].amount, + &dki->fees.deposit)); + aie = GNUNET_new (struct TEAH_AuditorInteractionEntry); + aie->dch = TALER_AUDITOR_deposit_confirmation ( + ah, + &dh->h_wire, + &dh->h_extensions, + &dh->dcd.h_contract_terms, + dh->exchange_timestamp, + dh->dcd.wire_deadline, + dh->dcd.refund_deadline, + &amount_without_fee, + &dh->cdds[coin].coin_pub, + &dh->dcd.merchant_pub, + &dh->exchange_pub, + &dh->exchange_sigs[coin], + &key_state->master_pub, + spk->valid_from, + spk->valid_until, + spk->valid_legal, + &spk->master_sig, + &TEAH_acc_confirmation_cb, + aie); + return aie; +} + + +/** + * Function called when we're done processing the + * HTTP /deposit request. + * + * @param cls the `struct TALER_EXCHANGE_BatchDepositHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_deposit_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_BatchDepositHandle *dh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_BatchDepositResult dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + const struct TALER_EXCHANGE_Keys *keys; + + dh->job = NULL; + keys = TALER_EXCHANGE_get_keys (dh->exchange); + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + const struct TALER_EXCHANGE_Keys *key_state; + json_t *sigs; + json_t *sig; + unsigned int idx; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("exchange_sigs", + &sigs), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &dh->exchange_pub), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("transaction_base_url", + &dr.details.success.transaction_base_url), + NULL), + GNUNET_JSON_spec_timestamp ("exchange_timestamp", + &dh->exchange_timestamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (json_array_size (sigs) != dh->num_cdds) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + dh->exchange_sigs = GNUNET_new_array (dh->num_cdds, + struct TALER_ExchangeSignatureP); + key_state = TALER_EXCHANGE_get_keys (dh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &dh->exchange_pub)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + json_array_foreach (sigs, idx, sig) + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &dh->exchange_sigs[idx]), + GNUNET_JSON_spec_end () + }; + struct TALER_Amount amount_without_fee; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + + if (GNUNET_OK != + GNUNET_JSON_parse (sig, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + GNUNET_JSON_parse_free (spec); + break; + } + dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, + &dh->cdds[idx]. + h_denom_pub); + GNUNET_assert (NULL != dki); + GNUNET_assert (0 <= + TALER_amount_subtract (&amount_without_fee, + &dh->cdds[idx].amount, + &dki->fees.deposit)); + + if (GNUNET_OK != + TALER_exchange_online_deposit_confirmation_verify ( + &dh->dcd.h_contract_terms, + &dh->h_wire, + &dh->h_extensions, + dh->exchange_timestamp, + dh->dcd.wire_deadline, + dh->dcd.refund_deadline, + &amount_without_fee, + &dh->cdds[idx].coin_pub, + &dh->dcd.merchant_pub, + &dh->exchange_pub, + &dh->exchange_sigs[idx])) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + } + TEAH_get_auditors_for_dc (dh->exchange, + &auditor_cb, + dh); + } + dr.details.success.exchange_sigs = dh->exchange_sigs; + dr.details.success.exchange_pub = &dh->exchange_pub; + dr.details.success.deposit_timestamp = dh->exchange_timestamp; + dr.details.success.num_signatures = dh->num_cdds; + 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 */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* 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: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + { + const struct TALER_EXCHANGE_Keys *key_state; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &coin_pub), + GNUNET_JSON_spec_end () + }; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + bool found = false; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + for (unsigned int i = 0; inum_cdds; i++) + { + if (0 != + GNUNET_memcmp (&coin_pub, + &dh->cdds[i].coin_pub)) + continue; + key_state = TALER_EXCHANGE_get_keys (dh->exchange); + dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, + &dh->cdds[i]. + h_denom_pub); + GNUNET_assert (NULL != dki); + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_conflict_ ( + keys, + j, + dki, + &dh->cdds[i].coin_pub, + &dh->cdds[i].coin_sig, + &dh->cdds[i].amount)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + found = true; + break; + } + if (! found) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + } + break; + case MHD_HTTP_GONE: + /* could happen if denomination was revoked */ + /* Note: one might want to check /keys for revocation + signature here, alas tricky in case our /keys + is outdated => left to clients */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange deposit\n", + (unsigned int) response_code, + dr.hr.ec); + GNUNET_break_op (0); + break; + } + dh->cb (dh->cb_cls, + &dr); + TALER_EXCHANGE_batch_deposit_cancel (dh); +} + + +struct TALER_EXCHANGE_BatchDepositHandle * +TALER_EXCHANGE_batch_deposit ( + struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DepositContractDetail *dcd, + unsigned int num_cdds, + const struct TALER_EXCHANGE_CoinDepositDetail *cdds, + TALER_EXCHANGE_BatchDepositResultCallback cb, + void *cb_cls, + enum TALER_ErrorCode *ec) +{ + const struct TALER_EXCHANGE_Keys *key_state; + struct TALER_EXCHANGE_BatchDepositHandle *dh; + struct GNUNET_CURL_Context *ctx; + json_t *deposit_obj; + json_t *deposits; + CURL *eh; + struct TALER_Amount amount_without_fee; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline, + >, + dcd->wire_deadline)) + { + GNUNET_break_op (0); + *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE; + return NULL; + } + key_state = TALER_EXCHANGE_get_keys (exchange); + dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle); + dh->auditor_chance = AUDITOR_CHANCE; + dh->exchange = exchange; + dh->cb = cb; + dh->cb_cls = cb_cls; + dh->cdds = GNUNET_memdup (cdds, + num_cdds + * sizeof (*cdds)); + dh->num_cdds = num_cdds; + dh->dcd = *dcd; + if (NULL != dcd->extension_details) + TALER_deposit_extension_hash (dcd->extension_details, + &dh->h_extensions); + TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri, + &dcd->wire_salt, + &dh->h_wire); + deposits = json_array (); + GNUNET_assert (NULL != deposits); + for (unsigned int i = 0; ih_denom_pub); + if (NULL == dki) + { + *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + GNUNET_break_op (0); + return NULL; + } + if (0 > + TALER_amount_subtract (&amount_without_fee, + &cdd->amount, + &dki->fees.deposit)) + { + *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT; + GNUNET_break_op (0); + GNUNET_free (dh->cdds); + GNUNET_free (dh); + return NULL; + } + + if (GNUNET_OK != + TALER_EXCHANGE_verify_deposit_signature_ (dcd, + &dh->h_extensions, + &dh->h_wire, + cdd, + dki)) + { + *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID; + GNUNET_break_op (0); + GNUNET_free (dh->cdds); + GNUNET_free (dh); + return NULL; + } + GNUNET_assert ( + 0 == + json_array_append_new ( + deposits, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("contribution", + &cdd->amount), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + &cdd->h_age_commitment)), + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &cdd->h_denom_pub), + TALER_JSON_pack_denom_sig ("ub_sig", + &cdd->denom_sig), + GNUNET_JSON_pack_data_auto ("coin_sig", + &cdd->coin_sig) + ))); + } + dh->url = TEAH_path_to_url (exchange, + "/batch-deposit"); + if (NULL == dh->url) + { + GNUNET_break (0); + *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; + GNUNET_free (dh->url); + GNUNET_free (dh->cdds); + GNUNET_free (dh); + return NULL; + } + + deposit_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("coins", + deposits), + GNUNET_JSON_pack_string ("merchant_payto_uri", + dcd->merchant_payto_uri), + GNUNET_JSON_pack_data_auto ("wire_salt", + &dcd->wire_salt), + GNUNET_JSON_pack_data_auto ("h_contract_terms", + &dcd->h_contract_terms), + GNUNET_JSON_pack_timestamp ("timestamp", + dcd->timestamp), + GNUNET_JSON_pack_data_auto ("merchant_pub", + &dcd->merchant_pub), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("refund_deadline", + dcd->refund_deadline)), + GNUNET_JSON_pack_timestamp ("wire_transfer_deadline", + dcd->wire_deadline)); + GNUNET_assert (NULL != deposit_obj); + eh = TALER_EXCHANGE_curl_easy_get_ (dh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&dh->ctx, + eh, + deposit_obj)) ) + { + *ec = TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE; + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (deposit_obj); + GNUNET_free (dh->cdds); + GNUNET_free (dh->url); + GNUNET_free (dh); + return NULL; + } + json_decref (deposit_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for deposit: `%s'\n", + dh->url); + ctx = TEAH_handle_to_context (exchange); + dh->job = GNUNET_CURL_job_add2 (ctx, + eh, + dh->ctx.headers, + &handle_deposit_finished, + dh); + return dh; +} + + +void +TALER_EXCHANGE_batch_deposit_force_dc (struct + TALER_EXCHANGE_BatchDepositHandle * + deposit) +{ + deposit->auditor_chance = 1; +} + + +void +TALER_EXCHANGE_batch_deposit_cancel (struct + TALER_EXCHANGE_BatchDepositHandle *deposit) +{ + if (NULL != deposit->job) + { + GNUNET_CURL_job_cancel (deposit->job); + deposit->job = NULL; + } + GNUNET_free (deposit->url); + GNUNET_free (deposit->cdds); + GNUNET_free (deposit->exchange_sigs); + TALER_curl_easy_post_finished (&deposit->ctx); + GNUNET_free (deposit); +} + + +/* end of exchange_api_batch_deposit.c */ diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 26ddb3c06..739b215f6 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -1946,4 +1946,66 @@ TALER_EXCHANGE_get_min_denomination_ ( } +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_verify_deposit_signature_ ( + const struct TALER_EXCHANGE_DepositContractDetail *dcd, + const struct TALER_ExtensionContractHashP *ech, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_EXCHANGE_CoinDepositDetail *cdd, + const struct TALER_EXCHANGE_DenomPublicKey *dki) +{ + if (GNUNET_OK != + TALER_wallet_deposit_verify (&cdd->amount, + &dki->fees.deposit, + h_wire, + &dcd->h_contract_terms, + &cdd->h_age_commitment, + ech, + &cdd->h_denom_pub, + dcd->timestamp, + &dcd->merchant_pub, + dcd->refund_deadline, + &cdd->coin_pub, + &cdd->coin_sig)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n"); + TALER_LOG_DEBUG ("... amount_with_fee was %s\n", + TALER_amount2s (&cdd->amount)); + TALER_LOG_DEBUG ("... deposit_fee was %s\n", + TALER_amount2s (&dki->fees.deposit)); + return GNUNET_SYSERR; + } + + /* check coin signature */ + { + struct TALER_CoinPublicInfo coin_info = { + .coin_pub = cdd->coin_pub, + .denom_pub_hash = cdd->h_denom_pub, + .denom_sig = cdd->denom_sig, + .h_age_commitment = cdd->h_age_commitment, + }; + + if (GNUNET_YES != + TALER_test_coin_valid (&coin_info, + &dki->key)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); + return GNUNET_SYSERR; + } + } + + /* Check coin does make a contribution */ + if (0 < TALER_amount_cmp (&dki->fees.deposit, + &cdd->amount)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Deposit amount smaller than fee\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + /* end of exchange_api_common.c */ diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h index f4737ca98..a75ed3ed2 100644 --- a/src/lib/exchange_api_common.h +++ b/src/lib/exchange_api_common.h @@ -198,4 +198,24 @@ TALER_EXCHANGE_get_min_denomination_ ( const struct TALER_EXCHANGE_Keys *keys, struct TALER_Amount *min); + +/** + * Verify signature information about the deposit. + * + * @param dcd contract details + * @param ech hashed contract (passed to avoid recomputation) + * @param h_wire hashed wire details (passed to avoid recomputation) + * @param cdd coin-specific details + * @param dki denomination of the coin + * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_verify_deposit_signature_ ( + const struct TALER_EXCHANGE_DepositContractDetail *dcd, + const struct TALER_ExtensionContractHashP *ech, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_EXCHANGE_CoinDepositDetail *cdd, + const struct TALER_EXCHANGE_DenomPublicKey *dki); + + #endif diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index 7e8246170..329643413 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -378,77 +378,6 @@ handle_deposit_finished (void *cls, } -/** - * Verify signature information about the deposit. - * - * @param dcd contract details - * @param ech hashed contract (passed to avoid recomputation) - * @param h_wire hashed wire details (passed to avoid recomputation) - * @param cdd coin-specific details - * @param dki denomination of the coin - * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not - */ -static enum GNUNET_GenericReturnValue -verify_signatures (const struct TALER_EXCHANGE_DepositContractDetail *dcd, - const struct TALER_ExtensionContractHashP *ech, - const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_EXCHANGE_CoinDepositDetail *cdd, - const struct TALER_EXCHANGE_DenomPublicKey *dki) -{ - if (GNUNET_OK != - TALER_wallet_deposit_verify (&cdd->amount, - &dki->fees.deposit, - h_wire, - &dcd->h_contract_terms, - &cdd->h_age_commitment, - ech, - &cdd->h_denom_pub, - dcd->timestamp, - &dcd->merchant_pub, - dcd->refund_deadline, - &cdd->coin_pub, - &cdd->coin_sig)) - { - GNUNET_break_op (0); - TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n"); - TALER_LOG_DEBUG ("... amount_with_fee was %s\n", - TALER_amount2s (&cdd->amount)); - TALER_LOG_DEBUG ("... deposit_fee was %s\n", - TALER_amount2s (&dki->fees.deposit)); - return GNUNET_SYSERR; - } - - /* check coin signature */ - { - struct TALER_CoinPublicInfo coin_info = { - .coin_pub = cdd->coin_pub, - .denom_pub_hash = cdd->h_denom_pub, - .denom_sig = cdd->denom_sig, - .h_age_commitment = cdd->h_age_commitment, - }; - - if (GNUNET_YES != - TALER_test_coin_valid (&coin_info, - &dki->key)) - { - GNUNET_break_op (0); - TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); - return GNUNET_SYSERR; - } - } - - /* Check coin does make a contribution */ - if (0 < TALER_amount_cmp (&dki->fees.deposit, - &cdd->amount)) - { - GNUNET_break_op (0); - TALER_LOG_WARNING ("Deposit amount smaller than fee\n"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - struct TALER_EXCHANGE_DepositHandle * TALER_EXCHANGE_deposit ( struct TALER_EXCHANGE_Handle *exchange, @@ -526,11 +455,11 @@ TALER_EXCHANGE_deposit ( &dcd->wire_salt, &dh->h_wire); if (GNUNET_OK != - verify_signatures (dcd, - &dh->h_extensions, - &dh->h_wire, - cdd, - dki)) + TALER_EXCHANGE_verify_deposit_signature_ (dcd, + &dh->h_extensions, + &dh->h_wire, + cdd, + dki)) { *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID; GNUNET_break_op (0); diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index 3cdc8ad29..488a419be 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -992,7 +992,7 @@ decode_keys_json (const json_t *resp_obj, check_sig, denom_key_obj, &key_data->master_pub, - check_sig ? &hash_xor: NULL)); + check_sig ? &hash_xor : NULL)); // Build the running xor of the SHA512-hash of the public keys { diff --git a/src/lib/exchange_api_purse_create_with_deposit.c b/src/lib/exchange_api_purse_create_with_deposit.c index 3a5b7df59..ce0221aa6 100644 --- a/src/lib/exchange_api_purse_create_with_deposit.c +++ b/src/lib/exchange_api_purse_create_with_deposit.c @@ -680,8 +680,8 @@ TALER_EXCHANGE_purse_create_with_deposit ( GNUNET_JSON_pack_allow_null ( TALER_JSON_pack_econtract ("econtract", upload_contract - ? &pch->econtract - : NULL)), + ? &pch->econtract + : NULL)), GNUNET_JSON_pack_data_auto ("purse_sig", &pch->purse_sig), GNUNET_JSON_pack_data_auto ("merge_pub", diff --git a/src/lib/exchange_api_purse_create_with_merge.c b/src/lib/exchange_api_purse_create_with_merge.c index 2b4f1cd27..f3c72d4f7 100644 --- a/src/lib/exchange_api_purse_create_with_merge.c +++ b/src/lib/exchange_api_purse_create_with_merge.c @@ -478,8 +478,8 @@ TALER_EXCHANGE_purse_create_with_merge ( GNUNET_JSON_pack_allow_null ( TALER_JSON_pack_econtract ("econtract", upload_contract - ? &pcm->econtract - : NULL)), + ? &pcm->econtract + : NULL)), GNUNET_JSON_pack_allow_null ( pay_for_purse ? TALER_JSON_pack_amount ("purse_fee",