From 1277f8445d0497107c5bd41b35007480d6a4472a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 22 Mar 2015 16:09:01 +0100 Subject: [PATCH] include fees in amounts being signed, check available balance on refresh --- src/include/taler_signatures.h | 44 +++++-- src/mint/plugin_mintdb_postgres.c | 2 +- src/mint/taler-mint-httpd_db.c | 178 ++++++++++++++++---------- src/mint/taler-mint-httpd_db.h | 2 +- src/mint/taler-mint-httpd_deposit.c | 6 +- src/mint/taler-mint-httpd_refresh.c | 10 +- src/mint/taler-mint-httpd_responses.c | 14 +- src/mint/taler_mintdb_plugin.h | 28 ++-- src/mint/test_mint_db.c | 2 +- src/mint/test_mint_deposits.c | 8 +- 10 files changed, 183 insertions(+), 111 deletions(-) diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index b91639321..4566764d0 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -131,6 +131,15 @@ struct TALER_WithdrawRequest */ struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + /** + * Value of the coin being minted (matching the denomination key) + * plus the transaction fee. We include this in what is being + * signed so that we can verify a reserve's remaining total balance + * without needing to access the respective denomination key + * information each time. + */ + struct TALER_AmountNBO amount_with_fee; + /** * Hash of the denomination public key for the coin that is withdrawn. */ @@ -171,9 +180,11 @@ struct TALER_DepositRequest uint64_t transaction_id GNUNET_PACKED; /** - * Amount to be deposited. + * Amount to be deposited, including fee. */ - struct TALER_AmountNBO amount; + struct TALER_AmountNBO amount_with_fee; + /* FIXME: we should probably also include the value of + the depositing fee here as well! */ /** * The coin's public key. @@ -211,9 +222,12 @@ struct TALER_DepositConfirmation uint64_t transaction_id GNUNET_PACKED; /** - * Amount to be deposited. + * Amount to be deposited, including fee. */ - struct TALER_AmountNBO amount; + struct TALER_AmountNBO amount_with_fee; + + /* FIXME: we should probably also include the value of + the depositing fee here as well! */ /** * The coin's public key. @@ -245,11 +259,17 @@ struct RefreshMeltCoinSignature struct GNUNET_HashCode melt_hash; /** - * How much of the value of the coin should be melted? - * This amount includes the fees, so the final amount contributed - * to the melt is this value minus the fee for melting the coin. + * How much of the value of the coin should be melted? This amount + * includes the fees, so the final amount contributed to the melt is + * this value minus the fee for melting the coin. We include the + * fee in what is being signed so that we can verify a reserve's + * remaining total balance without needing to access the respective + * denomination key information each time. */ - struct TALER_AmountNBO amount; + struct TALER_AmountNBO amount_with_fee; + + /* FIXME: we should probably also include the value of + the melting fee here as well! */ /** * The coin's public key. @@ -282,9 +302,13 @@ struct RefreshMeltSessionSignature /** * What is the total value of the coins created during the - * refresh, excluding fees? + * refresh, including melting fee! */ - struct TALER_AmountNBO amount; + struct TALER_AmountNBO amount_with_fee; + + /* FIXME: we should probably also include the value of + the melting fee here as well! */ + }; diff --git a/src/mint/plugin_mintdb_postgres.c b/src/mint/plugin_mintdb_postgres.c index 16b134350..1d7a965cf 100644 --- a/src/mint/plugin_mintdb_postgres.c +++ b/src/mint/plugin_mintdb_postgres.c @@ -1401,7 +1401,7 @@ postgres_insert_deposit (void *cls, &denom_sig_enc); json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT); TALER_amount_hton (&amount_nbo, - &deposit->amount); + &deposit->amount_with_fee); struct TALER_DB_QueryParam params[]= { TALER_DB_QUERY_PARAM_PTR (&deposit->coin.coin_pub), TALER_DB_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size), diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index d347126d9..b3e239c89 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -34,6 +34,64 @@ #include "plugin.h" +/** + * Calculate the total value of all transactions performed. + * Stores @a off plus the cost of all transactions in @a tl + * in @a ret. + * + * @param pos transaction list to process + * @param off offset to use as the starting value + * @param ret where the resulting total is to be stored + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +calculate_transaction_list_totals (struct TALER_MINT_DB_TransactionList *tl, + const struct TALER_Amount *off, + struct TALER_Amount *ret) +{ + struct TALER_Amount spent = *off; + struct TALER_MINT_DB_TransactionList *pos; + + for (pos = tl; NULL != pos; pos = pos->next) + { + switch (pos->type) + { + case TALER_MINT_DB_TT_DEPOSIT: + if (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &pos->details.deposit->amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + break; + case TALER_MINT_DB_TT_REFRESH_MELT: + if (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &pos->details.melt->amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + break; + case TALER_MINT_DB_TT_LOCK: + /* should check if lock is still active, + and if it is for THIS operation; if + lock is inactive, delete it; if lock + is for THIS operation, ignore it; + if lock is for another operation, + count it! */ + GNUNET_assert (0); // FIXME: not implemented! (#3625) + return GNUNET_SYSERR; + } + } + *ret = spent; + return GNUNET_OK; +} + + /** * Execute a deposit. The validity of the coin and signature * have already been checked. The database must now check that @@ -50,11 +108,9 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, { struct TALER_MINTDB_Session *session; struct TALER_MINT_DB_TransactionList *tl; - struct TALER_MINT_DB_TransactionList *pos; struct TALER_Amount spent; struct TALER_Amount value; struct TALER_Amount fee_deposit; - struct TALER_Amount fee_refresh; struct MintKeyState *mks; struct TALER_MINT_DenomKeyIssuePriv *dki; int ret; @@ -76,7 +132,7 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, &deposit->h_contract, deposit->transaction_id, &deposit->merchant_pub, - &deposit->amount); + &deposit->amount_with_fee); } mks = TALER_MINT_key_state_acquire (); dki = TALER_MINT_get_denom_key (mks, @@ -85,8 +141,6 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, &dki->issue.value); TALER_amount_ntoh (&fee_deposit, &dki->issue.fee_deposit); - TALER_amount_ntoh (&fee_refresh, - &dki->issue.fee_refresh); TALER_MINT_key_state_release (mks); if (GNUNET_OK != @@ -96,69 +150,29 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } + /* fee for THIS transaction */ + spent = deposit->amount_with_fee; + if (TALER_amount_cmp (&fee_deposit, + &spent) < 0) + { + return (MHD_YES == + TALER_MINT_reply_external_error (connection, + "deposited amount smaller than depositing fee")) + ? GNUNET_NO : GNUNET_SYSERR; + } + /* add cost of all previous transactions */ tl = plugin->get_coin_transactions (plugin->cls, session, &deposit->coin.coin_pub); - spent = fee_deposit; /* fee for THIS transaction */ if (GNUNET_OK != - TALER_amount_add (&spent, - &spent, - &deposit->amount)) + calculate_transaction_list_totals (tl, + &spent, + &spent)) { - GNUNET_break (0); plugin->free_coin_transaction_list (plugin->cls, tl); return TALER_MINT_reply_internal_db_error (connection); } - - for (pos = tl; NULL != pos; pos = pos->next) - { - switch (pos->type) - { - case TALER_MINT_DB_TT_DEPOSIT: - if ( (GNUNET_OK != - TALER_amount_add (&spent, - &spent, - &pos->details.deposit->amount)) || - (GNUNET_OK != - TALER_amount_add (&spent, - &spent, - &fee_deposit)) ) - { - GNUNET_break (0); - plugin->free_coin_transaction_list (plugin->cls, - tl); - return TALER_MINT_reply_internal_db_error (connection); - } - break; - case TALER_MINT_DB_TT_REFRESH_MELT: - if ( (GNUNET_OK != - TALER_amount_add (&spent, - &spent, - &pos->details.melt->amount)) || - (GNUNET_OK != - TALER_amount_add (&spent, - &spent, - &fee_refresh)) ) - { - GNUNET_break (0); - plugin->free_coin_transaction_list (plugin->cls, - tl); - return TALER_MINT_reply_internal_db_error (connection); - } - break; - case TALER_MINT_DB_TT_LOCK: - /* should check if lock is still active, - and if it is for THIS operation; if - lock is inactive, delete it; if lock - is for THIS operation, ignore it; - if lock is for another operation, - count it! */ - GNUNET_assert (0); // FIXME: not implemented! (#3625) - break; - } - } - if (0 < TALER_amount_cmp (&spent, &value)) { @@ -197,7 +211,7 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, &deposit->h_contract, deposit->transaction_id, &deposit->merchant_pub, - &deposit->amount); + &deposit->amount_with_fee); } @@ -501,8 +515,11 @@ refresh_accept_melts (struct MHD_Connection *connection, { struct TALER_MINT_DenomKeyIssue *dki; struct TALER_MINT_DB_TransactionList *tl; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; struct TALER_Amount coin_value; struct TALER_Amount coin_residual; + struct TALER_Amount spent; struct RefreshMelt melt; int res; @@ -518,26 +535,51 @@ refresh_accept_melts (struct MHD_Connection *connection, "denom not found")) ? GNUNET_NO : GNUNET_SYSERR; + TALER_amount_ntoh (&fee_deposit, + &dki->fee_deposit); + TALER_amount_ntoh (&fee_refresh, + &dki->fee_refresh); TALER_amount_ntoh (&coin_value, &dki->value); + /* fee for THIS transaction; the melt amount includes the fee! */ + spent = coin_details->melt_amount_with_fee; + if (TALER_amount_cmp (&fee_refresh, + &spent) < 0) + { + return (MHD_YES == + TALER_MINT_reply_external_error (connection, + "melt amount smaller than melting fee")) + ? GNUNET_NO : GNUNET_SYSERR; + } + /* add historic transaction costs of this coin */ tl = plugin->get_coin_transactions (plugin->cls, session, &coin_public_info->coin_pub); - /* FIXME: #3636: compute how much value is left with this coin and - compare to `expected_value`! (subtract from "coin_value") */ - coin_residual = coin_value; + if (GNUNET_OK != + calculate_transaction_list_totals (tl, + &spent, + &spent)) + { + GNUNET_break (0); + plugin->free_coin_transaction_list (plugin->cls, + tl); + return TALER_MINT_reply_internal_db_error (connection); + } /* Refuse to refresh when the coin does not have enough money left to * pay the refreshing fees of the coin. */ - - if (TALER_amount_cmp (&coin_residual, - &coin_details->melt_amount) < 0) + if (TALER_amount_cmp (&coin_value, + &spent) < 0) { + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&coin_residual, + &spent, + &coin_details->melt_amount_with_fee)); res = (MHD_YES == TALER_MINT_reply_refresh_melt_insufficient_funds (connection, &coin_public_info->coin_pub, coin_value, tl, - coin_details->melt_amount, + coin_details->melt_amount_with_fee, coin_residual)) ? GNUNET_NO : GNUNET_SYSERR; plugin->free_coin_transaction_list (plugin->cls, @@ -550,7 +592,7 @@ refresh_accept_melts (struct MHD_Connection *connection, melt.coin = *coin_public_info; melt.coin_sig = coin_details->melt_sig; melt.melt_hash = *melt_hash; - melt.amount = coin_details->melt_amount; + melt.amount_with_fee = coin_details->melt_amount_with_fee; if (GNUNET_OK != plugin->insert_refresh_melt (plugin->cls, session, diff --git a/src/mint/taler-mint-httpd_db.h b/src/mint/taler-mint-httpd_db.h index aefbfc424..52e86f898 100644 --- a/src/mint/taler-mint-httpd_db.h +++ b/src/mint/taler-mint-httpd_db.h @@ -95,7 +95,7 @@ struct MeltDetails * This amount includes the fees, so the final amount contributed * to the melt is this value minus the fee for melting the coin. */ - struct TALER_Amount melt_amount; + struct TALER_Amount melt_amount_with_fee; }; diff --git a/src/mint/taler-mint-httpd_deposit.c b/src/mint/taler-mint-httpd_deposit.c index e411e0d8e..647cd8242 100644 --- a/src/mint/taler-mint-httpd_deposit.c +++ b/src/mint/taler-mint-httpd_deposit.c @@ -65,8 +65,8 @@ verify_and_execute_deposit (struct MHD_Connection *connection, dr.h_contract = deposit->h_contract; dr.h_wire = deposit->h_wire; dr.transaction_id = GNUNET_htonll (deposit->transaction_id); - TALER_amount_hton (&dr.amount, - &deposit->amount); + TALER_amount_hton (&dr.amount_with_fee, + &deposit->amount_with_fee); dr.coin_pub = deposit->coin.coin_pub; if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_WALLET_DEPOSIT, @@ -167,7 +167,7 @@ parse_and_handle_deposit_request (struct MHD_Connection *connection, GNUNET_free (wire_enc); deposit.wire = wire; - deposit.amount = *amount; + deposit.amount_with_fee = *amount; res = verify_and_execute_deposit (connection, &deposit); TALER_MINT_release_parsed_data (spec); diff --git a/src/mint/taler-mint-httpd_refresh.c b/src/mint/taler-mint-httpd_refresh.c index d4979288d..602fc67f6 100644 --- a/src/mint/taler-mint-httpd_refresh.c +++ b/src/mint/taler-mint-httpd_refresh.c @@ -109,8 +109,8 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_SESSION); body.purpose.size = htonl (sizeof (struct RefreshMeltSessionSignature)); body.melt_hash = melt_hash; - TALER_amount_hton (&body.amount, - &coin_melt_details->melt_amount); + TALER_amount_hton (&body.amount_with_fee, + &coin_melt_details->melt_amount_with_fee); if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_REFRESH_MELT_SESSION, &body.purpose, @@ -252,7 +252,7 @@ get_coin_public_info (struct MHD_Connection *connection, ? GNUNET_NO : GNUNET_SYSERR; } r_melt_detail->melt_sig = melt_sig; - r_melt_detail->melt_amount = amount; + r_melt_detail->melt_amount_with_fee = amount; TALER_MINT_release_parsed_data (spec); return GNUNET_OK; } @@ -288,8 +288,8 @@ verify_coin_public_info (struct MHD_Connection *connection, body.purpose.size = htonl (sizeof (struct RefreshMeltCoinSignature)); body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_COIN); body.melt_hash = *melt_hash; - TALER_amount_hton (&body.amount, - &r_melt_detail->melt_amount); + TALER_amount_hton (&body.amount_with_fee, + &r_melt_detail->melt_amount_with_fee); body.coin_pub = r_public_info->coin_pub; if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_REFRESH_MELT_COIN, diff --git a/src/mint/taler-mint-httpd_responses.c b/src/mint/taler-mint-httpd_responses.c index a4b442152..681b2b922 100644 --- a/src/mint/taler-mint-httpd_responses.c +++ b/src/mint/taler-mint-httpd_responses.c @@ -299,7 +299,7 @@ TALER_MINT_reply_deposit_success (struct MHD_Connection *connection, dc.h_contract = *h_contract; dc.h_wire = *h_wire; dc.transaction_id = GNUNET_htonll (transaction_id); - TALER_amount_hton (&dc.amount, + TALER_amount_hton (&dc.amount_with_fee, amount); dc.coin_pub = *coin_pub; dc.merchant = *merchant; @@ -341,14 +341,14 @@ compile_transaction_history (const struct TALER_MINT_DB_TransactionList *tl) const struct Deposit *deposit = pos->details.deposit; type = "deposit"; - value = deposit->amount; + value = deposit->amount_with_fee; dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_DEPOSIT); dr.purpose.size = htonl (sizeof (struct TALER_DepositRequest)); dr.h_contract = deposit->h_contract; dr.h_wire = deposit->h_wire; dr.transaction_id = GNUNET_htonll (deposit->transaction_id); - TALER_amount_hton (&dr.amount, - &deposit->amount); + TALER_amount_hton (&dr.amount_with_fee, + &deposit->amount_with_fee); dr.coin_pub = deposit->coin.coin_pub; transaction = TALER_JSON_from_ecdsa_sig (&dr.purpose, &deposit->csig); @@ -360,12 +360,12 @@ compile_transaction_history (const struct TALER_MINT_DB_TransactionList *tl) const struct RefreshMelt *melt = pos->details.melt; type = "melt"; - value = melt->amount; + value = melt->amount_with_fee; ms.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_COIN); ms.purpose.size = htonl (sizeof (struct RefreshMeltCoinSignature)); ms.melt_hash = melt->melt_hash; - TALER_amount_hton (&ms.amount, - &melt->amount); + TALER_amount_hton (&ms.amount_with_fee, + &melt->amount_with_fee); ms.coin_pub = melt->coin.coin_pub; transaction = TALER_JSON_from_ecdsa_sig (&ms.purpose, &melt->coin_sig); diff --git a/src/mint/taler_mintdb_plugin.h b/src/mint/taler_mintdb_plugin.h index bc5cd69a6..83e373340 100644 --- a/src/mint/taler_mintdb_plugin.h +++ b/src/mint/taler_mintdb_plugin.h @@ -14,13 +14,13 @@ TALER; see the file COPYING. If not, If not, see */ /** - * @file mint/mint_db.h + * @file mint/taler_mintdb_plugin.h * @brief Low-level (statement-level) database access for the mint * @author Florian Dold * @author Christian Grothoff */ -#ifndef MINT_DB_H -#define MINT_DB_H +#ifndef TALER_MINTDB_PLUGIN_H +#define TALER_MINTDB_PLUGIN_H #include #include "taler_util.h" @@ -87,6 +87,9 @@ struct CollectableBlindcoin /** * Denomination key (which coin was generated). + * FIXME: we should probably instead have the + * AMOUNT *including* fee in what is being signed + * as well! */ struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; @@ -217,10 +220,10 @@ struct Deposit uint64_t transaction_id; /** - * Fraction of the coin's remaining value to be deposited. - * The coin is identified by @e coin_pub. + * Fraction of the coin's remaining value to be deposited, including + * depositing fee (if any). The coin is identified by @e coin_pub. */ - struct TALER_Amount amount; + struct TALER_Amount amount_with_fee; }; @@ -296,11 +299,14 @@ struct RefreshMelt struct GNUNET_HashCode melt_hash; /** - * How much value is being melted? - * This amount includes the fees, so the final amount contributed - * to the melt is this value minus the fee for melting the coin. + * How much value is being melted? This amount includes the fees, + * so the final amount contributed to the melt is this value minus + * the fee for melting the coin. We include the fee in what is + * being signed so that we can verify a reserve's remaining total + * balance without needing to access the respective denomination key + * information each time. */ - struct TALER_Amount amount; + struct TALER_Amount amount_with_fee; }; @@ -397,7 +403,7 @@ struct Lock const struct GNUNET_CRYPTO_EcdsaSignature coin_sig; /** - * How much value is being melted? + * How much value is being locked? */ struct TALER_Amount amount; diff --git a/src/mint/test_mint_db.c b/src/mint/test_mint_db.c index e4d312927..c80be70c3 100644 --- a/src/mint/test_mint_db.c +++ b/src/mint/test_mint_db.c @@ -286,7 +286,7 @@ run (void *cls, deposit.wire = wire; deposit.transaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX); - deposit.amount = amount; + deposit.amount_with_fee = amount; FAILIF (GNUNET_OK != plugin->insert_deposit (plugin->cls, session, &deposit)); diff --git a/src/mint/test_mint_deposits.c b/src/mint/test_mint_deposits.c index c829e6e10..4107c1aee 100644 --- a/src/mint/test_mint_deposits.c +++ b/src/mint/test_mint_deposits.c @@ -94,12 +94,12 @@ run (void *cls, UINT64_MAX); deposit->transaction_id = GNUNET_htonll (transaction_id); /* Random amount */ - deposit->amount.value = + deposit->amount_with_fee.value = htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); - deposit->amount.fraction = + deposit->amount_with_fee.fraction = htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); - GNUNET_assert (strlen (MINT_CURRENCY) < sizeof (deposit->amount.currency)); - strcpy (deposit->amount.currency, MINT_CURRENCY); + GNUNET_assert (strlen (MINT_CURRENCY) < sizeof (deposit->amount_with_fee.currency)); + strcpy (deposit->amount_with_fee.currency, MINT_CURRENCY); /* Copy wireformat */ deposit->wire = json_loads (wire, 0, NULL); EXITIF (GNUNET_OK !=