From 60a30b1c75644710d635c2efa4bccf440e1839b4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 29 Jun 2022 14:00:56 +0200 Subject: [PATCH] -deduplicate purse deposit logic --- src/exchange/Makefile.am | 1 + .../taler-exchange-httpd_common_deposit.c | 311 +++++++++++++++ .../taler-exchange-httpd_common_deposit.h | 130 +++++++ src/exchange/taler-exchange-httpd_deposit.c | 29 -- .../taler-exchange-httpd_purses_create.c | 353 +++--------------- .../taler-exchange-httpd_purses_deposit.c | 333 ++--------------- 6 files changed, 522 insertions(+), 635 deletions(-) create mode 100644 src/exchange/taler-exchange-httpd_common_deposit.c create mode 100644 src/exchange/taler-exchange-httpd_common_deposit.h diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index ba22281fd..b027daf82 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -109,6 +109,7 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd.c taler-exchange-httpd.h \ taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \ taler-exchange-httpd_batch-withdraw.c taler-exchange-httpd_batch-withdraw.h \ + taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \ taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \ taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \ taler-exchange-httpd_db.c taler-exchange-httpd_db.h \ diff --git a/src/exchange/taler-exchange-httpd_common_deposit.c b/src/exchange/taler-exchange-httpd_common_deposit.c new file mode 100644 index 000000000..b6959cc9a --- /dev/null +++ b/src/exchange/taler-exchange-httpd_common_deposit.c @@ -0,0 +1,311 @@ +/* + This file is part of TALER + Copyright (C) 2022 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 + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_common_deposit.c + * @brief shared logic for handling deposited coins + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-exchange-httpd_common_deposit.h" +#include "taler-exchange-httpd.h" +#include "taler-exchange-httpd_keys.h" + + +enum GNUNET_GenericReturnValue +TEH_common_deposit_parse_coin ( + struct MHD_Connection *connection, + struct TEH_DepositedCoin *coin, + const json_t *jcoin) +{ + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("amount", + TEH_currency, + &coin->amount), + GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", + &coin->cpi.denom_pub_hash), + TALER_JSON_spec_denom_sig ("ub_sig", + &coin->cpi.denom_sig), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("attest", + &coin->attest), + &coin->no_attest), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_age_commitment ("age_commitment", + &coin->age_commitment), + &coin->cpi.no_age_commitment), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &coin->coin_sig), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &coin->cpi.coin_pub), + GNUNET_JSON_spec_end () + }; + + memset (coin, + 0, + sizeof (*coin)); + coin->cpi.no_age_commitment = true; + coin->no_attest = true; + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + jcoin, + spec); + if (GNUNET_OK != res) + return res; + } + + if (! coin->cpi.no_age_commitment) + TALER_age_commitment_hash (&coin->age_commitment, + &coin->cpi.h_age_commitment); + + /* check denomination exists and is valid */ + { + struct TEH_DenominationKey *dk; + MHD_RESULT mret; + + dk = TEH_keys_denomination_by_hash (&coin->cpi.denom_pub_hash, + connection, + &mret); + if (NULL == dk) + { + GNUNET_JSON_parse_free (spec); + return (MHD_YES == mret) ? GNUNET_NO : GNUNET_SYSERR; + } + if (0 > TALER_amount_cmp (&dk->meta.value, + &coin->amount)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE, + NULL)) + ? GNUNET_NO : GNUNET_SYSERR; + } + if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time)) + { + /* This denomination is past the expiration time for deposits */ + GNUNET_JSON_parse_free (spec); + return (MHD_YES == + TEH_RESPONSE_reply_expired_denom_pub_hash ( + connection, + &coin->cpi.denom_pub_hash, + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, + "PURSE CREATE")) + ? GNUNET_NO : GNUNET_SYSERR; + } + if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) + { + /* This denomination is not yet valid */ + GNUNET_JSON_parse_free (spec); + return (MHD_YES == + TEH_RESPONSE_reply_expired_denom_pub_hash ( + connection, + &coin->cpi.denom_pub_hash, + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, + "PURSE CREATE")) + ? GNUNET_NO : GNUNET_SYSERR; + } + if (dk->recoup_possible) + { + /* This denomination has been revoked */ + GNUNET_JSON_parse_free (spec); + return (MHD_YES == + TEH_RESPONSE_reply_expired_denom_pub_hash ( + connection, + &coin->cpi.denom_pub_hash, + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, + "PURSE CREATE")) + ? GNUNET_NO : GNUNET_SYSERR; + } + if (dk->denom_pub.cipher != coin->cpi.denom_sig.cipher) + { + /* denomination cipher and denomination signature cipher not the same */ + GNUNET_JSON_parse_free (spec); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, + NULL)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + coin->deposit_fee = dk->meta.fees.deposit; + if (0 < TALER_amount_cmp (&coin->deposit_fee, + &coin->amount)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE, + NULL); + } + GNUNET_assert (0 <= + TALER_amount_subtract (&coin->amount_minus_fee, + &coin->amount, + &coin->deposit_fee)); + + /* check coin signature */ + switch (dk->denom_pub.cipher) + { + case TALER_DENOMINATION_RSA: + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++; + break; + case TALER_DENOMINATION_CS: + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++; + break; + default: + break; + } + if (GNUNET_YES != + TALER_test_coin_valid (&coin->cpi, + &dk->denom_pub)) + { + TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); + GNUNET_JSON_parse_free (spec); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, + NULL)) + ? GNUNET_NO : GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TEH_common_deposit_check_purse_deposit ( + struct MHD_Connection *connection, + const struct TEH_DepositedCoin *coin, + const struct TALER_PurseContractPublicKeyP *purse_pub, + uint32_t min_age) +{ + if (GNUNET_OK != + TALER_wallet_purse_deposit_verify (TEH_base_url, + purse_pub, + &coin->amount, + &coin->cpi.denom_pub_hash, + &coin->cpi.h_age_commitment, + &coin->cpi.coin_pub, + &coin->coin_sig)) + { + TALER_LOG_WARNING ( + "Invalid coin signature to deposit into purse\n"); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID, + TEH_base_url)) + ? GNUNET_NO + : GNUNET_SYSERR; + } + + /* Check and verify the age restriction. */ + if (coin->no_attest != coin->cpi.no_age_commitment) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + /* FIXME: other error code? */ + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, + "mismatch of attest and age_commitment"); + } + + if (coin->cpi.no_age_commitment) + return GNUNET_OK; /* unrestricted coin */ + /* age attestation must be valid */ + if (GNUNET_OK != + TALER_age_commitment_verify (&coin->age_commitment, + min_age, + &coin->attest)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + /* FIXME: other error code? */ + TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, + "invalid attest for minimum age"); + } + return GNUNET_OK; +} + + +/** + * Release data structures of @a coin. Note that + * @a coin itself is NOT freed. + * + * @param[in] coin information to release + */ +void +TEH_common_deposit_free_coin (struct TEH_DepositedCoin *coin) +{ + TALER_denom_sig_free (&coin->cpi.denom_sig); + if (! coin->cpi.no_age_commitment) + TALER_age_commitment_free (&coin->age_commitment); +} + + +#if LEGACY + +if (0 > + TALER_amount_add (&pcc->deposit_total, + &pcc->deposit_total, + &coin->amount_minus_fee)) +{ + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, + "total deposit contribution"); +} + + +{ + MHD_RESULT mhd_ret = MHD_NO; + enum GNUNET_DB_QueryStatus qs; + + /* make sure coin is 'known' in database */ + for (unsigned int tries = 0; triescpi, + connection, + &coin->known_coin_id, + &mhd_ret); + /* no transaction => no serialization failures should be possible */ + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; + } + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + GNUNET_break (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "make_coin_known")) + ? GNUNET_NO : GNUNET_SYSERR; + } + if (qs < 0) + return (MHD_YES == mhd_ret) ? GNUNET_NO : GNUNET_SYSERR; +} +return GNUNET_OK; +} +#endif diff --git a/src/exchange/taler-exchange-httpd_common_deposit.h b/src/exchange/taler-exchange-httpd_common_deposit.h new file mode 100644 index 000000000..d6940c5db --- /dev/null +++ b/src/exchange/taler-exchange-httpd_common_deposit.h @@ -0,0 +1,130 @@ +/* + This file is part of TALER + Copyright (C) 2022 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 + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_common_deposit.h + * @brief shared logic for handling deposited coins + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_COMMON_DEPOSIT_H +#define TALER_EXCHANGE_HTTPD_COMMON_DEPOSIT_H + +#include +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" + + +/** + * Information about an individual coin being deposited. + */ +struct TEH_DepositedCoin +{ + /** + * Public information about the coin. + */ + struct TALER_CoinPublicInfo cpi; + + /** + * Signature affirming spending the coin. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Amount to be put into the purse from this coin. + */ + struct TALER_Amount amount; + + /** + * Deposit fee applicable for this coin. + */ + struct TALER_Amount deposit_fee; + + /** + * Amount to be put into the purse from this coin. + */ + struct TALER_Amount amount_minus_fee; + + /** + * Age attestation provided, set if @e no_attest is false. + */ + struct TALER_AgeAttestation attest; + + /** + * Age commitment provided, set if @e cpi.no_age_commitment is false. + */ + struct TALER_AgeCommitment age_commitment; + + /** + * ID of the coin in known_coins. + */ + uint64_t known_coin_id; + + /** + * True if @e attest was not provided. + */ + bool no_attest; + +}; + + +/** + * Parse a coin and check signature of the coin and the denomination + * signature over the coin. + * + * @param[in,out] connection our HTTP connection + * @param[out] coin coin to initialize + * @param jcoin coin to parse + * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned, + * #GNUNET_SYSERR on failure and no error could be returned + */ +enum GNUNET_GenericReturnValue +TEH_common_deposit_parse_coin ( + struct MHD_Connection *connection, + struct TEH_DepositedCoin *coin, + const json_t *jcoin); + + +/** + * Check that the deposited @a coin is valid for @a purse_pub + * and has a valid age commitment for @a min_age. + * + * @param[in,out] connection our HTTP connection + * @param coin the coin to evaluate + * @param purse_pub public key of the purse the coin was deposited into + * @param min_age minimum age restriction expected for this purse + * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned, + * #GNUNET_SYSERR on failure and no error could be returned + */ +enum GNUNET_GenericReturnValue +TEH_common_deposit_check_purse_deposit ( + struct MHD_Connection *connection, + const struct TEH_DepositedCoin *coin, + const struct TALER_PurseContractPublicKeyP *purse_pub, + uint32_t min_age); + + +/** + * Release data structures of @a coin. Note that + * @a coin itself is NOT freed. + * + * @param[in] coin information to release + */ +void +TEH_common_deposit_free_coin (struct TEH_DepositedCoin *coin); + +#endif diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index c5f98d21e..672e7b6ea 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -452,35 +452,6 @@ TEH_handler_deposit (struct MHD_Connection *connection, "preflight failure"); } -#if NOT_MOVED - { - MHD_RESULT mhd_ret = MHD_NO; - enum GNUNET_DB_QueryStatus qs; - - /* make sure coin is 'known' in database */ - for (unsigned int tries = 0; tries no serialization failures should be possible */ - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - break; - } - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - "make_coin_known"); - } - if (qs < 0) - return mhd_ret; - } -#endif - /* execute transaction */ { MHD_RESULT mhd_ret; diff --git a/src/exchange/taler-exchange-httpd_purses_create.c b/src/exchange/taler-exchange-httpd_purses_create.c index b016aeee4..e227f2395 100644 --- a/src/exchange/taler-exchange-httpd_purses_create.c +++ b/src/exchange/taler-exchange-httpd_purses_create.c @@ -25,52 +25,15 @@ #include #include #include -#include #include "taler_json_lib.h" #include "taler_mhd_lib.h" +#include "taler-exchange-httpd_common_deposit.h" #include "taler-exchange-httpd_purses_create.h" #include "taler-exchange-httpd_responses.h" #include "taler_exchangedb_lib.h" #include "taler-exchange-httpd_keys.h" -/** - * Information about an individual coin being deposited. - */ -struct Coin -{ - /** - * Public information about the coin. - */ - struct TALER_CoinPublicInfo cpi; - - /** - * Signature affirming spending the coin. - */ - struct TALER_CoinSpendSignatureP coin_sig; - - /** - * Amount to be put into the purse from this coin. - */ - struct TALER_Amount amount; - - /** - * Deposit fee applicable to this coin. - */ - struct TALER_Amount deposit_fee; - - /** - * Amount to be put into the purse from this coin. - */ - struct TALER_Amount amount_minus_fee; - - /** - * ID of the coin in known_coins. - */ - uint64_t known_coin_id; -}; - - /** * Closure for #create_transaction. */ @@ -124,7 +87,7 @@ struct PurseCreateContext /** * Array of coins being deposited. */ - struct Coin *coins; + struct TEH_DepositedCoin *coins; /** * Length of the @e coins array. @@ -295,10 +258,16 @@ create_transaction (void *cls, /* 2) deposit all coins */ for (unsigned int i = 0; inum_coins; i++) { - struct Coin *coin = &pcc->coins[i]; + struct TEH_DepositedCoin *coin = &pcc->coins[i]; bool balance_ok = false; bool conflict = true; + qs = TEH_make_coin_known (&coin->cpi, + connection, + &coin->known_coin_id, + mhd_ret); + if (qs < 0) + return qs; qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls, pcc->purse_pub, &coin->cpi.coin_pub, @@ -461,268 +430,37 @@ create_transaction (void *cls, static enum GNUNET_GenericReturnValue parse_coin (struct MHD_Connection *connection, struct PurseCreateContext *pcc, - struct Coin *coin, + struct TEH_DepositedCoin *coin, const json_t *jcoin) { - struct TALER_AgeAttestation attest = {0}; - bool no_attest = true; - struct TALER_AgeCommitment age_commitment = {0}; - bool no_age_commitment = true; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("amount", - TEH_currency, - &coin->amount), - GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", - &coin->cpi.denom_pub_hash), - TALER_JSON_spec_denom_sig ("ub_sig", - &coin->cpi.denom_sig), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("attest", - &attest), - &no_attest), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_age_commitment ("age_commitment", - &age_commitment), - &no_age_commitment), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &coin->coin_sig), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &coin->cpi.coin_pub), - GNUNET_JSON_spec_end () - }; + enum GNUNET_GenericReturnValue iret; + if (GNUNET_OK != + (iret = TEH_common_deposit_parse_coin (connection, + coin, + jcoin))) + return iret; + + if (GNUNET_OK != + (iret = TEH_common_deposit_check_purse_deposit ( + connection, + coin, + pcc->purse_pub, + pcc->min_age))) + return iret; + if (0 > + TALER_amount_add (&pcc->deposit_total, + &pcc->deposit_total, + &coin->amount_minus_fee)) { - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (connection, - jcoin, - spec); - if (GNUNET_OK != res) - return res; - } - - { - struct TALER_AgeCommitmentHash h_age; - - if (no_age_commitment) - memset (&h_age, 0, sizeof (h_age)); - else - TALER_age_commitment_hash (&age_commitment, - &h_age); - if (GNUNET_OK != - TALER_wallet_purse_deposit_verify (TEH_base_url, - pcc->purse_pub, - &coin->amount, - &coin->cpi.denom_pub_hash, - &h_age, - &coin->cpi.coin_pub, - &coin->coin_sig)) - { - TALER_LOG_WARNING ( - "Invalid coin signature on /purses/$PID/create request\n"); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_PURSE_CREATE_COIN_SIGNATURE_INVALID, - TEH_base_url)) - ? GNUNET_NO : GNUNET_SYSERR; - } - } - /* check denomination exists and is valid */ - { - struct TEH_DenominationKey *dk; - MHD_RESULT mret; - - dk = TEH_keys_denomination_by_hash (&coin->cpi.denom_pub_hash, - connection, - &mret); - if (NULL == dk) - { - GNUNET_JSON_parse_free (spec); - return (MHD_YES == mret) ? GNUNET_NO : GNUNET_SYSERR; - } - if (0 > TALER_amount_cmp (&dk->meta.value, - &coin->amount)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE, - NULL)) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time)) - { - /* This denomination is past the expiration time for deposits */ - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, - &coin->cpi.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, - "PURSE CREATE")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) - { - /* This denomination is not yet valid */ - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, - &coin->cpi.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, - "PURSE CREATE")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (dk->recoup_possible) - { - /* This denomination has been revoked */ - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, - &coin->cpi.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, - "PURSE CREATE")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (dk->denom_pub.cipher != coin->cpi.denom_sig.cipher) - { - /* denomination cipher and denomination signature cipher not the same */ - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, - NULL)) - ? GNUNET_NO : GNUNET_SYSERR; - } - - coin->deposit_fee = dk->meta.fees.deposit; - if (0 < TALER_amount_cmp (&coin->deposit_fee, - &coin->amount)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE, - NULL); - } - GNUNET_assert (0 <= - TALER_amount_subtract (&coin->amount_minus_fee, - &coin->amount, - &coin->deposit_fee)); - - // Check and verify the age restriction. Needs to happen before - // coin-signature check, because we set the h_age_commitment here. - { - if (no_attest != no_age_commitment) - - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - /* FIXME: other error code? */ - TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, - "mismatch of attest and age_commitment"); - } - - if (! no_age_commitment) - { - // attestation must be valid. - if (GNUNET_OK != - TALER_age_commitment_verify ( - &age_commitment, - pcc->min_age, - &attest)) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - /* FIXME: other error code? */ - TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, - "invalid attest for minimum age"); - } - - // Save the hash of the age commitment in the coin's public info, so we - // can verify the signature later. - TALER_age_commitment_hash (&age_commitment, - &coin->cpi.h_age_commitment); - coin->cpi.no_age_commitment = false; - - } - } - - - /* check coin signature */ - switch (dk->denom_pub.cipher) - { - case TALER_DENOMINATION_RSA: - TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++; - break; - case TALER_DENOMINATION_CS: - TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++; - break; - default: - break; - } - if (GNUNET_YES != - TALER_test_coin_valid (&coin->cpi, - &dk->denom_pub)) - { - TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, - NULL)) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (&pcc->deposit_total, - &pcc->deposit_total, - &coin->amount_minus_fee)) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, - "total deposit contribution"); - } - } - { - MHD_RESULT mhd_ret = MHD_NO; - enum GNUNET_DB_QueryStatus qs; - - /* make sure coin is 'known' in database */ - for (unsigned int tries = 0; triescpi, - connection, - &coin->known_coin_id, - &mhd_ret); - /* no transaction => no serialization failures should be possible */ - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - break; - } - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - GNUNET_break (0); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - "make_coin_known")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (qs < 0) - return (MHD_YES == mhd_ret) ? GNUNET_NO : GNUNET_SYSERR; + GNUNET_break (0); + return (MHD_YES == + TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, + "total deposit contribution")) + ? GNUNET_NO + : GNUNET_SYSERR; } return GNUNET_OK; } @@ -843,11 +581,11 @@ TEH_handler_purses_create ( } /* parse deposits */ pcc.coins = GNUNET_new_array (pcc.num_coins, - struct Coin); + struct TEH_DepositedCoin); json_array_foreach (deposits, idx, deposit) { enum GNUNET_GenericReturnValue res; - struct Coin *coin = &pcc.coins[idx]; + struct TEH_DepositedCoin *coin = &pcc.coins[idx]; res = parse_coin (connection, &pcc, @@ -875,13 +613,14 @@ TEH_handler_purses_create ( TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != - TALER_wallet_purse_create_verify (pcc.purse_expiration, - &pcc.h_contract_terms, - &pcc.merge_pub, - pcc.min_age, - &pcc.amount, - pcc.purse_pub, - &pcc.purse_sig)) + TALER_wallet_purse_create_verify ( + pcc.purse_expiration, + &pcc.h_contract_terms, + &pcc.merge_pub, + pcc.min_age, + &pcc.amount, + pcc.purse_pub, + &pcc.purse_sig)) { TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n"); GNUNET_JSON_parse_free (spec); diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.c b/src/exchange/taler-exchange-httpd_purses_deposit.c index 4158b2936..87e2f5689 100644 --- a/src/exchange/taler-exchange-httpd_purses_deposit.c +++ b/src/exchange/taler-exchange-httpd_purses_deposit.c @@ -25,53 +25,16 @@ #include #include #include -#include #include "taler_dbevents.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" +#include "taler-exchange-httpd_common_deposit.h" #include "taler-exchange-httpd_purses_deposit.h" #include "taler-exchange-httpd_responses.h" #include "taler_exchangedb_lib.h" #include "taler-exchange-httpd_keys.h" -/** - * Information about an individual coin being deposited. - */ -struct Coin -{ - /** - * Public information about the coin. - */ - struct TALER_CoinPublicInfo cpi; - - /** - * Signature affirming spending the coin. - */ - struct TALER_CoinSpendSignatureP coin_sig; - - /** - * Amount to be put into the purse from this coin. - */ - struct TALER_Amount amount; - - /** - * Deposit fee applicable for this coin. - */ - struct TALER_Amount deposit_fee; - - /** - * Amount to be put into the purse from this coin. - */ - struct TALER_Amount amount_minus_fee; - - /** - * ID of the coin in known_coins. - */ - uint64_t known_coin_id; -}; - - /** * Closure for #deposit_transaction. */ @@ -110,7 +73,7 @@ struct PurseDepositContext /** * Array of coins being deposited. */ - struct Coin *coins; + struct TEH_DepositedCoin *coins; /** * Length of the @e coins array. @@ -200,10 +163,16 @@ deposit_transaction (void *cls, qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; for (unsigned int i = 0; inum_coins; i++) { - struct Coin *coin = &pcc->coins[i]; + struct TEH_DepositedCoin *coin = &pcc->coins[i]; bool balance_ok = false; bool conflict = true; + qs = TEH_make_coin_known (&coin->cpi, + connection, + &coin->known_coin_id, + mhd_ret); + if (qs < 0) + return qs; qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls, pcc->purse_pub, &coin->cpi.coin_pub, @@ -305,267 +274,33 @@ deposit_transaction (void *cls, static enum GNUNET_GenericReturnValue parse_coin (struct MHD_Connection *connection, struct PurseDepositContext *pcc, - struct Coin *coin, + struct TEH_DepositedCoin *coin, const json_t *jcoin) { - struct TALER_AgeAttestation attest = {0}; - bool no_attest = true; - struct TALER_AgeCommitment age_commitment = {0}; - bool no_age_commitment = true; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("amount", - TEH_currency, - &coin->amount), - GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", - &coin->cpi.denom_pub_hash), - TALER_JSON_spec_denom_sig ("ub_sig", - &coin->cpi.denom_sig), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("attest", - &attest), - &no_attest), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_age_commitment ("age_commitment", - &age_commitment), - &no_age_commitment), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &coin->coin_sig), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &coin->cpi.coin_pub), - GNUNET_JSON_spec_end () - }; + enum GNUNET_GenericReturnValue iret; + if (GNUNET_OK != + (iret = TEH_common_deposit_parse_coin (connection, + coin, + jcoin))) + return iret; + if (GNUNET_OK != + (iret = TEH_common_deposit_check_purse_deposit ( + connection, + coin, + pcc->purse_pub, + pcc->min_age))) + return iret; + if (0 > + TALER_amount_add (&pcc->deposit_total, + &pcc->deposit_total, + &coin->amount_minus_fee)) { - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (connection, - jcoin, - spec); - if (GNUNET_OK != res) - return res; - } - { - struct TALER_AgeCommitmentHash h_age; - - if (no_age_commitment) - memset (&h_age, 0, sizeof (h_age)); - else - TALER_age_commitment_hash (&age_commitment, - &h_age); - - if (GNUNET_OK != - TALER_wallet_purse_deposit_verify (TEH_base_url, - pcc->purse_pub, - &coin->amount, - &coin->cpi.denom_pub_hash, - &h_age, - &coin->cpi.coin_pub, - &coin->coin_sig)) - { - TALER_LOG_WARNING ("Invalid signature on /purses/$PID/deposit request\n"); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID, - TEH_base_url)) - ? GNUNET_NO : GNUNET_SYSERR; - } - } - /* check denomination exists and is valid */ - { - struct TEH_DenominationKey *dk; - MHD_RESULT mret; - - dk = TEH_keys_denomination_by_hash (&coin->cpi.denom_pub_hash, - connection, - &mret); - if (NULL == dk) - { - GNUNET_JSON_parse_free (spec); - return (MHD_YES == mret) ? GNUNET_NO : GNUNET_SYSERR; - } - if (0 > TALER_amount_cmp (&dk->meta.value, - &coin->amount)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE, - NULL)) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time)) - { - /* This denomination is past the expiration time for deposits */ - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, - &coin->cpi.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, - "PURSE DEPOSIT")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) - { - /* This denomination is not yet valid */ - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, - &coin->cpi.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, - "PURSE DEPOSIT")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (dk->recoup_possible) - { - /* This denomination has been revoked */ - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, - &coin->cpi.denom_pub_hash, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, - "PURSE DEPOSIT")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (dk->denom_pub.cipher != coin->cpi.denom_sig.cipher) - { - /* denomination cipher and denomination signature cipher not the same */ - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, - NULL)) - ? GNUNET_NO : GNUNET_SYSERR; - } - - coin->deposit_fee = dk->meta.fees.deposit; - if (0 < TALER_amount_cmp (&coin->deposit_fee, - &coin->amount)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE, - NULL); - } - GNUNET_assert (0 <= - TALER_amount_subtract (&coin->amount_minus_fee, - &coin->amount, - &coin->deposit_fee)); - - /* Check and verify the age restriction. Needs to happen before - coin-signature check, because we set the h_age_commitment here. */ - { - if (no_attest != no_age_commitment) - - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - /* FIXME: other error code? */ - TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, - "mismatch of attest and age_commitment"); - } - - if (! no_age_commitment) - { - /* attestation must be valid. */ - if (GNUNET_OK != - TALER_age_commitment_verify ( - &age_commitment, - pcc->min_age, - &attest)) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - /* FIXME: other error code? */ - TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH, - "invalid attest for minimum age"); - } - - /* Save the hash of the age commitment in the coin's public info, so we - can verify the signature later. */ - TALER_age_commitment_hash (&age_commitment, - &coin->cpi.h_age_commitment); - coin->cpi.no_age_commitment = false; - - } - } - - /* check coin signature */ - switch (dk->denom_pub.cipher) - { - case TALER_DENOMINATION_RSA: - TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++; - break; - case TALER_DENOMINATION_CS: - TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++; - break; - default: - break; - } - if (GNUNET_YES != - TALER_test_coin_valid (&coin->cpi, - &dk->denom_pub)) - { - TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, - NULL)) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (&pcc->deposit_total, - &pcc->deposit_total, - &coin->amount_minus_fee)) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, - "total deposit contribution"); - } - } - - { - MHD_RESULT mhd_ret = MHD_NO; - enum GNUNET_DB_QueryStatus qs; - - /* make sure coin is 'known' in database */ - for (unsigned int tries = 0; triescpi, - connection, - &coin->known_coin_id, - &mhd_ret); - /* no transaction => no serialization failures should be possible */ - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - break; - } - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - GNUNET_break (0); - return (MHD_YES == - TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - "make_coin_known")) - ? GNUNET_NO : GNUNET_SYSERR; - } - if (qs < 0) - return (MHD_YES == mhd_ret) ? GNUNET_NO : GNUNET_SYSERR; + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, + "total deposit contribution"); } return GNUNET_OK; } @@ -667,11 +402,11 @@ TEH_handler_purses_deposit ( /* parse deposits */ pcc.coins = GNUNET_new_array (pcc.num_coins, - struct Coin); + struct TEH_DepositedCoin); json_array_foreach (deposits, idx, deposit) { enum GNUNET_GenericReturnValue res; - struct Coin *coin = &pcc.coins[idx]; + struct TEH_DepositedCoin *coin = &pcc.coins[idx]; res = parse_coin (connection, &pcc,