diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c b/src/exchange/taler-exchange-httpd_batch-withdraw.c index cf2382042..541d65728 100644 --- a/src/exchange/taler-exchange-httpd_batch-withdraw.c +++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c @@ -240,6 +240,7 @@ batch_withdraw_transaction (void *cls, TEH_plugin->rollback (TEH_plugin->cls); *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( connection, + TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, &wc->batch_total, wc->reserve_pub); return GNUNET_DB_STATUS_HARD_ERROR; diff --git a/src/exchange/taler-exchange-httpd_reserves_history.c b/src/exchange/taler-exchange-httpd_reserves_history.c index aa3f8ab55..ffdc6eaa4 100644 --- a/src/exchange/taler-exchange-httpd_reserves_history.c +++ b/src/exchange/taler-exchange-httpd_reserves_history.c @@ -159,7 +159,7 @@ reserve_history_transaction (void *cls, { return TALER_MHD_reply_with_error (connection, MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_WITHDRAW_HISTORY_ERROR_INSUFFICIENT_FUNDS, + TALER_EC_EXCHANGE_GET_RESERVE_HISTORY_ERROR_INSUFFICIENT_BALANCE, NULL); } if (idempotent) diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c index ced291d77..6909c862a 100644 --- a/src/exchange/taler-exchange-httpd_reserves_open.c +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -288,6 +288,7 @@ reserve_open_transaction (void *cls, *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( connection, + TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS, &rsc->reserve_payment, rsc->reserve_pub); return GNUNET_DB_STATUS_HARD_ERROR; diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index ca110ad45..4120405fd 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -1000,8 +1000,9 @@ TEH_RESPONSE_compile_reserve_history ( * @return MHD result code */ static MHD_RESULT -reply_withdraw_insufficient_funds ( +reply_reserve_insufficient_funds ( struct MHD_Connection *connection, + enum TALER_ErrorCode ec, const struct TALER_Amount *ebalance, const struct TALER_Amount *withdraw_amount, const struct TALER_EXCHANGEDB_ReserveHistory *rh) @@ -1012,12 +1013,12 @@ reply_withdraw_insufficient_funds ( if (NULL == json_history) return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_WITHDRAW_HISTORY_ERROR_INSUFFICIENT_FUNDS, + TALER_EC_EXCHANGE_RESERVE_HISTORY_ERROR_INSUFFICIENT_FUNDS, NULL); return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_CONFLICT, - TALER_JSON_pack_ec (TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS), + TALER_JSON_pack_ec (ec), TALER_JSON_pack_amount ("balance", ebalance), TALER_JSON_pack_amount ("requested_amount", @@ -1030,6 +1031,7 @@ reply_withdraw_insufficient_funds ( MHD_RESULT TEH_RESPONSE_reply_reserve_insufficient_balance ( struct MHD_Connection *connection, + enum TALER_ErrorCode ec, const struct TALER_Amount *balance_required, const struct TALER_ReservePublicKeyP *reserve_pub) { @@ -1063,8 +1065,9 @@ TEH_RESPONSE_reply_reserve_insufficient_balance ( TALER_EC_GENERIC_DB_FETCH_FAILED, "reserve history"); } - mhd_ret = reply_withdraw_insufficient_funds ( + mhd_ret = reply_reserve_insufficient_funds ( connection, + ec, &balance, balance_required, rh); diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h index 3eebf0274..ba6577b29 100644 --- a/src/exchange/taler-exchange-httpd_responses.h +++ b/src/exchange/taler-exchange-httpd_responses.h @@ -63,6 +63,7 @@ TEH_RESPONSE_reply_unknown_denom_pub_hash ( * an insufficient balance for the given operation. * * @param connection connection to the client + * @param ec specific error code to return with the reserve history * @param balance_required the balance required for the operation * @param reserve_pub the reserve with insufficient balance * @return MHD result code @@ -70,6 +71,7 @@ TEH_RESPONSE_reply_unknown_denom_pub_hash ( MHD_RESULT TEH_RESPONSE_reply_reserve_insufficient_balance ( struct MHD_Connection *connection, + enum TALER_ErrorCode ec, const struct TALER_Amount *balance_required, const struct TALER_ReservePublicKeyP *reserve_pub); diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c index 57020ee87..27b176722 100644 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ b/src/exchange/taler-exchange-httpd_withdraw.c @@ -217,6 +217,7 @@ withdraw_transaction (void *cls, TEH_plugin->rollback (TEH_plugin->cls); *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( connection, + TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, &wc->collectable.amount_with_fee, &wc->collectable.reserve_pub); return GNUNET_DB_STATUS_HARD_ERROR; diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h index 80c36daf1..1b9ddce34 100644 --- a/src/lib/exchange_api_common.h +++ b/src/lib/exchange_api_common.h @@ -129,12 +129,10 @@ TALER_EXCHANGE_check_coin_amount_conflict_ ( /** - * Verify that @a proof contains a coin history - * that demonstrates that @a coin_pub was previously - * used with a denomination key that is different - * from @a ch_denom_pub. Note that the coin history - * MUST have been checked before using - * #TALER_EXCHANGE_check_coin_amount_conflict_(). + * Verify that @a proof contains a coin history that demonstrates that @a + * coin_pub was previously used with a denomination key that is different from + * @a ch_denom_pub. Note that the coin history MUST have been checked before + * using #TALER_EXCHANGE_check_coin_amount_conflict_(). * * @param proof a proof to check * @param ch_denom_pub hash of the conflicting denomination diff --git a/src/lib/exchange_api_reserves_open.c b/src/lib/exchange_api_reserves_open.c index 64d259ba0..2b7ef0d90 100644 --- a/src/lib/exchange_api_reserves_open.c +++ b/src/lib/exchange_api_reserves_open.c @@ -27,11 +27,39 @@ #include #include "taler_exchange_service.h" #include "taler_json_lib.h" +#include "exchange_api_common.h" #include "exchange_api_handle.h" #include "taler_signatures.h" #include "exchange_api_curl_defaults.h" +/** + * Information we keep per coin to validate the reply. + */ +struct CoinData +{ + /** + * Public key of the coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature by the coin. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * The hash of the denomination's public key + */ + struct TALER_DenominationHashP h_denom_pub; + + /** + * How much did this coin contribute. + */ + struct TALER_Amount contribution; +}; + + /** * @brief A /reserves/$RID/open Handle */ @@ -69,6 +97,16 @@ struct TALER_EXCHANGE_ReservesOpenHandle */ void *cb_cls; + /** + * Information we keep per coin to validate the reply. + */ + struct CoinData *coins; + + /** + * Length of the @e coins array. + */ + unsigned int num_coins; + /** * Public key of the reserve we are querying. */ @@ -282,12 +320,74 @@ handle_reserves_open_finished (void *cls, rs.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_CONFLICT: - // FIXME: not yet specified (#7428), but needed in - // case of double-spending or insufficient - // reserve balance! - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; + { + const struct TALER_EXCHANGE_Keys *keys; + const struct CoinData *cd = NULL; + struct TALER_CoinSpendPublicKeyP coin_pub; + const struct TALER_EXCHANGE_DenomPublicKey *dk; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &coin_pub), + GNUNET_JSON_spec_end () + }; + + keys = TALER_EXCHANGE_get_keys (roh->exchange); + GNUNET_assert (NULL != keys); + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + for (unsigned int i = 0; inum_coins; i++) + { + const struct CoinData *cdi = &roh->coins[i]; + + if (0 == GNUNET_memcmp (&coin_pub, + &cdi->coin_pub)) + { + cd = cdi; + break; + } + } + if (NULL == cd) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys, + &cd->h_denom_pub); + if (NULL == dk) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR; + break; + } + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_conflict_ (keys, + j, + dk, + &coin_pub, + &cd->coin_sig, + &cd->contribution)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + } case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: if (GNUNET_OK != handle_reserves_open_kyc (roh, @@ -402,18 +502,21 @@ TALER_EXCHANGE_reserves_open ( min_purses, reserve_priv, &roh->reserve_sig); + roh->coins = GNUNET_new_array (coin_payments_length, + struct CoinData); cpa = json_array (); GNUNET_assert (NULL != cpa); for (unsigned int i = 0; iage_commitment_proof; struct TALER_AgeCommitmentHash ahac; struct TALER_AgeCommitmentHash *achp = NULL; + struct CoinData *cd = &roh->coins[i]; json_t *cp; + cd->contribution = pd->amount; + cd->h_denom_pub = pd->h_denom_pub; if (NULL != acp) { TALER_age_commitment_hash (&acp->commitment, @@ -423,9 +526,9 @@ TALER_EXCHANGE_reserves_open ( TALER_wallet_reserve_open_deposit_sign (&pd->amount, &roh->reserve_sig, &pd->coin_priv, - &coin_sig); + &cd->coin_sig); GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); + &cd->coin_pub.eddsa_pub); cp = GNUNET_JSON_PACK ( GNUNET_JSON_pack_allow_null ( @@ -438,9 +541,9 @@ TALER_EXCHANGE_reserves_open ( TALER_JSON_pack_denom_sig ("ub_sig", &pd->denom_sig), GNUNET_JSON_pack_data_auto ("coin_pub", - &coin_pub), + &cd->coin_pub), GNUNET_JSON_pack_data_auto ("coin_sig", - &coin_sig)); + &cd->coin_sig)); GNUNET_assert (0 == json_array_append_new (cpa, cp)); @@ -468,6 +571,7 @@ TALER_EXCHANGE_reserves_open ( GNUNET_break (0); curl_easy_cleanup (eh); json_decref (open_obj); + GNUNET_free (roh->coins); GNUNET_free (roh->url); GNUNET_free (roh); return NULL; @@ -494,6 +598,7 @@ TALER_EXCHANGE_reserves_open_cancel ( roh->job = NULL; } TALER_curl_easy_post_finished (&roh->post_ctx); + GNUNET_free (roh->coins); GNUNET_free (roh->url); GNUNET_free (roh); }