diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/exchange/taler-exchange-httpd.c | 110 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_coins_get.h | 2 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_reserves_get.c | 20 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_reserves_get.h | 9 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_reserves_history.c | 52 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_reserves_history.h | 12 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_responses.h | 2 | ||||
-rw-r--r-- | src/include/taler_exchange_service.h | 59 | ||||
-rw-r--r-- | src/include/taler_mhd_lib.h | 44 | ||||
-rw-r--r-- | src/include/taler_util.h | 10 | ||||
-rw-r--r-- | src/lib/exchange_api_age_withdraw.c | 126 | ||||
-rw-r--r-- | src/lib/exchange_api_batch_withdraw2.c | 113 | ||||
-rw-r--r-- | src/lib/exchange_api_coins_history.c | 1290 | ||||
-rw-r--r-- | src/lib/exchange_api_common.c | 1633 | ||||
-rw-r--r-- | src/lib/exchange_api_common.h | 37 | ||||
-rw-r--r-- | src/lib/exchange_api_reserves_history.c | 784 | ||||
-rw-r--r-- | src/mhd/mhd_parsing.c | 36 |
17 files changed, 2275 insertions, 2064 deletions
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index d059e7430..7129f6d13 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -402,14 +402,17 @@ handle_get_coins (struct TEH_RequestContext *rc, TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB, args[0]); } - - if (NULL == args[1]) - return TEH_handler_coins_get (rc, - &coin_pub); - if (0 == strcmp (args[1], - "link")) - return TEH_handler_link (rc, - &coin_pub); + if (NULL != args[1]) + { + if (0 == strcmp (args[1], + "history")) + return TEH_handler_coins_get (rc, + &coin_pub); + if (0 == strcmp (args[1], + "link")) + return TEH_handler_link (rc, + &coin_pub); + } return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_GENERIC_ENDPOINT_UNKNOWN, @@ -731,11 +734,6 @@ handle_post_reserves (struct TEH_RequestContext *rc, .op = "withdraw", .handler = &TEH_handler_withdraw }, - // FIXME: change to GET! - { - .op = "history", - .handler = &TEH_handler_reserves_history - }, { .op = "purse", .handler = &TEH_handler_reserves_purse @@ -778,6 +776,87 @@ handle_post_reserves (struct TEH_RequestContext *rc, /** + * Signature of functions that handle GET operations on reserves. + * + * @param rc request context + * @param reserve_pub the public key of the reserve + * @return MHD result code + */ +typedef MHD_RESULT +(*ReserveGetOpHandler)(struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub); + + +/** + * Handle a "GET /reserves/$RESERVE_PUB[/$OP]" request. Parses the "reserve_pub" + * EdDSA key of the reserve and demultiplexes based on $OP. + * + * @param rc request context + * @param args NULL-terminated array of additional options, zero, one or two + * @return MHD result code + */ +static MHD_RESULT +handle_get_reserves (struct TEH_RequestContext *rc, + const char *const args[]) +{ + struct TALER_ReservePublicKeyP reserve_pub; + static const struct + { + /** + * Name of the operation (args[1]), optional + */ + const char *op; + + /** + * Function to call to perform the operation. + */ + ReserveGetOpHandler handler; + + } h[] = { + { + .op = NULL, + .handler = &TEH_handler_reserves_get + }, + { + .op = "history", + .handler = &TEH_handler_reserves_history + }, + { + .op = NULL, + .handler = NULL + }, + }; + + if ( (NULL == args[0]) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &reserve_pub, + sizeof (reserve_pub))) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, + args[0]); + } + for (unsigned int i = 0; NULL != h[i].handler; i++) + { + if ( ( (NULL == args[1]) && + (NULL == h[i].op) ) || + ( (NULL != args[1]) && + (NULL != h[i].op) && + (0 == strcmp (h[i].op, + args[1])) ) ) + return h[i].handler (rc, + &reserve_pub); + } + return r404 (rc->connection, + args[1]); +} + + +/** * Signature of functions that handle operations on purses. * * @param connection HTTP request handle @@ -1546,8 +1625,9 @@ handle_mhd_request (void *cls, { .url = "reserves", .method = MHD_HTTP_METHOD_GET, - .handler.get = &TEH_handler_reserves_get, - .nargs = 1 + .handler.get = &handle_get_reserves, + .nargs = 2, + .nargs_is_upper_bound = true }, { .url = "reserves", diff --git a/src/exchange/taler-exchange-httpd_coins_get.h b/src/exchange/taler-exchange-httpd_coins_get.h index 712269c3b..90405b55d 100644 --- a/src/exchange/taler-exchange-httpd_coins_get.h +++ b/src/exchange/taler-exchange-httpd_coins_get.h @@ -37,7 +37,7 @@ TEH_reserves_get_cleanup (void); /** - * Handle a GET "/coins/$COIN_PUB" request. Parses the + * Handle a GET "/coins/$COIN_PUB/history" request. Parses the * given "coins_pub" in @a args (which should contain the * EdDSA public key of a reserve) and then respond with the * transaction history of the coin. diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c index a46886ef4..0775a4c65 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get.c +++ b/src/exchange/taler-exchange-httpd_reserves_get.c @@ -170,8 +170,9 @@ db_event_cb (void *cls, MHD_RESULT -TEH_handler_reserves_get (struct TEH_RequestContext *rc, - const char *const args[1]) +TEH_handler_reserves_get ( + struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub) { struct ReservePoller *rp = rc->rh_ctx; @@ -185,18 +186,7 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc, GNUNET_CONTAINER_DLL_insert (rp_head, rp_tail, rp); - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[0], - strlen (args[0]), - &rp->reserve_pub, - sizeof (rp->reserve_pub))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, - args[0]); - } + rp->reserve_pub = *reserve_pub; TALER_MHD_parse_request_timeout (rc->connection, &rp->timeout); } @@ -254,7 +244,7 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc, return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, - args[0]); + NULL); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Long-polling on reserve for %s\n", diff --git a/src/exchange/taler-exchange-httpd_reserves_get.h b/src/exchange/taler-exchange-httpd_reserves_get.h index 30c6559f6..6c453d0cd 100644 --- a/src/exchange/taler-exchange-httpd_reserves_get.h +++ b/src/exchange/taler-exchange-httpd_reserves_get.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -43,11 +43,12 @@ TEH_reserves_get_cleanup (void); * status of the reserve. * * @param rc request context - * @param args array of additional options (length: 1, just the reserve_pub) + * @param reserve_pub public key of the reserve * @return MHD result code */ MHD_RESULT -TEH_handler_reserves_get (struct TEH_RequestContext *rc, - const char *const args[1]); +TEH_handler_reserves_get ( + struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub); #endif diff --git a/src/exchange/taler-exchange-httpd_reserves_history.c b/src/exchange/taler-exchange-httpd_reserves_history.c index 1bf73cb25..0c692c8fd 100644 --- a/src/exchange/taler-exchange-httpd_reserves_history.c +++ b/src/exchange/taler-exchange-httpd_reserves_history.c @@ -23,7 +23,6 @@ #include "platform.h" #include <gnunet/gnunet_util_lib.h> #include <jansson.h> -#include "taler_mhd_lib.h" #include "taler_json_lib.h" #include "taler_dbevents.h" #include "taler-exchange-httpd_keys.h" @@ -54,6 +53,11 @@ struct ReserveHistoryContext struct TALER_EXCHANGEDB_ReserveHistory *rh; /** + * Requested startin offset for the reserve history. + */ + uint64_t start_off; + + /** * Current reserve balance. */ struct TALER_Amount balance; @@ -415,7 +419,7 @@ reserve_history_transaction (void *cls, qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, rsc->reserve_pub, - 0, + rsc->start_off, &rsc->balance, &rsc->rh); if (GNUNET_DB_STATUS_HARD_ERROR == qs) @@ -432,40 +436,28 @@ reserve_history_transaction (void *cls, MHD_RESULT -TEH_handler_reserves_history (struct TEH_RequestContext *rc, - const struct TALER_ReservePublicKeyP *reserve_pub, - const json_t *root) +TEH_handler_reserves_history ( + struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub) { - struct ReserveHistoryContext rsc; + struct ReserveHistoryContext rsc = { + .reserve_pub = reserve_pub + }; MHD_RESULT mhd_ret; - uint64_t start_off = 0; struct TALER_ReserveSignatureP reserve_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &reserve_sig), - GNUNET_JSON_spec_end () - }; - + bool required = true; + + TALER_MHD_parse_request_header_auto (rc->connection, + TALER_RESERVE_HISTORY_SIGNATURE_HEADER, + &reserve_sig, + required); + TALER_MHD_parse_request_number (rc->connection, + "start", + &rsc.start_off); rsc.reserve_pub = reserve_pub; - { - enum GNUNET_GenericReturnValue res; - res = TALER_MHD_parse_json_data (rc->connection, - root, - spec); - if (GNUNET_SYSERR == res) - { - GNUNET_break (0); - return MHD_NO; /* hard failure */ - } - if (GNUNET_NO == res) - { - GNUNET_break_op (0); - return MHD_YES; /* failure */ - } - } if (GNUNET_OK != - TALER_wallet_reserve_history_verify (start_off, + TALER_wallet_reserve_history_verify (rsc.start_off, reserve_pub, &reserve_sig)) { diff --git a/src/exchange/taler-exchange-httpd_reserves_history.h b/src/exchange/taler-exchange-httpd_reserves_history.h index 9a2a93782..e1bd7ae1b 100644 --- a/src/exchange/taler-exchange-httpd_reserves_history.h +++ b/src/exchange/taler-exchange-httpd_reserves_history.h @@ -15,7 +15,7 @@ */ /** * @file taler-exchange-httpd_reserves_history.h - * @brief Handle /reserves/$RESERVE_PUB HISTORY requests + * @brief Handle /reserves/$RESERVE_PUB/history requests * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff @@ -24,20 +24,20 @@ #define TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H #include <microhttpd.h> +#include "taler_mhd_lib.h" #include "taler-exchange-httpd.h" /** - * Handle a POST "/reserves/$RID/history" request. + * Handle a GET "/reserves/$RID/history" request. * * @param rc request context * @param reserve_pub public key of the reserve - * @param root uploaded body from the client * @return MHD result code */ MHD_RESULT -TEH_handler_reserves_history (struct TEH_RequestContext *rc, - const struct TALER_ReservePublicKeyP *reserve_pub, - const json_t *root); +TEH_handler_reserves_history ( + struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub); #endif diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h index 3e55f0b07..877e8878f 100644 --- a/src/exchange/taler-exchange-httpd_responses.h +++ b/src/exchange/taler-exchange-httpd_responses.h @@ -24,13 +24,13 @@ */ #ifndef TALER_EXCHANGE_HTTPD_RESPONSES_H #define TALER_EXCHANGE_HTTPD_RESPONSES_H + #include <gnunet/gnunet_util_lib.h> #include <jansson.h> #include <microhttpd.h> #include "taler_error_codes.h" #include "taler-exchange-httpd.h" #include "taler-exchange-httpd_db.h" -#include <gnunet/gnunet_mhd_compat.h> /** diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 77d4f2bad..f973cfb0d 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -3778,65 +3778,6 @@ TALER_EXCHANGE_deposits_get_cancel ( struct TALER_EXCHANGE_DepositGetHandle *dwh); -/** - * Convenience function. Verifies a coin's transaction history as - * returned by the exchange. - * - * @param dk fee structure for the coin - * @param coin_pub public key of the coin - * @param history history of the coin in json encoding - * @param[out] total how much of the coin has been spent according to @a history - * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not - */ -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_verify_coin_history ( - const struct TALER_EXCHANGE_DenomPublicKey *dk, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const json_t *history, - struct TALER_Amount *total); - - -/** - * Parse history given in JSON format and return it in binary - * format. - * - * @param keys exchange keys - * @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] total_in set to value of credits to reserve - * @param[out] total_out set to value of debits from reserve - * @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 - */ -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_parse_reserve_history ( - const struct TALER_EXCHANGE_Keys *keys, - const json_t *history, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *currency, - struct TALER_Amount *total_in, - struct TALER_Amount *total_out, - unsigned int history_length, - struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]); - - -/** - * Free memory (potentially) allocated by #TALER_EXCHANGE_parse_reserve_history(). - * - * @param len number of entries in @a rhistory - * @param[in] rhistory result to free - */ -void -TALER_EXCHANGE_free_reserve_history ( - unsigned int len, - struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]); - - /* ********************* /recoup *********************** */ diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h index e4aa916e7..60c5b209d 100644 --- a/src/include/taler_mhd_lib.h +++ b/src/include/taler_mhd_lib.h @@ -477,6 +477,50 @@ TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection, /** + * Extract optional numeric limit argument from request. + * + * @param connection the MHD connection + * @param name name of the query parameter + * @param[out] off set to the offet, unchanged if the + * option was not given + * @return #GNUNET_OK on success, + * #GNUNET_NO if an error was returned on @a connection (caller should return #MHD_YES) and + * #GNUNET_SYSERR if we failed to return an error (caller should return #MHD_NO) + */ +enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection, + const char *name, + uint64_t *off); + + +/** + * Extract optional numeric argument from request. + * Macro that *returns* #MHD_YES/#MHD_NO if the + * requested argument existed but failed to parse. + * + * @param connection the MHD connection + * @param name name of the argument to parse + * @param[out] off set to the given numeric value, + * unchanged if value was not specified + */ +#define TALER_MHD_parse_request_number(connection,name,off) \ + do { \ + switch (TALER_MHD_parse_request_arg_number (connection, \ + name, \ + off)) \ + { \ + case GNUNET_SYSERR: \ + GNUNET_break (0); \ + return MHD_NO; \ + case GNUNET_NO: \ + GNUNET_break_op (0); \ + case GNUNET_OK: \ + break; \ + } \ + } while (0) + + +/** * Extract fixed-size base32crockford encoded data from request argument. * * Queues an error response to the connection if the parameter is missing or diff --git a/src/include/taler_util.h b/src/include/taler_util.h index 8762f7dc5..77cb58591 100644 --- a/src/include/taler_util.h +++ b/src/include/taler_util.h @@ -88,6 +88,16 @@ #define TALER_AML_OFFICER_SIGNATURE_HEADER "Taler-AML-Officer-Signature" /** + * Header with signature for reserve history requests. + */ +#define TALER_RESERVE_HISTORY_SIGNATURE_HEADER "Taler-Reserve-History-Signature" + +/** + * Header with signature for coin history requests. + */ +#define TALER_COIN_HISTORY_SIGNATURE_HEADER "Taler-Coin-History-Signature" + +/** * Log an error message at log-level 'level' that indicates * a failure of the command 'cmd' with the message given * by gcry_strerror(rc). diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c index f47736c08..c78d3cc57 100644 --- a/src/lib/exchange_api_age_withdraw.c +++ b/src/lib/exchange_api_age_withdraw.c @@ -357,108 +357,6 @@ reserve_age_withdraw_ok ( /** - * FIXME: This function should be common to batch- and age-withdraw - * - * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/age-withdraw operation. - * Check the signatures on the batch 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 keys The denomination keys from the exchange - * @param reserve_pub The reserve's public key - * @param requested_amount The requested amount - * @param json reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static enum GNUNET_GenericReturnValue -reserve_age_withdraw_payment_required ( - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_Amount *requested_amount, - const json_t *json) -{ - struct TALER_Amount balance; - struct TALER_Amount total_in_from_history; - struct TALER_Amount total_out_from_history; - json_t *history; - size_t len; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("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_ReserveHistoryEntry *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_ReserveHistoryEntry) - * len); - if (NULL == rhistory) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - TALER_EXCHANGE_parse_reserve_history ( - keys, - history, - reserve_pub, - balance.currency, - &total_in_from_history, - &total_out_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - TALER_EXCHANGE_free_reserve_history (len, - rhistory); - return GNUNET_SYSERR; - } - TALER_EXCHANGE_free_reserve_history (len, - rhistory); - } - - /* 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/age-withdraw request. * @@ -544,29 +442,7 @@ handle_reserve_age_withdraw_blinded_finished ( case MHD_HTTP_CONFLICT: /* The age requirements might not have been met */ awbr.hr.ec = TALER_JSON_get_error_code (j_response); - if (TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE == awbr.hr.ec) - { - awbr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - } - - /* The exchange says that the reserve has insufficient funds; - check the signatures in the history... */ - if (GNUNET_OK != - reserve_age_withdraw_payment_required (awbh->keys, - &awbh->reserve_pub, - &awbh->amount_with_fee, - j_response)) - { - GNUNET_break_op (0); - awbr.hr.http_status = 0; - awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - else - { - awbr.hr.ec = TALER_JSON_get_error_code (j_response); - awbr.hr.hint = TALER_JSON_get_error_hint (j_response); - } + awbr.hr.hint = TALER_JSON_get_error_hint (j_response); break; case MHD_HTTP_GONE: /* could happen if denomination was revoked */ diff --git a/src/lib/exchange_api_batch_withdraw2.c b/src/lib/exchange_api_batch_withdraw2.c index 1f59a6980..12c6aeff3 100644 --- a/src/lib/exchange_api_batch_withdraw2.c +++ b/src/lib/exchange_api_batch_withdraw2.c @@ -155,102 +155,6 @@ reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh, /** - * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/batch-withdraw operation. - * Check the signatures on the batch 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 enum GNUNET_GenericReturnValue -reserve_batch_withdraw_payment_required ( - struct TALER_EXCHANGE_BatchWithdraw2Handle *wh, - const json_t *json) -{ - struct TALER_Amount balance; - struct TALER_Amount total_in_from_history; - struct TALER_Amount total_out_from_history; - json_t *history; - size_t len; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("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_ReserveHistoryEntry *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_ReserveHistoryEntry) - * len); - if (NULL == rhistory) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - TALER_EXCHANGE_parse_reserve_history ( - wh->keys, - history, - &wh->reserve_pub, - balance.currency, - &total_in_from_history, - &total_out_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - TALER_EXCHANGE_free_reserve_history (len, - rhistory); - return GNUNET_SYSERR; - } - TALER_EXCHANGE_free_reserve_history (len, - rhistory); - } - - /* Check that funds were really insufficient */ - if (0 >= TALER_amount_cmp (&wh->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/batch-withdraw request. * @@ -334,21 +238,8 @@ handle_reserve_batch_withdraw_finished (void *cls, bwr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_CONFLICT: - /* The exchange says that the reserve has insufficient funds; - check the signatures in the history... */ - if (GNUNET_OK != - reserve_batch_withdraw_payment_required (wh, - j)) - { - GNUNET_break_op (0); - bwr.hr.http_status = 0; - bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - else - { - bwr.hr.ec = TALER_JSON_get_error_code (j); - bwr.hr.hint = TALER_JSON_get_error_hint (j); - } + bwr.hr.ec = TALER_JSON_get_error_code (j); + bwr.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_GONE: /* could happen if denomination was revoked */ diff --git a/src/lib/exchange_api_coins_history.c b/src/lib/exchange_api_coins_history.c new file mode 100644 index 000000000..ae718c459 --- /dev/null +++ b/src/lib/exchange_api_coins_history.c @@ -0,0 +1,1290 @@ +/* + This file is part of TALER + Copyright (C) 2014-2023 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_coins_history.c + * @brief Implementation of the POST /coins/$COIN_PUB/history requests + * @author Christian Grothoff + * + * NOTE: this is an incomplete draft, never finished! + */ +#include "platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP history 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 /coins/$RID/history Handle + */ +struct TALER_EXCHANGE_CoinsHistoryHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_CoinsHistoryCallback cb; + + /** + * Public key of the coin we are querying. + */ + struct TALER_CoinPublicKeyP coin_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Context for coin helpers. + */ +struct CoinHistoryParseContext +{ + + /** + * Denomination of the coin. + */ + const struct TALER_EXCHANGE_DenomPublicKey *dk; + + /** + * Our coin public key. + */ + const struct TALER_CoinSpendPublicKeyP *coin_pub; + + /** + * Where to sum up total refunds. + */ + struct TALER_Amount rtotal; + + /** + * Total amount encountered. + */ + struct TALER_Amount *total; + +}; + + +/** + * Signature of functions that operate on one of + * the coin's history entries. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +typedef enum GNUNET_GenericReturnValue +(*CoinCheckHelper)(struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction); + + +/** + * Handle deposit entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_deposit (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct TALER_MerchantWireHashP h_wire; + struct TALER_PrivateContractHashP h_contract_terms; + struct TALER_ExtensionPolicyHashP h_policy; + bool no_h_policy; + struct GNUNET_HashCode wallet_data_hash; + bool no_wallet_data_hash; + struct GNUNET_TIME_Timestamp wallet_timestamp; + struct TALER_MerchantPublicKeyP merchant_pub; + struct GNUNET_TIME_Timestamp refund_deadline = {0}; + struct TALER_CoinSpendSignatureP sig; + struct TALER_AgeCommitmentHash hac; + bool no_hac; + struct TALER_Amount deposit_fee; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &h_contract_terms), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("wallet_data_hash", + &wallet_data_hash), + &no_wallet_data_hash), + GNUNET_JSON_spec_fixed_auto ("h_wire", + &h_wire), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &hac), + &no_hac), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_policy", + &h_policy), + &no_h_policy), + GNUNET_JSON_spec_timestamp ("timestamp", + &wallet_timestamp), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("refund_deadline", + &refund_deadline), + NULL), + TALER_JSON_spec_amount_any ("deposit_fee", + &deposit_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &merchant_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_deposit_verify ( + amount, + &deposit_fee, + &h_wire, + &h_contract_terms, + no_wallet_data_hash ? NULL : &wallet_data_hash, + no_hac ? NULL : &hac, + no_h_policy ? NULL : &h_policy, + &pc->dk->h_key, + wallet_timestamp, + &merchant_pub, + refund_deadline, + pc->coin_pub, + &sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check that deposit fee matches our expectations from /keys! */ + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&deposit_fee, + &pc->dk->fees.deposit)) || + (0 != + TALER_amount_cmp (&deposit_fee, + &pc->dk->fees.deposit)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Handle melt entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_melt (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct TALER_CoinSpendSignatureP sig; + struct TALER_RefreshCommitmentP rc; + struct TALER_AgeCommitmentHash h_age_commitment; + bool no_hac; + struct TALER_Amount melt_fee; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("rc", + &rc), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &h_age_commitment), + &no_hac), + TALER_JSON_spec_amount_any ("melt_fee", + &melt_fee), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* check that melt fee matches our expectations from /keys! */ + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&melt_fee, + &pc->dk->fees.refresh)) || + (0 != + TALER_amount_cmp (&melt_fee, + &pc->dk->fees.refresh)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_melt_verify ( + amount, + &melt_fee, + &rc, + &pc->dk->h_key, + no_hac + ? NULL + : &h_age_commitment, + pc->coin_pub, + &sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Handle refund entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_refund (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct TALER_PrivateContractHashP h_contract_terms; + struct TALER_MerchantPublicKeyP merchant_pub; + struct TALER_MerchantSignatureP sig; + struct TALER_Amount refund_fee; + struct TALER_Amount sig_amount; + uint64_t rtransaction_id; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("refund_fee", + &refund_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &merchant_pub), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rtransaction_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (&sig_amount, + &refund_fee, + amount)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_merchant_refund_verify (pc->coin_pub, + &h_contract_terms, + rtransaction_id, + &sig_amount, + &merchant_pub, + &sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* NOTE: theoretically, we could also check that the given + merchant_pub and h_contract_terms appear in the + history under deposits. However, there is really no benefit + for the exchange to lie here, so not checking is probably OK + (an auditor ought to check, though). Then again, we similarly + had no reason to check the merchant's signature (other than a + well-formendess check). */ + + /* check that refund fee matches our expectations from /keys! */ + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&refund_fee, + &pc->dk->fees.refund)) || + (0 != + TALER_amount_cmp (&refund_fee, + &pc->dk->fees.refund)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Handle recoup entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_recoup (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct TALER_ReservePublicKeyP reserve_pub; + struct GNUNET_TIME_Timestamp timestamp; + union TALER_DenominationBlindingKeyP coin_bks; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_CoinSpendSignatureP coin_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 ("reserve_pub", + &reserve_pub), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &coin_sig), + GNUNET_JSON_spec_fixed_auto ("coin_blind", + &coin_bks), + GNUNET_JSON_spec_timestamp ("timestamp", + ×tamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_recoup_verify ( + timestamp, + amount, + pc->coin_pub, + &reserve_pub, + &exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_recoup_verify (&pc->dk->h_key, + &coin_bks, + pc->coin_pub, + &coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Handle recoup-refresh entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_recoup_refresh (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + /* This is the coin that was subjected to a recoup, + the value being credited to the old coin. */ + struct TALER_CoinSpendPublicKeyP old_coin_pub; + union TALER_DenominationBlindingKeyP coin_bks; + struct GNUNET_TIME_Timestamp timestamp; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_CoinSpendSignatureP coin_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 ("coin_sig", + &coin_sig), + GNUNET_JSON_spec_fixed_auto ("old_coin_pub", + &old_coin_pub), + GNUNET_JSON_spec_fixed_auto ("coin_blind", + &coin_bks), + GNUNET_JSON_spec_timestamp ("timestamp", + ×tamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_recoup_refresh_verify ( + timestamp, + amount, + pc->coin_pub, + &old_coin_pub, + &exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_recoup_verify (&pc->dk->h_key, + &coin_bks, + pc->coin_pub, + &coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Handle old coin recoup entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_old_coin_recoup (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + /* This is the coin that was credited in a recoup, + the value being credited to the this coin. */ + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_CoinSpendPublicKeyP new_coin_pub; + struct GNUNET_TIME_Timestamp timestamp; + 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 ("coin_pub", + &new_coin_pub), + GNUNET_JSON_spec_timestamp ("timestamp", + ×tamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_recoup_refresh_verify ( + timestamp, + amount, + &new_coin_pub, + pc->coin_pub, + &exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Handle purse deposit entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_purse_deposit (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct TALER_PurseContractPublicKeyP purse_pub; + struct TALER_CoinSpendSignatureP coin_sig; + const char *exchange_base_url; + bool refunded; + struct TALER_AgeCommitmentHash phac = { 0 }; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("purse_pub", + &purse_pub), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &coin_sig), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &coin_sig), + NULL), + GNUNET_JSON_spec_string ("exchange_base_url", + &exchange_base_url), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &phac), + NULL), + GNUNET_JSON_spec_bool ("refunded", + &refunded), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_purse_deposit_verify ( + exchange_base_url, + &purse_pub, + amount, + &pc->dk->h_key, + &phac, + pc->coin_pub, + &coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (refunded) + { + /* We wave the deposit fee. */ + if (0 > + TALER_amount_add (&pc->rtotal, + &pc->rtotal, + &pc->dk->fees.deposit)) + { + /* overflow in refund history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_YES; +} + + +/** + * Handle purse refund entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_purse_refund (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct TALER_PurseContractPublicKeyP purse_pub; + struct TALER_Amount refund_fee; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("refund_fee", + &refund_fee), + GNUNET_JSON_spec_fixed_auto ("purse_pub", + &purse_pub), + 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 (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_purse_refund_verify ( + amount, + &refund_fee, + pc->coin_pub, + &purse_pub, + &exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&refund_fee, + &pc->dk->fees.refund)) || + (0 != + TALER_amount_cmp (&refund_fee, + &pc->dk->fees.refund)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Handle reserve deposit entry in the coin's history. + * + * @param[in,out] pc overall context + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_reserve_open_deposit (struct CoinHistoryParseContext *pc, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct TALER_ReserveSignatureP reserve_sig; + struct TALER_CoinSpendSignatureP coin_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &reserve_sig), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &coin_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_reserve_open_deposit_verify ( + amount, + &reserve_sig, + pc->coin_pub, + &coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Convenience function. Verifies a coin's transaction history as + * returned by the exchange. + * + * FIXME: add support for partial histories! + * NOTE: this API will thus still change! + * + * @param dk fee structure for the coin + * @param coin_pub public key of the coin + * @param history history of the coin in json encoding + * @param[out] total how much of the coin has been spent according to @a history + * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not + */ +static enum GNUNET_GenericReturnValue +verify_coin_history ( + const struct TALER_EXCHANGE_DenomPublicKey *dk, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const json_t *history, + struct TALER_Amount *total) +{ + const char *currency = dk->value.currency; + const struct + { + const char *type; + CoinCheckHelper helper; + } map[] = { + { "DEPOSIT", &help_deposit }, + { "MELT", &help_melt }, + { "REFUND", &help_refund }, + { "RECOUP", &help_recoup }, + { "RECOUP-REFRESH", &help_recoup_refresh }, + { "OLD-COIN-RECOUP", &help_old_coin_recoup }, + { "PURSE-DEPOSIT", &help_purse_deposit }, + { "PURSE-REFUND", &help_purse_refund }, + { "RESERVE-OPEN-DEPOSIT", &help_reserve_open_deposit }, + { NULL, NULL } + }; + struct CoinHistoryParseContext pc = { + .dk = dk, + .coin_pub = coin_pub, + .total = total + }; + size_t len; + + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + len = json_array_size (history); + if (0 == len) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + total)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + &pc.rtotal)); + for (size_t off = 0; off<len; off++) + { + enum GNUNET_GenericReturnValue add; + json_t *transaction; + struct TALER_Amount amount; + const char *type; + struct GNUNET_JSON_Specification spec_glob[] = { + TALER_JSON_spec_amount_any ("amount", + &amount), + GNUNET_JSON_spec_string ("type", + &type), + GNUNET_JSON_spec_end () + }; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec_glob, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_YES != + TALER_amount_cmp_currency (&amount, + &pc.rtotal)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Operation of type %s with amount %s\n", + type, + TALER_amount2s (&amount)); + add = GNUNET_SYSERR; + for (unsigned int i = 0; NULL != map[i].type; i++) + { + if (0 == strcasecmp (type, + map[i].type)) + { + add = map[i].helper (&pc, + &amount, + transaction); + break; + } + } + switch (add) + { + case GNUNET_SYSERR: + /* entry type not supported, new version on server? */ + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected type `%s' in response\n", + type); + return GNUNET_SYSERR; + case GNUNET_YES: + /* This amount should be added to the total */ + if (0 > + TALER_amount_add (total, + total, + &amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + break; + case GNUNET_NO: + /* This amount should be subtracted from the total. + + However, for the implementation, we first *add* up all of + these negative amounts, as we might get refunds before + deposits from a semi-evil exchange. Then, at the end, we do + the subtraction by calculating "total = total - rtotal" */ + if (0 > + TALER_amount_add (&pc.rtotal, + &pc.rtotal, + &amount)) + { + /* overflow in refund history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + break; + } /* end of switch(add) */ + } + /* Finally, subtract 'rtotal' from total to handle the subtractions */ + if (0 > + TALER_amount_subtract (total, + total, + &pc.rtotal)) + { + /* underflow in history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify that @a coin_sig does NOT appear in + * the history of @a proof and thus whatever transaction + * is authorized by @a coin_sig is a conflict with + * @a proof. + * + * @param proof a proof to check + * @param coin_sig signature that must not be in @a proof + * @return #GNUNET_OK if @a coin_sig is not in @a proof + */ +// FIXME: should be used... +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_signature_conflict_ ( + const json_t *proof, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + json_t *history; + size_t off; + json_t *entry; + + history = json_object_get (proof, + "history"); + if (NULL == history) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_array_foreach (history, off, entry) + { + struct TALER_CoinSpendSignatureP cs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &cs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (entry, + spec, + NULL, NULL)) + continue; /* entry without coin signature */ + if (0 == + GNUNET_memcmp (&cs, + coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Check that the provided @a proof indeeds indicates + * a conflict for @a coin_pub. + * + * @param keys exchange keys + * @param proof provided conflict proof + * @param dk denomination of @a coin_pub that the client + * used + * @param coin_pub public key of the coin + * @param required balance required on the coin for the operation + * @return #GNUNET_OK if @a proof holds + */ +// FIXME: should be used! +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_conflict_ ( + const struct TALER_EXCHANGE_Keys *keys, + const json_t *proof, + const struct TALER_EXCHANGE_DenomPublicKey *dk, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *required) +{ + enum TALER_ErrorCode ec; + + ec = TALER_JSON_get_error_code (proof); + switch (ec) + { + case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: + /* Nothing to check anymore here, proof needs to be + checked in the GET /coins/$COIN_PUB handler */ + break; + case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: + // FIXME: write check! + break; + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * We received an #MHD_HTTP_OK history code. Handle the JSON + * response. + * + * @param rsh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh, + const json_t *j) +{ + const json_t *history; + unsigned int len; + struct TALER_EXCHANGE_CoinHistory rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK + }; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("balance", + &rs.details.ok.balance), + GNUNET_JSON_spec_array_const ("history", + &history), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + len = json_array_size (history); + { + struct TALER_EXCHANGE_CoinHistoryEntry *rhistory; + + rhistory = GNUNET_new_array (len, + struct TALER_EXCHANGE_CoinHistoryEntry); + if (GNUNET_OK != + parse_coin_history (rsh->keys, + history, + &rsh->coin_pub, + rs.details.ok.balance.currency, + &rs.details.ok.total_in, + &rs.details.ok.total_out, + len, + rhistory)) + { + GNUNET_break_op (0); + free_coin_history (len, + rhistory); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + if (NULL != rsh->cb) + { + rs.details.ok.history = rhistory; + rs.details.ok.history_len = len; + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + } + free_coin_history (len, + rhistory); + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /coins/$RID/history request. + * + * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_coins_history_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_CoinHistory rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rsh->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_coins_history_ok (rsh, + j)) + { + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + 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 */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for coins history\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != rsh->cb) + { + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + } + TALER_EXCHANGE_coins_history_cancel (rsh); +} + + +struct TALER_EXCHANGE_CoinsHistoryHandle * +TALER_EXCHANGE_coins_history ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_CoinPrivateKeyP *coin_priv, + uint64_t start_off, + TALER_EXCHANGE_CoinsHistoryCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_CoinsHistoryHandle *rsh; + CURL *eh; + char arg_str[sizeof (struct TALER_CoinPublicKeyP) * 2 + 64]; + struct curl_slist *job_headers; + + rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle); + rsh->cb = cb; + rsh->cb_cls = cb_cls; + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &rsh->coin_pub.eddsa_pub); + { + char pub_str[sizeof (struct TALER_CoinPublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &rsh->coin_pub, + sizeof (rsh->coin_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + if (0 != start_off) + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "coins/%s/history?start=%llu", + pub_str, + (unsigned long long) start_off); + else + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "coins/%s/history", + pub_str); + } + rsh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rsh->url) + { + GNUNET_free (rsh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (rsh->url); + GNUNET_free (rsh); + return NULL; + } + + { + struct TALER_CoinSignatureP coin_sig; + char *sig_hdr; + char *hdr; + + TALER_wallet_coin_history_sign (start_off, + coin_priv, + &coin_sig); + + sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( + &coin_sig, + sizeof (coin_sig)); + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_COIN_HISTORY_SIGNATURE_HEADER, + sig_hdr); + GNUNET_free (sig_hdr); + job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + if (NULL == job_headers) + { + GNUNET_break (0); + return NULL; + } + } + + rsh->keys = TALER_EXCHANGE_keys_incref (keys); + rsh->job = GNUNET_CURL_job_add2 (ctx, + eh, + job_headers, + &handle_coins_history_finished, + rsh); + curl_slist_free_all (job_headers); + return rsh; +} + + +void +TALER_EXCHANGE_coins_history_cancel ( + struct TALER_EXCHANGE_CoinsHistoryHandle *rsh) +{ + if (NULL != rsh->job) + { + GNUNET_CURL_job_cancel (rsh->job); + rsh->job = NULL; + } + TALER_curl_easy_post_finished (&rsh->post_ctx); + GNUNET_free (rsh->url); + TALER_EXCHANGE_keys_decref (rsh->keys); + GNUNET_free (rsh); +} + + +/* end of exchange_api_coins_history.c */ diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 3ad880171..4846e1188 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015-2022 Taler Systems SA + Copyright (C) 2015-2023 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 @@ -27,1542 +27,6 @@ #include "taler_signatures.h" -/** - * Context for history entry helpers. - */ -struct HistoryParseContext -{ - - /** - * Keys of the exchange we use. - */ - const struct TALER_EXCHANGE_Keys *keys; - - /** - * Our reserve public key. - */ - const struct TALER_ReservePublicKeyP *reserve_pub; - - /** - * Array of UUIDs. - */ - struct GNUNET_HashCode *uuids; - - /** - * Where to sum up total inbound amounts. - */ - struct TALER_Amount *total_in; - - /** - * Where to sum up total outbound amounts. - */ - struct TALER_Amount *total_out; - - /** - * Number of entries already used in @e uuids. - */ - unsigned int uuid_off; -}; - - -/** - * Type of a function called to parse a reserve history - * entry @a rh. - * - * @param[in,out] rh where to write the result - * @param[in,out] uc UUID context for duplicate detection - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -typedef enum GNUNET_GenericReturnValue -(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction); - - -/** - * Parse "credit" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - const char *wire_url; - uint64_t wire_reference; - struct GNUNET_TIME_Timestamp timestamp; - struct GNUNET_JSON_Specification withdraw_spec[] = { - GNUNET_JSON_spec_uint64 ("wire_reference", - &wire_reference), - GNUNET_JSON_spec_timestamp ("timestamp", - ×tamp), - GNUNET_JSON_spec_string ("sender_account_url", - &wire_url), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_CREDIT; - if (0 > - TALER_amount_add (uc->total_in, - uc->total_in, - &rh->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; - } - rh->details.in_details.sender_url = GNUNET_strdup (wire_url); - rh->details.in_details.wire_reference = wire_reference; - rh->details.in_details.timestamp = timestamp; - return GNUNET_OK; -} - - -/** - * Parse "credit" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - struct TALER_ReserveSignatureP sig; - struct TALER_DenominationHashP h_denom_pub; - struct TALER_BlindedCoinHashP bch; - struct TALER_Amount withdraw_fee; - struct GNUNET_JSON_Specification withdraw_spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &sig), - TALER_JSON_spec_amount_any ("withdraw_fee", - &withdraw_fee), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - &h_denom_pub), - GNUNET_JSON_spec_fixed_auto ("h_coin_envelope", - &bch), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - withdraw_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* Check that the signature is a valid withdraw request */ - if (GNUNET_OK != - TALER_wallet_withdraw_verify (&h_denom_pub, - &rh->amount, - &bch, - uc->reserve_pub, - &sig)) - { - 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; - - key_state = uc->keys; - dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, - &h_denom_pub); - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&withdraw_fee, - &dki->fees.withdraw)) || - (0 != - TALER_amount_cmp (&withdraw_fee, - &dki->fees.withdraw)) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - rh->details.withdraw.fee = withdraw_fee; - } - rh->details.withdraw.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 - signatures, and compare the hashes to find - duplicates. */ - GNUNET_CRYPTO_hash (&sig, - sizeof (sig), - &uc->uuids[uc->uuid_off]); - for (unsigned int i = 0; i<uc->uuid_off; i++) - { - if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off], - &uc->uuids[i])) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - } - uc->uuid_off++; - - if (0 > - TALER_amount_add (uc->total_out, - uc->total_out, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Parse "recoup" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification recoup_spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rh->details.recoup_details.coin_pub), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rh->details.recoup_details.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rh->details.recoup_details.exchange_pub), - GNUNET_JSON_spec_timestamp ("timestamp", - &rh->details.recoup_details.timestamp), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_RECOUP; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - recoup_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = uc->keys; - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - &rh->details. - recoup_details.exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_recoup_verify ( - rh->details.recoup_details.timestamp, - &rh->amount, - &rh->details.recoup_details.coin_pub, - uc->reserve_pub, - &rh->details.recoup_details.exchange_pub, - &rh->details.recoup_details.exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (uc->total_in, - uc->total_in, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Parse "closing" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification closing_spec[] = { - GNUNET_JSON_spec_string ( - "receiver_account_details", - &rh->details.close_details.receiver_account_details), - GNUNET_JSON_spec_fixed_auto ("wtid", - &rh->details.close_details.wtid), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rh->details.close_details.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rh->details.close_details.exchange_pub), - TALER_JSON_spec_amount_any ("closing_fee", - &rh->details.close_details.fee), - GNUNET_JSON_spec_timestamp ("timestamp", - &rh->details.close_details.timestamp), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_CLOSING; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - closing_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = uc->keys; - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key ( - key_state, - &rh->details.close_details.exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_reserve_closed_verify ( - rh->details.close_details.timestamp, - &rh->amount, - &rh->details.close_details.fee, - rh->details.close_details.receiver_account_details, - &rh->details.close_details.wtid, - uc->reserve_pub, - &rh->details.close_details.exchange_pub, - &rh->details.close_details.exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (uc->total_out, - uc->total_out, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Parse "merge" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - uint32_t flags32; - struct GNUNET_JSON_Specification merge_spec[] = { - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &rh->details.merge_details.h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("merge_pub", - &rh->details.merge_details.merge_pub), - GNUNET_JSON_spec_fixed_auto ("purse_pub", - &rh->details.merge_details.purse_pub), - GNUNET_JSON_spec_uint32 ("min_age", - &rh->details.merge_details.min_age), - GNUNET_JSON_spec_uint32 ("flags", - &flags32), - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &rh->details.merge_details.reserve_sig), - TALER_JSON_spec_amount_any ("purse_fee", - &rh->details.merge_details.purse_fee), - GNUNET_JSON_spec_timestamp ("merge_timestamp", - &rh->details.merge_details.merge_timestamp), - GNUNET_JSON_spec_timestamp ("purse_expiration", - &rh->details.merge_details.purse_expiration), - GNUNET_JSON_spec_bool ("merged", - &rh->details.merge_details.merged), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_MERGE; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - merge_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rh->details.merge_details.flags = - (enum TALER_WalletAccountMergeFlags) flags32; - if (GNUNET_OK != - TALER_wallet_account_merge_verify ( - rh->details.merge_details.merge_timestamp, - &rh->details.merge_details.purse_pub, - rh->details.merge_details.purse_expiration, - &rh->details.merge_details.h_contract_terms, - &rh->amount, - &rh->details.merge_details.purse_fee, - rh->details.merge_details.min_age, - rh->details.merge_details.flags, - uc->reserve_pub, - &rh->details.merge_details.reserve_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (rh->details.merge_details.merged) - { - if (0 > - TALER_amount_add (uc->total_in, - uc->total_in, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - else - { - if (0 > - TALER_amount_add (uc->total_out, - uc->total_out, - &rh->details.merge_details.purse_fee)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Parse "open" reserve open entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - struct GNUNET_JSON_Specification open_spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &rh->details.open_request.reserve_sig), - TALER_JSON_spec_amount_any ("open_payment", - &rh->details.open_request.reserve_payment), - GNUNET_JSON_spec_uint32 ("requested_min_purses", - &rh->details.open_request.purse_limit), - GNUNET_JSON_spec_timestamp ("request_timestamp", - &rh->details.open_request.request_timestamp), - GNUNET_JSON_spec_timestamp ("requested_expiration", - &rh->details.open_request.reserve_expiration), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_OPEN; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - open_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_reserve_open_verify ( - &rh->amount, - rh->details.open_request.request_timestamp, - rh->details.open_request.reserve_expiration, - rh->details.open_request.purse_limit, - uc->reserve_pub, - &rh->details.open_request.reserve_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (uc->total_out, - uc->total_out, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Parse "close" reserve close entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - struct GNUNET_JSON_Specification close_spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &rh->details.close_request.reserve_sig), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_payto", - &rh->details.close_request. - target_account_h_payto), - NULL), - GNUNET_JSON_spec_timestamp ("request_timestamp", - &rh->details.close_request.request_timestamp), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_CLOSE; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - close_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* force amount to invalid */ - memset (&rh->amount, - 0, - sizeof (rh->amount)); - if (GNUNET_OK != - TALER_wallet_reserve_close_verify ( - rh->details.close_request.request_timestamp, - &rh->details.close_request.target_account_h_payto, - uc->reserve_pub, - &rh->details.close_request.reserve_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_parse_reserve_history ( - const struct TALER_EXCHANGE_Keys *keys, - const json_t *history, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *currency, - struct TALER_Amount *total_in, - struct TALER_Amount *total_out, - unsigned int history_length, - struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]) -{ - const struct - { - const char *type; - ParseHelper helper; - } map[] = { - { "CREDIT", &parse_credit }, - { "WITHDRAW", &parse_withdraw }, - { "RECOUP", &parse_recoup }, - { "MERGE", &parse_merge }, - { "CLOSING", &parse_closing }, - { "OPEN", &parse_open }, - { "CLOSE", &parse_close }, - { NULL, NULL } - }; - struct GNUNET_HashCode uuid[history_length]; - struct HistoryParseContext uc = { - .keys = keys, - .reserve_pub = reserve_pub, - .uuids = uuid, - .total_in = total_in, - .total_out = total_out - }; - - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (currency, - total_in)); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (currency, - total_out)); - for (unsigned int off = 0; off<history_length; off++) - { - struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[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_any ("amount", - &amount), - /* 'wire' and 'signature' are optional depending on 'type'! */ - GNUNET_JSON_spec_end () - }; - bool found = false; - - transaction = json_array_get (history, - off); - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - hist_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - json_dumpf (transaction, - stderr, - JSON_INDENT (2)); - return GNUNET_SYSERR; - } - rh->amount = amount; - if (GNUNET_YES != - TALER_amount_cmp_currency (&amount, - total_in)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - for (unsigned int i = 0; NULL != map[i].type; i++) - { - if (0 == strcasecmp (map[i].type, - type)) - { - found = true; - if (GNUNET_OK != - map[i].helper (rh, - &uc, - transaction)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - break; - } - } - if (! found) - { - /* unexpected 'type', protocol incompatibility, complain! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -void -TALER_EXCHANGE_free_reserve_history ( - unsigned int len, - struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]) -{ - for (unsigned int i = 0; i<len; i++) - { - switch (rhistory[i].type) - { - case TALER_EXCHANGE_RTT_CREDIT: - GNUNET_free (rhistory[i].details.in_details.sender_url); - break; - case TALER_EXCHANGE_RTT_WITHDRAWAL: - break; - case TALER_EXCHANGE_RTT_AGEWITHDRAWAL: - break; - case TALER_EXCHANGE_RTT_RECOUP: - break; - case TALER_EXCHANGE_RTT_CLOSING: - break; - case TALER_EXCHANGE_RTT_MERGE: - break; - case TALER_EXCHANGE_RTT_OPEN: - break; - case TALER_EXCHANGE_RTT_CLOSE: - break; - } - } - GNUNET_free (rhistory); -} - - -/** - * Context for coin helpers. - */ -struct CoinHistoryParseContext -{ - - /** - * Denomination of the coin. - */ - const struct TALER_EXCHANGE_DenomPublicKey *dk; - - /** - * Our coin public key. - */ - const struct TALER_CoinSpendPublicKeyP *coin_pub; - - /** - * Where to sum up total refunds. - */ - struct TALER_Amount rtotal; - - /** - * Total amount encountered. - */ - struct TALER_Amount *total; - -}; - - -/** - * Signature of functions that operate on one of - * the coin's history entries. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -typedef enum GNUNET_GenericReturnValue -(*CoinCheckHelper)(struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction); - - -/** - * Handle deposit entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_deposit (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct TALER_MerchantWireHashP h_wire; - struct TALER_PrivateContractHashP h_contract_terms; - struct TALER_ExtensionPolicyHashP h_policy; - bool no_h_policy; - struct GNUNET_HashCode wallet_data_hash; - bool no_wallet_data_hash; - struct GNUNET_TIME_Timestamp wallet_timestamp; - struct TALER_MerchantPublicKeyP merchant_pub; - struct GNUNET_TIME_Timestamp refund_deadline = {0}; - struct TALER_CoinSpendSignatureP sig; - struct TALER_AgeCommitmentHash hac; - bool no_hac; - struct TALER_Amount deposit_fee; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &sig), - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &h_contract_terms), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("wallet_data_hash", - &wallet_data_hash), - &no_wallet_data_hash), - GNUNET_JSON_spec_fixed_auto ("h_wire", - &h_wire), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &hac), - &no_hac), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_policy", - &h_policy), - &no_h_policy), - GNUNET_JSON_spec_timestamp ("timestamp", - &wallet_timestamp), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("refund_deadline", - &refund_deadline), - NULL), - TALER_JSON_spec_amount_any ("deposit_fee", - &deposit_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &merchant_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_deposit_verify ( - amount, - &deposit_fee, - &h_wire, - &h_contract_terms, - no_wallet_data_hash ? NULL : &wallet_data_hash, - no_hac ? NULL : &hac, - no_h_policy ? NULL : &h_policy, - &pc->dk->h_key, - wallet_timestamp, - &merchant_pub, - refund_deadline, - pc->coin_pub, - &sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* check that deposit fee matches our expectations from /keys! */ - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&deposit_fee, - &pc->dk->fees.deposit)) || - (0 != - TALER_amount_cmp (&deposit_fee, - &pc->dk->fees.deposit)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -/** - * Handle melt entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_melt (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct TALER_CoinSpendSignatureP sig; - struct TALER_RefreshCommitmentP rc; - struct TALER_AgeCommitmentHash h_age_commitment; - bool no_hac; - struct TALER_Amount melt_fee; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &sig), - GNUNET_JSON_spec_fixed_auto ("rc", - &rc), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &h_age_commitment), - &no_hac), - TALER_JSON_spec_amount_any ("melt_fee", - &melt_fee), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* check that melt fee matches our expectations from /keys! */ - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&melt_fee, - &pc->dk->fees.refresh)) || - (0 != - TALER_amount_cmp (&melt_fee, - &pc->dk->fees.refresh)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_melt_verify ( - amount, - &melt_fee, - &rc, - &pc->dk->h_key, - no_hac - ? NULL - : &h_age_commitment, - pc->coin_pub, - &sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -/** - * Handle refund entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_refund (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct TALER_PrivateContractHashP h_contract_terms; - struct TALER_MerchantPublicKeyP merchant_pub; - struct TALER_MerchantSignatureP sig; - struct TALER_Amount refund_fee; - struct TALER_Amount sig_amount; - uint64_t rtransaction_id; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("refund_fee", - &refund_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_sig", - &sig), - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &merchant_pub), - GNUNET_JSON_spec_uint64 ("rtransaction_id", - &rtransaction_id), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (&sig_amount, - &refund_fee, - amount)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_merchant_refund_verify (pc->coin_pub, - &h_contract_terms, - rtransaction_id, - &sig_amount, - &merchant_pub, - &sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* NOTE: theoretically, we could also check that the given - merchant_pub and h_contract_terms appear in the - history under deposits. However, there is really no benefit - for the exchange to lie here, so not checking is probably OK - (an auditor ought to check, though). Then again, we similarly - had no reason to check the merchant's signature (other than a - well-formendess check). */ - - /* check that refund fee matches our expectations from /keys! */ - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&refund_fee, - &pc->dk->fees.refund)) || - (0 != - TALER_amount_cmp (&refund_fee, - &pc->dk->fees.refund)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_NO; -} - - -/** - * Handle recoup entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_recoup (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct TALER_ReservePublicKeyP reserve_pub; - struct GNUNET_TIME_Timestamp timestamp; - union TALER_DenominationBlindingKeyP coin_bks; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_CoinSpendSignatureP coin_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 ("reserve_pub", - &reserve_pub), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &coin_sig), - GNUNET_JSON_spec_fixed_auto ("coin_blind", - &coin_bks), - GNUNET_JSON_spec_timestamp ("timestamp", - ×tamp), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_recoup_verify ( - timestamp, - amount, - pc->coin_pub, - &reserve_pub, - &exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_recoup_verify (&pc->dk->h_key, - &coin_bks, - pc->coin_pub, - &coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -/** - * Handle recoup-refresh entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_recoup_refresh (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - /* This is the coin that was subjected to a recoup, - the value being credited to the old coin. */ - struct TALER_CoinSpendPublicKeyP old_coin_pub; - union TALER_DenominationBlindingKeyP coin_bks; - struct GNUNET_TIME_Timestamp timestamp; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_CoinSpendSignatureP coin_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 ("coin_sig", - &coin_sig), - GNUNET_JSON_spec_fixed_auto ("old_coin_pub", - &old_coin_pub), - GNUNET_JSON_spec_fixed_auto ("coin_blind", - &coin_bks), - GNUNET_JSON_spec_timestamp ("timestamp", - ×tamp), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_recoup_refresh_verify ( - timestamp, - amount, - pc->coin_pub, - &old_coin_pub, - &exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_recoup_verify (&pc->dk->h_key, - &coin_bks, - pc->coin_pub, - &coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -/** - * Handle old coin recoup entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_old_coin_recoup (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - /* This is the coin that was credited in a recoup, - the value being credited to the this coin. */ - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_CoinSpendPublicKeyP new_coin_pub; - struct GNUNET_TIME_Timestamp timestamp; - 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 ("coin_pub", - &new_coin_pub), - GNUNET_JSON_spec_timestamp ("timestamp", - ×tamp), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_recoup_refresh_verify ( - timestamp, - amount, - &new_coin_pub, - pc->coin_pub, - &exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_NO; -} - - -/** - * Handle purse deposit entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_purse_deposit (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct TALER_PurseContractPublicKeyP purse_pub; - struct TALER_CoinSpendSignatureP coin_sig; - const char *exchange_base_url; - bool refunded; - struct TALER_AgeCommitmentHash phac = { 0 }; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("purse_pub", - &purse_pub), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &coin_sig), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &coin_sig), - NULL), - GNUNET_JSON_spec_string ("exchange_base_url", - &exchange_base_url), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &phac), - NULL), - GNUNET_JSON_spec_bool ("refunded", - &refunded), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_purse_deposit_verify ( - exchange_base_url, - &purse_pub, - amount, - &pc->dk->h_key, - &phac, - pc->coin_pub, - &coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (refunded) - { - /* We wave the deposit fee. */ - if (0 > - TALER_amount_add (&pc->rtotal, - &pc->rtotal, - &pc->dk->fees.deposit)) - { - /* overflow in refund history? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_YES; -} - - -/** - * Handle purse refund entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_purse_refund (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct TALER_PurseContractPublicKeyP purse_pub; - struct TALER_Amount refund_fee; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("refund_fee", - &refund_fee), - GNUNET_JSON_spec_fixed_auto ("purse_pub", - &purse_pub), - 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 (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_purse_refund_verify ( - amount, - &refund_fee, - pc->coin_pub, - &purse_pub, - &exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&refund_fee, - &pc->dk->fees.refund)) || - (0 != - TALER_amount_cmp (&refund_fee, - &pc->dk->fees.refund)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_NO; -} - - -/** - * Handle reserve deposit entry in the coin's history. - * - * @param[in,out] pc overall context - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_reserve_open_deposit (struct CoinHistoryParseContext *pc, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct TALER_ReserveSignatureP reserve_sig; - struct TALER_CoinSpendSignatureP coin_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &reserve_sig), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &coin_sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_reserve_open_deposit_verify ( - amount, - &reserve_sig, - pc->coin_pub, - &coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_verify_coin_history ( - const struct TALER_EXCHANGE_DenomPublicKey *dk, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const json_t *history, - struct TALER_Amount *total) -{ - const char *currency = dk->value.currency; - const struct - { - const char *type; - CoinCheckHelper helper; - } map[] = { - { "DEPOSIT", &help_deposit }, - { "MELT", &help_melt }, - { "REFUND", &help_refund }, - { "RECOUP", &help_recoup }, - { "RECOUP-REFRESH", &help_recoup_refresh }, - { "OLD-COIN-RECOUP", &help_old_coin_recoup }, - { "PURSE-DEPOSIT", &help_purse_deposit }, - { "PURSE-REFUND", &help_purse_refund }, - { "RESERVE-OPEN-DEPOSIT", &help_reserve_open_deposit }, - { NULL, NULL } - }; - struct CoinHistoryParseContext pc = { - .dk = dk, - .coin_pub = coin_pub, - .total = total - }; - size_t len; - - if (NULL == history) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - len = json_array_size (history); - if (0 == len) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (currency, - total)); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (currency, - &pc.rtotal)); - for (size_t off = 0; off<len; off++) - { - enum GNUNET_GenericReturnValue add; - json_t *transaction; - struct TALER_Amount amount; - const char *type; - struct GNUNET_JSON_Specification spec_glob[] = { - TALER_JSON_spec_amount_any ("amount", - &amount), - GNUNET_JSON_spec_string ("type", - &type), - GNUNET_JSON_spec_end () - }; - - transaction = json_array_get (history, - off); - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec_glob, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_YES != - TALER_amount_cmp_currency (&amount, - &pc.rtotal)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Operation of type %s with amount %s\n", - type, - TALER_amount2s (&amount)); - add = GNUNET_SYSERR; - for (unsigned int i = 0; NULL != map[i].type; i++) - { - if (0 == strcasecmp (type, - map[i].type)) - { - add = map[i].helper (&pc, - &amount, - transaction); - break; - } - } - switch (add) - { - case GNUNET_SYSERR: - /* entry type not supported, new version on server? */ - GNUNET_break_op (0); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected type `%s' in response\n", - type); - return GNUNET_SYSERR; - case GNUNET_YES: - /* This amount should be added to the total */ - if (0 > - TALER_amount_add (total, - total, - &amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - break; - case GNUNET_NO: - /* This amount should be subtracted from the total. - - However, for the implementation, we first *add* up all of - these negative amounts, as we might get refunds before - deposits from a semi-evil exchange. Then, at the end, we do - the subtraction by calculating "total = total - rtotal" */ - if (0 > - TALER_amount_add (&pc.rtotal, - &pc.rtotal, - &amount)) - { - /* overflow in refund history? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - break; - } /* end of switch(add) */ - } - /* Finally, subtract 'rtotal' from total to handle the subtractions */ - if (0 > - TALER_amount_subtract (total, - total, - &pc.rtotal)) - { - /* underflow in history? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - const struct TALER_EXCHANGE_SigningPublicKey * TALER_EXCHANGE_get_signing_key_info ( const struct TALER_EXCHANGE_Keys *keys, @@ -1811,59 +275,7 @@ TALER_EXCHANGE_check_purse_econtract_conflict_ ( } -/** - * Verify that @a coin_sig does NOT appear in - * the history of @a proof and thus whatever transaction - * is authorized by @a coin_sig is a conflict with - * @a proof. - * - * @param proof a proof to check - * @param coin_sig signature that must not be in @a proof - * @return #GNUNET_OK if @a coin_sig is not in @a proof - */ -// FIXME: move to exchange_api_coin_history.c! -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_check_coin_signature_conflict_ ( - const json_t *proof, - const struct TALER_CoinSpendSignatureP *coin_sig) -{ - json_t *history; - size_t off; - json_t *entry; - - history = json_object_get (proof, - "history"); - if (NULL == history) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - json_array_foreach (history, off, entry) - { - struct TALER_CoinSpendSignatureP cs; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &cs), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (entry, - spec, - NULL, NULL)) - continue; /* entry without coin signature */ - if (0 == - GNUNET_memcmp (&cs, - coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - +// FIXME: should be used... enum GNUNET_GenericReturnValue TALER_EXCHANGE_check_coin_denomination_conflict_ ( const json_t *proof, @@ -1896,47 +308,6 @@ TALER_EXCHANGE_check_coin_denomination_conflict_ ( } -/** - * Check that the provided @a proof indeeds indicates - * a conflict for @a coin_pub. - * - * @param keys exchange keys - * @param proof provided conflict proof - * @param dk denomination of @a coin_pub that the client - * used - * @param coin_pub public key of the coin - * @param required balance required on the coin for the operation - * @return #GNUNET_OK if @a proof holds - */ -// FIXME: move to exchange_api_coin_history.c! -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_check_coin_conflict_ ( - const struct TALER_EXCHANGE_Keys *keys, - const json_t *proof, - const struct TALER_EXCHANGE_DenomPublicKey *dk, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *required) -{ - enum TALER_ErrorCode ec; - - ec = TALER_JSON_get_error_code (proof); - switch (ec) - { - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - /* Nothing to check anymore here, proof needs to be - checked in the GET /coins/$COIN_PUB handler */ - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - // FIXME: write check! - break; - default: - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - enum GNUNET_GenericReturnValue TALER_EXCHANGE_get_min_denomination_ ( const struct TALER_EXCHANGE_Keys *keys, diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h index 0fcaac3f3..f1f0fd7fa 100644 --- a/src/lib/exchange_api_common.h +++ b/src/lib/exchange_api_common.h @@ -146,43 +146,6 @@ TALER_EXCHANGE_check_coin_denomination_conflict_ ( /** - * Verify that @a coin_sig does NOT appear in - * the history of @a proof and thus whatever transaction - * is authorized by @a coin_sig is a conflict with - * @a proof. - * - * @param proof a proof to check - * @param coin_sig signature that must not be in @a proof - * @return #GNUNET_OK if @a coin_sig is not in @a proof - */ -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_check_coin_signature_conflict_ ( - const json_t *proof, - const struct TALER_CoinSpendSignatureP *coin_sig); - - -/** - * Check that the provided @a proof indeeds indicates - * a conflict for @a coin_pub. - * - * @param keys exchange keys - * @param proof provided conflict proof - * @param dk denomination of @a coin_pub that the client - * used - * @param coin_pub public key of the coin - * @param required balance required on the coin for the operation - * @return #GNUNET_OK if @a proof holds - */ -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_check_coin_conflict_ ( - const struct TALER_EXCHANGE_Keys *keys, - const json_t *proof, - const struct TALER_EXCHANGE_DenomPublicKey *dk, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *required); - - -/** * Find the smallest denomination amount in @e keys. * * @param keys keys to search diff --git a/src/lib/exchange_api_reserves_history.c b/src/lib/exchange_api_reserves_history.c index c92fad5e8..b62da1af1 100644 --- a/src/lib/exchange_api_reserves_history.c +++ b/src/lib/exchange_api_reserves_history.c @@ -78,6 +78,725 @@ struct TALER_EXCHANGE_ReservesHistoryHandle /** + * Context for history entry helpers. + */ +struct HistoryParseContext +{ + + /** + * Keys of the exchange we use. + */ + const struct TALER_EXCHANGE_Keys *keys; + + /** + * Our reserve public key. + */ + const struct TALER_ReservePublicKeyP *reserve_pub; + + /** + * Array of UUIDs. + */ + struct GNUNET_HashCode *uuids; + + /** + * Where to sum up total inbound amounts. + */ + struct TALER_Amount *total_in; + + /** + * Where to sum up total outbound amounts. + */ + struct TALER_Amount *total_out; + + /** + * Number of entries already used in @e uuids. + */ + unsigned int uuid_off; +}; + + +/** + * Type of a function called to parse a reserve history + * entry @a rh. + * + * @param[in,out] rh where to write the result + * @param[in,out] uc UUID context for duplicate detection + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +typedef enum GNUNET_GenericReturnValue +(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction); + + +/** + * Parse "credit" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + const char *wire_url; + uint64_t wire_reference; + struct GNUNET_TIME_Timestamp timestamp; + struct GNUNET_JSON_Specification withdraw_spec[] = { + GNUNET_JSON_spec_uint64 ("wire_reference", + &wire_reference), + GNUNET_JSON_spec_timestamp ("timestamp", + ×tamp), + GNUNET_JSON_spec_string ("sender_account_url", + &wire_url), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_CREDIT; + if (0 > + TALER_amount_add (uc->total_in, + uc->total_in, + &rh->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; + } + rh->details.in_details.sender_url = GNUNET_strdup (wire_url); + rh->details.in_details.wire_reference = wire_reference; + rh->details.in_details.timestamp = timestamp; + return GNUNET_OK; +} + + +/** + * Parse "credit" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + struct TALER_ReserveSignatureP sig; + struct TALER_DenominationHashP h_denom_pub; + struct TALER_BlindedCoinHashP bch; + struct TALER_Amount withdraw_fee; + struct GNUNET_JSON_Specification withdraw_spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &sig), + TALER_JSON_spec_amount_any ("withdraw_fee", + &withdraw_fee), + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &h_denom_pub), + GNUNET_JSON_spec_fixed_auto ("h_coin_envelope", + &bch), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + withdraw_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* Check that the signature is a valid withdraw request */ + if (GNUNET_OK != + TALER_wallet_withdraw_verify (&h_denom_pub, + &rh->amount, + &bch, + uc->reserve_pub, + &sig)) + { + 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; + + key_state = uc->keys; + dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, + &h_denom_pub); + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&withdraw_fee, + &dki->fees.withdraw)) || + (0 != + TALER_amount_cmp (&withdraw_fee, + &dki->fees.withdraw)) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + rh->details.withdraw.fee = withdraw_fee; + } + rh->details.withdraw.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 + signatures, and compare the hashes to find + duplicates. */ + GNUNET_CRYPTO_hash (&sig, + sizeof (sig), + &uc->uuids[uc->uuid_off]); + for (unsigned int i = 0; i<uc->uuid_off; i++) + { + if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off], + &uc->uuids[i])) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + } + uc->uuid_off++; + + if (0 > + TALER_amount_add (uc->total_out, + uc->total_out, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Parse "recoup" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification recoup_spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rh->details.recoup_details.coin_pub), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rh->details.recoup_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rh->details.recoup_details.exchange_pub), + GNUNET_JSON_spec_timestamp ("timestamp", + &rh->details.recoup_details.timestamp), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_RECOUP; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + recoup_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = uc->keys; + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &rh->details. + recoup_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_recoup_verify ( + rh->details.recoup_details.timestamp, + &rh->amount, + &rh->details.recoup_details.coin_pub, + uc->reserve_pub, + &rh->details.recoup_details.exchange_pub, + &rh->details.recoup_details.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (uc->total_in, + uc->total_in, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Parse "closing" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification closing_spec[] = { + GNUNET_JSON_spec_string ( + "receiver_account_details", + &rh->details.close_details.receiver_account_details), + GNUNET_JSON_spec_fixed_auto ("wtid", + &rh->details.close_details.wtid), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rh->details.close_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rh->details.close_details.exchange_pub), + TALER_JSON_spec_amount_any ("closing_fee", + &rh->details.close_details.fee), + GNUNET_JSON_spec_timestamp ("timestamp", + &rh->details.close_details.timestamp), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_CLOSING; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + closing_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = uc->keys; + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key ( + key_state, + &rh->details.close_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_reserve_closed_verify ( + rh->details.close_details.timestamp, + &rh->amount, + &rh->details.close_details.fee, + rh->details.close_details.receiver_account_details, + &rh->details.close_details.wtid, + uc->reserve_pub, + &rh->details.close_details.exchange_pub, + &rh->details.close_details.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (uc->total_out, + uc->total_out, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Parse "merge" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + uint32_t flags32; + struct GNUNET_JSON_Specification merge_spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &rh->details.merge_details.h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("merge_pub", + &rh->details.merge_details.merge_pub), + GNUNET_JSON_spec_fixed_auto ("purse_pub", + &rh->details.merge_details.purse_pub), + GNUNET_JSON_spec_uint32 ("min_age", + &rh->details.merge_details.min_age), + GNUNET_JSON_spec_uint32 ("flags", + &flags32), + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rh->details.merge_details.reserve_sig), + TALER_JSON_spec_amount_any ("purse_fee", + &rh->details.merge_details.purse_fee), + GNUNET_JSON_spec_timestamp ("merge_timestamp", + &rh->details.merge_details.merge_timestamp), + GNUNET_JSON_spec_timestamp ("purse_expiration", + &rh->details.merge_details.purse_expiration), + GNUNET_JSON_spec_bool ("merged", + &rh->details.merge_details.merged), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_MERGE; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + merge_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rh->details.merge_details.flags = + (enum TALER_WalletAccountMergeFlags) flags32; + if (GNUNET_OK != + TALER_wallet_account_merge_verify ( + rh->details.merge_details.merge_timestamp, + &rh->details.merge_details.purse_pub, + rh->details.merge_details.purse_expiration, + &rh->details.merge_details.h_contract_terms, + &rh->amount, + &rh->details.merge_details.purse_fee, + rh->details.merge_details.min_age, + rh->details.merge_details.flags, + uc->reserve_pub, + &rh->details.merge_details.reserve_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (rh->details.merge_details.merged) + { + if (0 > + TALER_amount_add (uc->total_in, + uc->total_in, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + else + { + if (0 > + TALER_amount_add (uc->total_out, + uc->total_out, + &rh->details.merge_details.purse_fee)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Parse "open" reserve open entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + struct GNUNET_JSON_Specification open_spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rh->details.open_request.reserve_sig), + TALER_JSON_spec_amount_any ("open_payment", + &rh->details.open_request.reserve_payment), + GNUNET_JSON_spec_uint32 ("requested_min_purses", + &rh->details.open_request.purse_limit), + GNUNET_JSON_spec_timestamp ("request_timestamp", + &rh->details.open_request.request_timestamp), + GNUNET_JSON_spec_timestamp ("requested_expiration", + &rh->details.open_request.reserve_expiration), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_OPEN; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + open_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_reserve_open_verify ( + &rh->amount, + rh->details.open_request.request_timestamp, + rh->details.open_request.reserve_expiration, + rh->details.open_request.purse_limit, + uc->reserve_pub, + &rh->details.open_request.reserve_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (uc->total_out, + uc->total_out, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Parse "close" reserve close entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + struct GNUNET_JSON_Specification close_spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rh->details.close_request.reserve_sig), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_payto", + &rh->details.close_request. + target_account_h_payto), + NULL), + GNUNET_JSON_spec_timestamp ("request_timestamp", + &rh->details.close_request.request_timestamp), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_CLOSE; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + close_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* force amount to invalid */ + memset (&rh->amount, + 0, + sizeof (rh->amount)); + if (GNUNET_OK != + TALER_wallet_reserve_close_verify ( + rh->details.close_request.request_timestamp, + &rh->details.close_request.target_account_h_payto, + uc->reserve_pub, + &rh->details.close_request.reserve_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +free_reserve_history ( + unsigned int len, + struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]) +{ + for (unsigned int i = 0; i<len; i++) + { + switch (rhistory[i].type) + { + case TALER_EXCHANGE_RTT_CREDIT: + GNUNET_free (rhistory[i].details.in_details.sender_url); + break; + case TALER_EXCHANGE_RTT_WITHDRAWAL: + break; + case TALER_EXCHANGE_RTT_AGEWITHDRAWAL: + break; + case TALER_EXCHANGE_RTT_RECOUP: + break; + case TALER_EXCHANGE_RTT_CLOSING: + break; + case TALER_EXCHANGE_RTT_MERGE: + break; + case TALER_EXCHANGE_RTT_OPEN: + break; + case TALER_EXCHANGE_RTT_CLOSE: + break; + } + } + GNUNET_free (rhistory); +} + + +/** + * Parse history given in JSON format and return it in binary + * format. + * + * @param keys exchange keys + * @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] total_in set to value of credits to reserve + * @param[out] total_out set to value of debits from reserve + * @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 enum GNUNET_GenericReturnValue +parse_reserve_history ( + const struct TALER_EXCHANGE_Keys *keys, + const json_t *history, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *currency, + struct TALER_Amount *total_in, + struct TALER_Amount *total_out, + unsigned int history_length, + struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]) +{ + const struct + { + const char *type; + ParseHelper helper; + } map[] = { + { "CREDIT", &parse_credit }, + { "WITHDRAW", &parse_withdraw }, + { "RECOUP", &parse_recoup }, + { "MERGE", &parse_merge }, + { "CLOSING", &parse_closing }, + { "OPEN", &parse_open }, + { "CLOSE", &parse_close }, + { NULL, NULL } + }; + struct GNUNET_HashCode uuid[history_length]; + struct HistoryParseContext uc = { + .keys = keys, + .reserve_pub = reserve_pub, + .uuids = uuid, + .total_in = total_in, + .total_out = total_out + }; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + total_in)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + total_out)); + for (unsigned int off = 0; off<history_length; off++) + { + struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[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_any ("amount", + &amount), + /* 'wire' and 'signature' are optional depending on 'type'! */ + GNUNET_JSON_spec_end () + }; + bool found = false; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + hist_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + json_dumpf (transaction, + stderr, + JSON_INDENT (2)); + return GNUNET_SYSERR; + } + rh->amount = amount; + if (GNUNET_YES != + TALER_amount_cmp_currency (&amount, + total_in)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; NULL != map[i].type; i++) + { + if (0 == strcasecmp (map[i].type, + type)) + { + found = true; + if (GNUNET_OK != + map[i].helper (rh, + &uc, + transaction)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + break; + } + } + if (! found) + { + /* unexpected 'type', protocol incompatibility, complain! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** * We received an #MHD_HTTP_OK history code. Handle the JSON * response. * @@ -119,18 +838,18 @@ handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh, rhistory = GNUNET_new_array (len, struct TALER_EXCHANGE_ReserveHistoryEntry); if (GNUNET_OK != - TALER_EXCHANGE_parse_reserve_history (rsh->keys, - history, - &rsh->reserve_pub, - rs.details.ok.balance.currency, - &rs.details.ok.total_in, - &rs.details.ok.total_out, - len, - rhistory)) + parse_reserve_history (rsh->keys, + history, + &rsh->reserve_pub, + rs.details.ok.balance.currency, + &rs.details.ok.total_in, + &rs.details.ok.total_out, + len, + rhistory)) { GNUNET_break_op (0); - TALER_EXCHANGE_free_reserve_history (len, - rhistory); + free_reserve_history (len, + rhistory); GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } @@ -142,8 +861,8 @@ handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh, &rs); rsh->cb = NULL; } - TALER_EXCHANGE_free_reserve_history (len, - rhistory); + free_reserve_history (len, + rhistory); } return GNUNET_OK; } @@ -244,7 +963,7 @@ TALER_EXCHANGE_reserves_history ( struct TALER_EXCHANGE_ReservesHistoryHandle *rsh; CURL *eh; char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64]; - struct TALER_ReserveSignatureP reserve_sig; + struct curl_slist *job_headers; rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle); rsh->cb = cb; @@ -289,34 +1008,41 @@ TALER_EXCHANGE_reserves_history ( GNUNET_free (rsh); return NULL; } - TALER_wallet_reserve_history_sign (start_off, - reserve_priv, - &reserve_sig); + { - json_t *history_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("reserve_sig", - &reserve_sig)); + struct TALER_ReserveSignatureP reserve_sig; + char *sig_hdr; + char *hdr; - if (GNUNET_OK != - TALER_curl_easy_post (&rsh->post_ctx, - eh, - history_obj)) + TALER_wallet_reserve_history_sign (start_off, + reserve_priv, + &reserve_sig); + + sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( + &reserve_sig, + sizeof (reserve_sig)); + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_RESERVE_HISTORY_SIGNATURE_HEADER, + sig_hdr); + GNUNET_free (sig_hdr); + job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + if (NULL == job_headers) { GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (history_obj); - GNUNET_free (rsh->url); - GNUNET_free (rsh); return NULL; } - json_decref (history_obj); } + rsh->keys = TALER_EXCHANGE_keys_incref (keys); rsh->job = GNUNET_CURL_job_add2 (ctx, eh, - rsh->post_ctx.headers, + job_headers, &handle_reserves_history_finished, rsh); + curl_slist_free_all (job_headers); return rsh; } diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c index b1f8417e4..381b064fa 100644 --- a/src/mhd/mhd_parsing.c +++ b/src/mhd/mhd_parsing.c @@ -210,6 +210,42 @@ TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection, enum GNUNET_GenericReturnValue +TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection, + const char *name, + uint64_t *off) +{ + const char *ts; + char dummy; + unsigned long long num; + + ts = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + name); + if (NULL == ts) + return GNUNET_OK; + if (1 != + sscanf (ts, + "%llu%c", + &num, + &dummy)) + { + MHD_RESULT mret; + + GNUNET_break_op (0); + mret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + name); + return (MHD_YES == mret) + ? GNUNET_NO + : GNUNET_SYSERR; + } + *off = (uint64_t) num; + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue TALER_MHD_parse_json_data (struct MHD_Connection *connection, const json_t *root, struct GNUNET_JSON_Specification *spec) |