diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_batch-withdraw.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_batch-withdraw.c | 660 |
1 files changed, 461 insertions, 199 deletions
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c b/src/exchange/taler-exchange-httpd_batch-withdraw.c index e58548af7..2b80c2fc4 100644 --- a/src/exchange/taler-exchange-httpd_batch-withdraw.c +++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 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 @@ -26,11 +26,14 @@ #include "platform.h" #include <gnunet/gnunet_util_lib.h> #include <jansson.h> +#include "taler-exchange-httpd.h" #include "taler_json_lib.h" +#include "taler_kyclogic_lib.h" #include "taler_mhd_lib.h" #include "taler-exchange-httpd_batch-withdraw.h" #include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_keys.h" +#include "taler_util.h" /** @@ -72,11 +75,16 @@ struct BatchWithdrawContext { /** - * Public key of the reserv. + * Public key of the reserve. */ const struct TALER_ReservePublicKeyP *reserve_pub; /** + * request context + */ + const struct TEH_RequestContext *rc; + + /** * KYC status of the reserve used for the operation. */ struct TALER_EXCHANGEDB_KycStatus kyc; @@ -87,6 +95,17 @@ struct BatchWithdrawContext struct PlanchetContext *planchets; /** + * Hash of the payto-URI representing the reserve + * from which we are withdrawing. + */ + struct TALER_PaytoHashP h_payto; + + /** + * Current time for the DB transaction. + */ + struct GNUNET_TIME_Timestamp now; + + /** * Total amount from all coins with fees. */ struct TALER_Amount batch_total; @@ -96,10 +115,175 @@ struct BatchWithdrawContext */ unsigned int planchets_length; + /** + * AML decision, #TALER_AML_NORMAL if we may proceed. + */ + enum TALER_AmlDecisionState aml_decision; + }; /** + * Function called to iterate over KYC-relevant + * transaction amounts for a particular time range. + * Called within a database transaction, so must + * not start a new one. + * + * @param cls closure, identifies the event type and + * account to iterate over events for + * @param limit maximum time-range for which events + * should be fetched (timestamp in the past) + * @param cb function to call on each event found, + * events must be returned in reverse chronological + * order + * @param cb_cls closure for @a cb + */ +static void +batch_withdraw_amount_cb (void *cls, + struct GNUNET_TIME_Absolute limit, + TALER_EXCHANGEDB_KycAmountCallback cb, + void *cb_cls) +{ + struct BatchWithdrawContext *wc = cls; + enum GNUNET_DB_QueryStatus qs; + + if (GNUNET_OK != + cb (cb_cls, + &wc->batch_total, + wc->now.abs_time)) + return; + qs = TEH_plugin->select_withdraw_amounts_for_kyc_check ( + TEH_plugin->cls, + &wc->h_payto, + limit, + cb, + cb_cls); + GNUNET_break (qs >= 0); +} + + +/** + * Function called on each @a amount that was found to + * be relevant for the AML check as it was merged into + * the reserve. + * + * @param cls `struct TALER_Amount *` to total up the amounts + * @param amount encountered transaction amount + * @param date when was the amount encountered + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to abort iteration + * #GNUNET_SYSERR on internal error (also abort itaration) + */ +static enum GNUNET_GenericReturnValue +aml_amount_cb ( + void *cls, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute date) +{ + struct TALER_Amount *total = cls; + + GNUNET_assert (0 <= + TALER_amount_add (total, + total, + amount)); + return GNUNET_OK; +} + + +/** + * Generates our final (successful) response. + * + * @param rc request context + * @param wc operation context + * @return MHD queue status + */ +static MHD_RESULT +generate_reply_success (const struct TEH_RequestContext *rc, + const struct BatchWithdrawContext *wc) +{ + json_t *sigs; + + if (! wc->kyc.ok) + { + /* KYC required */ + return TEH_RESPONSE_reply_kyc_required (rc->connection, + &wc->h_payto, + &wc->kyc); + } + if (TALER_AML_NORMAL != wc->aml_decision) + return TEH_RESPONSE_reply_aml_blocked (rc->connection, + wc->aml_decision); + + sigs = json_array (); + GNUNET_assert (NULL != sigs); + for (unsigned int i = 0; i<wc->planchets_length; i++) + { + struct PlanchetContext *pc = &wc->planchets[i]; + + GNUNET_assert ( + 0 == + json_array_append_new ( + sigs, + GNUNET_JSON_PACK ( + TALER_JSON_pack_blinded_denom_sig ( + "ev_sig", + &pc->collectable.sig)))); + } + TEH_METRICS_batch_withdraw_num_coins += wc->planchets_length; + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_array_steal ("ev_sigs", + sigs)); +} + + +/** + * Check if the @a wc is replayed and we already have an + * answer. If so, replay the existing answer and return the + * HTTP response. + * + * @param wc parsed request data + * @param[out] mret HTTP status, set if we return true + * @return true if the request is idempotent with an existing request + * false if we did not find the request in the DB and did not set @a mret + */ +static bool +check_request_idempotent (const struct BatchWithdrawContext *wc, + MHD_RESULT *mret) +{ + const struct TEH_RequestContext *rc = wc->rc; + + for (unsigned int i = 0; i<wc->planchets_length; i++) + { + struct PlanchetContext *pc = &wc->planchets[i]; + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, + &pc->h_coin_envelope, + &pc->collectable); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mret = TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_withdraw_info"); + return true; /* well, kind-of */ + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + return false; + } + /* generate idempotent reply */ + TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++; + *mret = generate_reply_success (rc, + wc); + return true; +} + + +/** * Function implementing withdraw transaction. Runs the * transaction logic; IF it returns a non-error code, the transaction * logic MUST NOT queue a MHD response. IF it returns an hard error, @@ -122,114 +306,254 @@ batch_withdraw_transaction (void *cls, MHD_RESULT *mhd_ret) { struct BatchWithdrawContext *wc = cls; - struct GNUNET_TIME_Timestamp now; uint64_t ruuid; enum GNUNET_DB_QueryStatus qs; - bool balance_ok = false; bool found = false; + bool balance_ok = false; + bool age_ok = false; + uint16_t allowed_maximum_age = 0; + struct TALER_Amount reserve_balance; + char *kyc_required; + struct TALER_PaytoHashP reserve_h_payto; + + wc->now = GNUNET_TIME_timestamp_get (); + /* Do AML check: compute total merged amount and check + against applicable AML threshold */ + { + char *reserve_payto; + + reserve_payto = TALER_reserve_make_payto (TEH_base_url, + wc->reserve_pub); + TALER_payto_hash (reserve_payto, + &reserve_h_payto); + GNUNET_free (reserve_payto); + } + { + struct TALER_Amount merge_amount; + struct TALER_Amount threshold; + struct GNUNET_TIME_Absolute now_minus_one_month; + + now_minus_one_month + = GNUNET_TIME_absolute_subtract (wc->now.abs_time, + GNUNET_TIME_UNIT_MONTHS); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &merge_amount)); + qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls, + &reserve_h_payto, + now_minus_one_month, + &aml_amount_cb, + &merge_amount); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_merge_amounts_for_kyc_check"); + return qs; + } + qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls, + &reserve_h_payto, + &wc->aml_decision, + &wc->kyc, + &threshold); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_aml_threshold"); + return qs; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + threshold = TEH_aml_threshold; /* use default */ + wc->aml_decision = TALER_AML_NORMAL; + } + + switch (wc->aml_decision) + { + case TALER_AML_NORMAL: + if (0 >= TALER_amount_cmp (&merge_amount, + &threshold)) + { + /* merge_amount <= threshold, continue withdraw below */ + break; + } + wc->aml_decision = TALER_AML_PENDING; + qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls, + &reserve_h_payto, + &merge_amount); + if (qs <= 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "trigger_aml_process"); + return qs; + } + return qs; + case TALER_AML_PENDING: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "AML already pending, doing nothing\n"); + return qs; + case TALER_AML_FROZEN: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Account frozen, doing nothing\n"); + return qs; + } + } + + /* Check if the money came from a wire transfer */ + qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls, + wc->reserve_pub, + &wc->h_payto); + if (qs < 0) + { + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "reserves_get_origin"); + return qs; + } + /* If no results, reserve was created by merge, in which case no KYC check + is required as the merge already did that. */ + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + qs = TALER_KYCLOGIC_kyc_test_required ( + TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW, + &wc->h_payto, + TEH_plugin->select_satisfied_kyc_processes, + TEH_plugin->cls, + &batch_withdraw_amount_cb, + wc, + &kyc_required); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "kyc_test_required"); + return qs; + } + if (NULL != kyc_required) + { + /* insert KYC requirement into DB! */ + wc->kyc.ok = false; + qs = TEH_plugin->insert_kyc_requirement_for_account ( + TEH_plugin->cls, + kyc_required, + &wc->h_payto, + wc->reserve_pub, + &wc->kyc.requirement_row); + GNUNET_free (kyc_required); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_kyc_requirement_for_account"); + } + return qs; + } + } + wc->kyc.ok = true; - now = GNUNET_TIME_timestamp_get (); qs = TEH_plugin->do_batch_withdraw (TEH_plugin->cls, - now, + wc->now, wc->reserve_pub, &wc->batch_total, + TEH_age_restriction_enabled, &found, &balance_ok, - &wc->kyc, + &reserve_balance, + &age_ok, + &allowed_maximum_age, &ruuid); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "update_reserve_batch_withdraw"); + } return qs; } if (! found) { *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, NULL); return GNUNET_DB_STATUS_HARD_ERROR; } - if (! balance_ok) + + if (! age_ok) { + /* We respond with the lowest age in the corresponding age group + * of the required age */ + uint16_t lowest_age = TALER_get_lowest_age ( + &TEH_age_restriction_config.mask, + allowed_maximum_age); + TEH_plugin->rollback (TEH_plugin->cls); - *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( + *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required ( connection, - &wc->batch_total, - wc->reserve_pub); + lowest_age); return GNUNET_DB_STATUS_HARD_ERROR; } - if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && - (! wc->kyc.ok) && - (TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) ) + if (! balance_ok) { - /* Wallet-to-wallet payments _always_ require KYC */ - *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( + TEH_plugin->rollback (TEH_plugin->cls); + *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( connection, - MHD_HTTP_ACCEPTED, - GNUNET_JSON_pack_uint64 ("payment_target_uuid", - wc->kyc.payment_target_uuid)); + TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS, + &reserve_balance, + &wc->batch_total, + wc->reserve_pub); return GNUNET_DB_STATUS_HARD_ERROR; } - if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && - (! wc->kyc.ok) && - (TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) && - (! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) ) - { - /* Withdraws require KYC if above threshold */ - enum GNUNET_DB_QueryStatus qs2; - bool below_limit; - - qs2 = TEH_plugin->do_withdraw_limit_check ( - TEH_plugin->cls, - ruuid, - GNUNET_TIME_absolute_subtract (now.abs_time, - TEH_kyc_config.withdraw_period), - &TEH_kyc_config.withdraw_limit, - &below_limit); - if (0 > qs2) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs2); - if (GNUNET_DB_STATUS_HARD_ERROR == qs2) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "do_withdraw_limit_check"); - return qs2; - } - if (! below_limit) - { - *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_ACCEPTED, - GNUNET_JSON_pack_uint64 ("payment_target_uuid", - wc->kyc.payment_target_uuid)); - return GNUNET_DB_STATUS_HARD_ERROR; - } - } /* Add information about each planchet in the batch */ for (unsigned int i = 0; i<wc->planchets_length; i++) { struct PlanchetContext *pc = &wc->planchets[i]; const struct TALER_BlindedPlanchet *bp = &pc->blinded_planchet; - const struct TALER_CsNonce *nonce; + const union GNUNET_CRYPTO_BlindSessionNonce *nonce = NULL; bool denom_unknown = true; bool conflict = true; bool nonce_reuse = true; - nonce = (TALER_DENOMINATION_CS == bp->cipher) - ? &bp->details.cs_blinded_planchet.nonce - : NULL; + switch (bp->blinded_message->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + break; + case GNUNET_CRYPTO_BSA_CS: + nonce = (const union GNUNET_CRYPTO_BlindSessionNonce *) + &bp->blinded_message->details.cs_blinded_message.nonce; + break; + } qs = TEH_plugin->do_batch_withdraw_insert (TEH_plugin->cls, nonce, &pc->collectable, - now, + wc->now, ruuid, &denom_unknown, &conflict, @@ -240,7 +564,7 @@ batch_withdraw_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "do_withdraw"); + "do_batch_withdraw_insert"); return qs; } if (denom_unknown) @@ -255,12 +579,18 @@ batch_withdraw_transaction (void *cls, if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || (conflict) ) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Idempotent coin in batch, not allowed. Aborting.\n"); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET, - NULL); + if (! check_request_idempotent (wc, + mhd_ret)) + { + /* We do not support *some* of the coins of the request being + idempotent while others being fresh. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Idempotent coin in batch, not allowed. Aborting.\n"); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET, + NULL); + } return GNUNET_DB_STATUS_HARD_ERROR; } if (nonce_reuse) @@ -273,92 +603,12 @@ batch_withdraw_transaction (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } } + TEH_METRICS_num_success[TEH_MT_SUCCESS_BATCH_WITHDRAW]++; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } /** - * Generates our final (successful) response. - * - * @param rc request context - * @param wc operation context - * @return MHD queue status - */ -static MHD_RESULT -generate_reply_success (const struct TEH_RequestContext *rc, - const struct BatchWithdrawContext *wc) -{ - json_t *sigs; - - sigs = json_array (); - GNUNET_assert (NULL != sigs); - for (unsigned int i = 0; i<wc->planchets_length; i++) - { - struct PlanchetContext *pc = &wc->planchets[i]; - - GNUNET_assert ( - 0 == - json_array_append_new ( - sigs, - GNUNET_JSON_PACK ( - TALER_JSON_pack_blinded_denom_sig ( - "ev_sig", - &pc->collectable.sig)))); - } - TEH_METRICS_batch_withdraw_num_coins += wc->planchets_length; - return TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_array_steal ("ev_sigs", - sigs)); -} - - -/** - * Check if the @a rc is replayed and we already have an - * answer. If so, replay the existing answer and return the - * HTTP response. - * - * @param rc request context - * @param wc parsed request data - * @param[out] mret HTTP status, set if we return true - * @return true if the request is idempotent with an existing request - * false if we did not find the request in the DB and did not set @a mret - */ -static bool -check_request_idempotent (const struct TEH_RequestContext *rc, - const struct BatchWithdrawContext *wc, - MHD_RESULT *mret) -{ - for (unsigned int i = 0; i<wc->planchets_length; i++) - { - struct PlanchetContext *pc = &wc->planchets[i]; - enum GNUNET_DB_QueryStatus qs; - - qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, - &pc->h_coin_envelope, - &pc->collectable); - if (0 > qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mret = TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_withdraw_info"); - return true; /* well, kind-of */ - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - return false; - } - /* generate idempotent reply */ - *mret = generate_reply_success (rc, - wc); - return true; -} - - -/** * The request was parsed successfully. Prepare * our side for the main DB transaction. * @@ -370,20 +620,24 @@ static MHD_RESULT prepare_transaction (const struct TEH_RequestContext *rc, struct BatchWithdrawContext *wc) { - /* Note: We could check the reserve balance here, - just to be reasonably sure that the reserve has - a sufficient balance before doing the "expensive" - signatures... */ - /* Sign before transaction! */ + struct TEH_CoinSignData csds[wc->planchets_length]; + struct TALER_BlindedDenominationSignature bss[wc->planchets_length]; + for (unsigned int i = 0; i<wc->planchets_length; i++) { struct PlanchetContext *pc = &wc->planchets[i]; + + csds[i].h_denom_pub = &pc->collectable.denom_pub_hash; + csds[i].bp = &pc->blinded_planchet; + } + { enum TALER_ErrorCode ec; - ec = TEH_keys_denomination_sign_withdraw ( - &pc->collectable.denom_pub_hash, - &pc->blinded_planchet, - &pc->collectable.sig); + ec = TEH_keys_denomination_batch_sign ( + wc->planchets_length, + csds, + false, + bss); if (TALER_EC_NONE != ec) { GNUNET_break (0); @@ -392,6 +646,12 @@ prepare_transaction (const struct TEH_RequestContext *rc, NULL); } } + for (unsigned int i = 0; i<wc->planchets_length; i++) + { + struct PlanchetContext *pc = &wc->planchets[i]; + + pc->collectable.sig = bss[i]; + } /* run transaction */ { @@ -455,13 +715,27 @@ parse_planchets (const struct TEH_RequestContext *rc, return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } pc->collectable.reserve_pub = *wc->reserve_pub; + for (unsigned int k = 0; k<i; k++) + { + const struct PlanchetContext *kpc = &wc->planchets[k]; + + if (0 == + TALER_blinded_planchet_cmp (&kpc->blinded_planchet, + &pc->blinded_planchet)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "duplicate planchet"); + } + } } ksh = TEH_keys_get_state (); if (NULL == ksh) { - if (! check_request_idempotent (rc, - wc, + if (! check_request_idempotent (wc, &mret)) { return TALER_MHD_reply_with_error (rc->connection, @@ -476,14 +750,15 @@ parse_planchets (const struct TEH_RequestContext *rc, struct PlanchetContext *pc = &wc->planchets[i]; struct TEH_DenominationKey *dk; - dk = TEH_keys_denomination_by_hash2 (ksh, - &pc->collectable.denom_pub_hash, - NULL, - NULL); + dk = TEH_keys_denomination_by_hash_from_state ( + ksh, + &pc->collectable.denom_pub_hash, + NULL, + NULL); + if (NULL == dk) { - if (! check_request_idempotent (rc, - wc, + if (! check_request_idempotent (wc, &mret)) { return TEH_RESPONSE_reply_unknown_denom_pub_hash ( @@ -495,8 +770,7 @@ parse_planchets (const struct TEH_RequestContext *rc, if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time)) { /* This denomination is past the expiration time for withdraws */ - if (! check_request_idempotent (rc, - wc, + if (! check_request_idempotent (wc, &mret)) { return TEH_RESPONSE_reply_expired_denom_pub_hash ( @@ -520,8 +794,7 @@ parse_planchets (const struct TEH_RequestContext *rc, if (dk->recoup_possible) { /* This denomination has been revoked */ - if (! check_request_idempotent (rc, - wc, + if (! check_request_idempotent (wc, &mret)) { return TEH_RESPONSE_reply_expired_denom_pub_hash ( @@ -532,7 +805,8 @@ parse_planchets (const struct TEH_RequestContext *rc, } return mret; } - if (dk->denom_pub.cipher != pc->blinded_planchet.cipher) + if (dk->denom_pub.bsign_pub_key->cipher != + pc->blinded_planchet.blinded_message->cipher) { /* denomination cipher and blinded planchet cipher not the same */ GNUNET_break_op (0); @@ -564,17 +838,10 @@ parse_planchets (const struct TEH_RequestContext *rc, NULL); } - if (GNUNET_OK != - TALER_coin_ev_hash (&pc->blinded_planchet, - &pc->collectable.denom_pub_hash, - &pc->collectable.h_coin_envelope)) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); - } + TALER_coin_ev_hash (&pc->blinded_planchet, + &pc->collectable.denom_pub_hash, + &pc->collectable.h_coin_envelope); + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != TALER_wallet_withdraw_verify (&pc->collectable.denom_pub_hash, @@ -601,21 +868,20 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc, const struct TALER_ReservePublicKeyP *reserve_pub, const json_t *root) { - struct BatchWithdrawContext wc; - json_t *planchets; + struct BatchWithdrawContext wc = { + .reserve_pub = reserve_pub, + .rc = rc + }; + const json_t *planchets; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("planchets", - &planchets), + GNUNET_JSON_spec_array_const ("planchets", + &planchets), GNUNET_JSON_spec_end () }; - memset (&wc, - 0, - sizeof (wc)); - TALER_amount_set_zero (TEH_currency, - &wc.batch_total); - wc.reserve_pub = reserve_pub; - + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &wc.batch_total)); { enum GNUNET_GenericReturnValue res; @@ -625,20 +891,17 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc, if (GNUNET_OK != res) return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } - if ( (! json_is_array (planchets)) || - (0 == json_array_size (planchets)) ) + wc.planchets_length = json_array_size (planchets); + if (0 == wc.planchets_length) { - GNUNET_JSON_parse_free (spec); GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "planchets"); } - wc.planchets_length = json_array_size (planchets); if (wc.planchets_length > TALER_MAX_FRESH_COINS) { - GNUNET_JSON_parse_free (spec); GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, @@ -664,7 +927,6 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc, TALER_blinded_planchet_free (&pc->blinded_planchet); TALER_blinded_denom_sig_free (&pc->collectable.sig); } - GNUNET_JSON_parse_free (spec); return ret; } } |