diff --git a/src/exchange-lib/exchange_api_common.c b/src/exchange-lib/exchange_api_common.c index 17cfab92b..29d0f6d1c 100644 --- a/src/exchange-lib/exchange_api_common.c +++ b/src/exchange-lib/exchange_api_common.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015, 2016 Inria & GNUnet e.V. + Copyright (C) 2015-2017 Inria & GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -65,20 +65,12 @@ TALER_EXCHANGE_verify_coin_history (const char *currency, { json_t *transaction; struct TALER_Amount amount; - struct TALER_CoinSpendSignatureP sig; - void *details; - size_t details_size; const char *type; - struct GNUNET_JSON_Specification spec[] = { + struct GNUNET_JSON_Specification spec_glob[] = { TALER_JSON_spec_amount ("amount", - &amount), + &amount), GNUNET_JSON_spec_string ("type", - &type), - GNUNET_JSON_spec_fixed_auto ("signature", - &sig), - GNUNET_JSON_spec_varsize ("details", - &details, - &details_size), + &type), GNUNET_JSON_spec_end() }; @@ -86,7 +78,7 @@ TALER_EXCHANGE_verify_coin_history (const char *currency, off); if (GNUNET_OK != GNUNET_JSON_parse (transaction, - spec, + spec_glob, NULL, NULL)) { GNUNET_break_op (0); @@ -96,40 +88,47 @@ TALER_EXCHANGE_verify_coin_history (const char *currency, if (0 == strcasecmp (type, "DEPOSIT")) { - const struct TALER_DepositRequestPS *dr; + struct TALER_DepositRequestPS dr; struct TALER_Amount dr_amount; + struct TALER_CoinSpendSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("signature", + &sig), + GNUNET_JSON_spec_fixed_auto ("details", + &dr), + GNUNET_JSON_spec_end() + }; - if (details_size != sizeof (struct TALER_DepositRequestPS)) + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } - dr = (const struct TALER_DepositRequestPS *) details; - if (details_size != ntohl (dr->purpose.size)) + /* TODO #4980 */ + if (sizeof (dr) != ntohl (dr.purpose.size)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, - &dr->purpose, + &dr.purpose, &sig.eddsa_signature, &coin_pub->eddsa_pub)) - { + { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } - TALER_amount_ntoh (&dr_amount, - &dr->amount_with_fee); + &dr.amount_with_fee); + /* TODO #4980 */ if (0 != TALER_amount_cmp (&dr_amount, &amount)) { GNUNET_break (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } add = GNUNET_YES; @@ -137,39 +136,47 @@ TALER_EXCHANGE_verify_coin_history (const char *currency, else if (0 == strcasecmp (type, "MELT")) { - const struct TALER_RefreshMeltCoinAffirmationPS *rm; + struct TALER_RefreshMeltCoinAffirmationPS rm; struct TALER_Amount rm_amount; + struct TALER_CoinSpendSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("signature", + &sig), + GNUNET_JSON_spec_fixed_auto ("details", + &rm), + GNUNET_JSON_spec_end() + }; - if (details_size != sizeof (struct TALER_RefreshMeltCoinAffirmationPS)) + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } - rm = (const struct TALER_RefreshMeltCoinAffirmationPS *) details; - if (details_size != ntohl (rm->purpose.size)) + /* TODO #4980 */ + if (sizeof (rm) != ntohl (rm.purpose.size)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, - &rm->purpose, + &rm.purpose, &sig.eddsa_signature, &coin_pub->eddsa_pub)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } TALER_amount_ntoh (&rm_amount, - &rm->amount_with_fee); + &rm.amount_with_fee); + /* TODO #4980 */ if (0 != TALER_amount_cmp (&rm_amount, &amount)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } add = GNUNET_YES; @@ -177,55 +184,60 @@ TALER_EXCHANGE_verify_coin_history (const char *currency, else if (0 == strcasecmp (type, "REFUND")) { - const struct TALER_RefundRequestPS *rr; + struct TALER_RefundRequestPS rr; struct TALER_Amount rr_amount; struct TALER_Amount rr_fee; struct TALER_Amount rr_delta; + struct TALER_CoinSpendSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("signature", + &sig), + GNUNET_JSON_spec_fixed_auto ("details", + &rr), + GNUNET_JSON_spec_end() + }; - if (details_size != sizeof (struct TALER_RefundRequestPS)) + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } - rr = (const struct TALER_RefundRequestPS *) details; - if (details_size != ntohl (rr->purpose.size)) + /* TODO #4980 */ + if (sizeof (rr) != ntohl (rr.purpose.size)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, - &rr->purpose, + &rr.purpose, &sig.eddsa_signature, - &rr->merchant.eddsa_pub)) + &rr.merchant.eddsa_pub)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } TALER_amount_ntoh (&rr_amount, - &rr->refund_amount); - TALER_amount_ntoh (&rr_fee, - &rr->refund_fee); + &rr.refund_amount); if (GNUNET_OK != TALER_amount_subtract (&rr_delta, &rr_amount, &rr_fee)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } + /* TODO #4980 */ if (0 != TALER_amount_cmp (&rr_delta, &amount)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } - /* NOTE/FIXME: theoretically, we could also check that the given + /* NOTE: theoretically, we could also check that the given merchant_pub and h_proposal_data appear in the history under deposits. However, there is really no benefit for the exchange to lie here, so not checking is probably OK @@ -237,14 +249,59 @@ TALER_EXCHANGE_verify_coin_history (const char *currency, else if (0 == strcasecmp (type, "PAYBACK")) { - GNUNET_break (0); /* #3887 */ + struct TALER_PaybackConfirmationPS pc; + struct TALER_Amount pc_amount; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + 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_fixed_auto ("details", + &pc), + GNUNET_JSON_spec_end() + }; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* TODO #4980 */ + if (sizeof (pc) != ntohl (pc.purpose.size)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, + &pc.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + TALER_amount_ntoh (&pc_amount, + &pc.payback_amount); + /* TODO #4980 */ + if (0 != TALER_amount_cmp (&pc_amount, + &amount)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + add = GNUNET_YES; } else { /* signature not supported, new version on server? */ GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } if (GNUNET_YES == add) @@ -257,7 +314,6 @@ TALER_EXCHANGE_verify_coin_history (const char *currency, { /* overflow in history already!? inconceivable! Bad exchange! */ GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } } @@ -277,11 +333,9 @@ TALER_EXCHANGE_verify_coin_history (const char *currency, { /* overflow in refund history? inconceivable! Bad exchange! */ GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } } - GNUNET_JSON_parse_free (spec); } /* Finally, subtract 'rtotal' from total to handle the subtractions */ diff --git a/src/exchange-lib/exchange_api_reserve.c b/src/exchange-lib/exchange_api_reserve.c index 900b6462e..6dd48866f 100644 --- a/src/exchange-lib/exchange_api_reserve.c +++ b/src/exchange-lib/exchange_api_reserve.c @@ -77,7 +77,8 @@ struct TALER_EXCHANGE_ReserveStatusHandle * Parse history given in JSON format and return it in binary * format. * - * @param[in] history JSON array with the history + * @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 @@ -89,7 +90,8 @@ struct TALER_EXCHANGE_ReserveStatusHandle * #GNUNET_SYSERR if there was a protocol violation in @a history */ static int -parse_reserve_history (const json_t *history, +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, @@ -214,6 +216,7 @@ parse_reserve_history (const json_t *history, } TALER_amount_ntoh (&amount_from_purpose, &withdraw_purpose.amount_with_fee); + /* TODO #4980 */ if (0 != TALER_amount_cmp (&amount, &amount_from_purpose)) { @@ -259,13 +262,75 @@ parse_reserve_history (const json_t *history, else if (0 == strcasecmp (type, "PAYBACK")) { - GNUNET_break (0); /* #3887 */ + struct TALER_PaybackConfirmationPS pc; + struct TALER_Amount amount_from_purpose; + struct GNUNET_TIME_Absolute timestamp_from_purpose; + struct GNUNET_TIME_Absolute timestamp; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification payback_spec[] = { + GNUNET_JSON_spec_fixed_auto ("details", + &pc), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rhistory[off].details.payback_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rhistory[off].details.payback_details.exchange_pub), + GNUNET_JSON_spec_absolute_time ("timetamp", + ×tamp), + TALER_JSON_spec_amount ("amount", + &rhistory[off].amount), + GNUNET_JSON_spec_end() + }; + + rhistory[off].type = TALER_EXCHANGE_RTT_PAYBACK; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + payback_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rhistory[off].details.payback_details.coin_pub = pc.coin_pub; + TALER_amount_ntoh (&amount_from_purpose, + &pc.payback_amount); + rhistory[off].details.payback_details.timestamp = timestamp; + timestamp_from_purpose = GNUNET_TIME_absolute_ntoh (pc.timestamp); + /* TODO #4980 */ + if ( (0 != memcmp (&pc.reserve_pub, + reserve_pub, + sizeof (*reserve_pub))) || + (timestamp_from_purpose.abs_value_us != + timestamp.abs_value_us) || + (0 != TALER_amount_cmp (&amount_from_purpose, + &rhistory[off].amount)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + key_state = TALER_EXCHANGE_get_keys (exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &rhistory[off].details.payback_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, + &pc.purpose, + &rhistory[off].details.payback_details.exchange_sig.eddsa_signature, + &rhistory[off].details.payback_details.exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } /* end type==PAYBACK */ } else if (0 == strcasecmp (type, "CLOSING")) { - GNUNET_break (0); /* #3887 / #4956 */ + GNUNET_break (0); /* FIXME: implement with #4956 */ /* end type==CLOSING */ } else @@ -304,9 +369,9 @@ handle_reserve_status_finished (void *cls, long response_code, const json_t *json) { - struct TALER_EXCHANGE_ReserveStatusHandle *wsh = cls; + struct TALER_EXCHANGE_ReserveStatusHandle *rsh = cls; - wsh->job = NULL; + rsh->job = NULL; switch (response_code) { case 0: @@ -346,8 +411,9 @@ handle_reserve_status_finished (void *cls, struct TALER_EXCHANGE_ReserveHistory rhistory[len]; if (GNUNET_OK != - parse_reserve_history (history, - &wsh->reserve_pub, + parse_reserve_history (rsh->exchange, + history, + &rsh->reserve_pub, balance.currency, &balance_from_history, len, @@ -366,14 +432,14 @@ handle_reserve_status_finished (void *cls, response_code = 0; break; } - wsh->cb (wsh->cb_cls, + rsh->cb (rsh->cb_cls, response_code, TALER_EC_NONE, json, &balance, len, rhistory); - wsh->cb = NULL; + rsh->cb = NULL; } } break; @@ -398,14 +464,14 @@ handle_reserve_status_finished (void *cls, response_code = 0; break; } - if (NULL != wsh->cb) - wsh->cb (wsh->cb_cls, + if (NULL != rsh->cb) + rsh->cb (rsh->cb_cls, response_code, TALER_JSON_get_error_code (json), json, NULL, 0, NULL); - TALER_EXCHANGE_reserve_status_cancel (wsh); + TALER_EXCHANGE_reserve_status_cancel (rsh); } @@ -431,7 +497,7 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange, TALER_EXCHANGE_ReserveStatusResultCallback cb, void *cb_cls) { - struct TALER_EXCHANGE_ReserveStatusHandle *wsh; + struct TALER_EXCHANGE_ReserveStatusHandle *rsh; struct GNUNET_CURL_Context *ctx; CURL *eh; char *pub_str; @@ -449,12 +515,12 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange, "/reserve/status?reserve_pub=%s", pub_str); GNUNET_free (pub_str); - wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle); - wsh->exchange = exchange; - wsh->cb = cb; - wsh->cb_cls = cb_cls; - wsh->reserve_pub = *reserve_pub; - wsh->url = MAH_path_to_url (exchange, + rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle); + rsh->exchange = exchange; + rsh->cb = cb; + rsh->cb_cls = cb_cls; + rsh->reserve_pub = *reserve_pub; + rsh->url = MAH_path_to_url (exchange, arg_str); GNUNET_free (arg_str); @@ -462,14 +528,14 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange, GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_URL, - wsh->url)); + rsh->url)); ctx = MAH_handle_to_context (exchange); - wsh->job = GNUNET_CURL_job_add (ctx, + rsh->job = GNUNET_CURL_job_add (ctx, eh, GNUNET_NO, &handle_reserve_status_finished, - wsh); - return wsh; + rsh); + return rsh; } @@ -477,18 +543,18 @@ TALER_EXCHANGE_reserve_status (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 wsh the withdraw status request handle + * @param rsh the withdraw status request handle */ void -TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *wsh) +TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *rsh) { - if (NULL != wsh->job) + if (NULL != rsh->job) { - GNUNET_CURL_job_cancel (wsh->job); - wsh->job = NULL; + GNUNET_CURL_job_cancel (rsh->job); + rsh->job = NULL; } - GNUNET_free (wsh->url); - GNUNET_free (wsh); + GNUNET_free (rsh->url); + GNUNET_free (rsh); } @@ -663,7 +729,8 @@ reserve_withdraw_payment_required (struct TALER_EXCHANGE_ReserveWithdrawHandle * struct TALER_EXCHANGE_ReserveHistory rhistory[len]; if (GNUNET_OK != - parse_reserve_history (history, + parse_reserve_history (wsh->exchange, + history, &wsh->reserve_pub, balance.currency, &balance_from_history, diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 4aa3d0240..bd8e281d3 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015, 2016 GNUnet e.V. + Copyright (C) 2014-2017 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -658,7 +658,17 @@ enum TALER_EXCHANGE_ReserveTransactionType { /** * Withdrawal from the reserve. */ - TALER_EXCHANGE_RTT_WITHDRAWAL + TALER_EXCHANGE_RTT_WITHDRAWAL, + + /** + * /payback operation. + */ + TALER_EXCHANGE_RTT_PAYBACK, + + /** + * Reserve closed operation. + */ + TALER_EXCHANGE_RTT_CLOSE }; @@ -684,6 +694,10 @@ struct TALER_EXCHANGE_ReserveHistory */ union { + /** + * Information about a deposit that filled this reserve. + * @e type is #TALER_EXCHANGE_RTT_DEPOSIT. + */ struct { /** * Sender account information for the incoming transfer. @@ -695,14 +709,60 @@ struct TALER_EXCHANGE_ReserveHistory */ json_t *transfer_details; - } in_details; /** * Signature authorizing the withdrawal for outgoing transaction. + * @e type is #TALER_EXCHANGE_RTT_WITHDRAWAL. */ json_t *out_authorization_sig; + /** + * Information provided if the reserve was filled via /payback. + * @e type is #TALER_EXCHANGE_RTT_PAYBACK. + */ + struct { + + /** + * Public key of the coin that was paid back. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature of the coin of type + * #TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK. + */ + struct TALER_ExchangeSignatureP exchange_sig; + + /** + * Public key of the exchange that was used for @e exchange_sig. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * When did the /payback operation happen? + */ + struct GNUNET_TIME_Absolute timestamp; + + } payback_details; + + /** + * Information about a close operation of the reserve. + * @e type is #TALER_EXCHANGE_RTT_CLOSE. + */ + struct { + /** + * Receiver account information for the outgoing wire transfer. + */ + json_t *receiver_account_details; + + /** + * Wire transfer details for the outgoing wire transfer. + */ + json_t *transfer_details; + + } close_details; + } details; }; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 8a1a82838..322a30524 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -537,8 +537,8 @@ struct TALER_EXCHANGEDB_LinkDataList * @brief Enumeration to classify the different types of transactions * that can be done with a coin. */ -enum TALER_EXCHANGEDB_TransactionType -{ +enum TALER_EXCHANGEDB_TransactionType { + /** * /deposit operation. */