From caf66486e70d896b1dd4eca3785fd42185a540cd Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 1 Apr 2022 16:39:07 +0200 Subject: [PATCH] work on purse creation logic --- contrib/gana | 2 +- src/exchange/taler-exchange-httpd_deposit.c | 12 +- src/exchange/taler-exchange-httpd_melt.c | 2 - .../taler-exchange-httpd_purses_create.c | 251 ++++++++++++++---- src/exchange/taler-exchange-httpd_withdraw.c | 1 - src/exchangedb/plugin_exchangedb_postgres.c | 23 +- src/include/taler_crypto_lib.h | 27 ++ src/include/taler_exchangedb_plugin.h | 23 +- src/include/taler_signatures.h | 5 + src/util/exchange_signatures.c | 113 ++++++++ 10 files changed, 400 insertions(+), 59 deletions(-) diff --git a/contrib/gana b/contrib/gana index 25eb78f2d..b799c63db 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 25eb78f2d0e20a137020dd0ab1c6474123843dbe +Subproject commit b799c63db9beda99e9151e7611a1ac4e810786ab diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index 59d25904f..011f5f159 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -178,7 +178,6 @@ deposit_transaction (void *cls, } if (in_conflict) { - TEH_plugin->rollback (TEH_plugin->cls); *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( connection, @@ -188,7 +187,6 @@ deposit_transaction (void *cls, } if (! balance_ok) { - TEH_plugin->rollback (TEH_plugin->cls); *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( connection, @@ -323,6 +321,16 @@ TEH_handler_deposit (struct MHD_Connection *connection, GNUNET_JSON_parse_free (spec); return mret; } + if (0 > TALER_amount_cmp (&dk->meta.value, + &deposit.amount_with_fee)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE, + NULL); + } if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time)) { /* This denomination is past the expiration time for deposits */ diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c index 6fe97c8f2..2ff03023a 100644 --- a/src/exchange/taler-exchange-httpd_melt.c +++ b/src/exchange/taler-exchange-httpd_melt.c @@ -183,7 +183,6 @@ melt_transaction (void *cls, if (rmc->zombie_required) { GNUNET_break_op (0); - TEH_plugin->rollback (TEH_plugin->cls); *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE, @@ -193,7 +192,6 @@ melt_transaction (void *cls, if (! balance_ok) { GNUNET_break_op (0); - TEH_plugin->rollback (TEH_plugin->cls); *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( connection, diff --git a/src/exchange/taler-exchange-httpd_purses_create.c b/src/exchange/taler-exchange-httpd_purses_create.c index ee822b259..75cb448a2 100644 --- a/src/exchange/taler-exchange-httpd_purses_create.c +++ b/src/exchange/taler-exchange-httpd_purses_create.c @@ -58,6 +58,11 @@ struct Coin * 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; }; @@ -147,22 +152,22 @@ reply_create_success (struct MHD_Connection *connection, { struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; - // FIXME: define what exactly we sign over! - struct TALER_DepositConfirmationPS dc = { - .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION), - .purpose.size = htonl (sizeof (dc)), - .h_contract_terms = pcc->h_contract_terms, - .purse_pub = *pcc->purse_pub - }; enum TALER_ErrorCode ec; - TALER_amount_hton (&dc.amount_without_fee, - &pcc->amount); if (TALER_EC_NONE != - (ec = TEH_keys_exchange_sign (&dc, - &pub, - &sig))) + (ec = TALER_exchange_online_purse_created_sign ( + &TEH_keys_exchange_sign_, + pcc->exchange_timestamp, + pcc->purse_expiration, + &pcc->amount, + &pcc->total_deposited, + pcc->purse_pub, + &pcc->merge_pub, + &pcc->h_contract_terms, + &pub, + &sig))) { + GNUNET_break (0); return TALER_MHD_reply_with_ec (connection, ec, NULL); @@ -170,8 +175,10 @@ reply_create_success (struct MHD_Connection *connection, return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, - // GNUNET_JSON_pack_timestamp ("exchange_timestamp", - // pcc->exchange_timestamp), + GNUNET_JSON_pack_amount ("total_deposited", + &pcc->deposit_total), + GNUNET_JSON_pack_timestamp ("exchange_timestamp", + pcc->exchange_timestamp), GNUNET_JSON_pack_data_auto ("exchange_sig", &sig), GNUNET_JSON_pack_data_auto ("exchange_pub", @@ -199,14 +206,120 @@ create_transaction (void *cls, { struct PurseCreateContext *pcc = cls; enum GNUNET_DB_QueryStatus qs; - bool balance_ok; - bool in_conflict; + bool in_conflict = true; - // 1) create purse - // 2) deposit all coins - // 3) if present, persist contract - // Also: nicely report errors... - qs = TEH_plugin->XXX (TEH_plugin->cls, ...); + /* 1) create purse */ + qs = TEH_plugin->insert_purse_request (TEH_plugin->cls, + &pcc->purse_pub, + &pcc->merge_pub, + pcc->purse_expiration, + &pcc->h_contract_terms, + pcc->min_age, + &pcc->amount, + &pcc->purse_sig, + &in_conflict); + if (qs < 0) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; + TALER_LOG_WARNING ( + "Failed to store create purse information in database\n"); + *mhd_ret = + TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "purse create"); + return qs; + } + if (in_conflict) + { + struct TALER_PurseMergePublicKeyP merge_pub; + struct GNUNET_TIME_Timestamp purse_expiration; + struct TALER_PrivateContractHashP h_contract_terms; + struct TALER_Amount target_amount; + struct TALER_Amount balance; + struct TALER_PurseContractSignatureP purse_sig; + + TEH_plugin->rollback (TEH_plugin->cls); + qs = TEH_plugin->select_purse_request (TEH_plugin->cls, + pcc->purse_pub, + &merge_pub, + &purse_expiration, + &h_contract_terms, + &target_amount, + &balance, + &purse_sig); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + TALER_LOG_WARNING ("Failed to fetch purse information from database\n"); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select purse request"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + *mhd_ret + = return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_CONFLICT, + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA), + GNUNET_JSON_pack_amount ("amount", + &amount), + GNUNET_JSON_pack_timestamp ("purse_expiration", + purse_expiration), + GNUNET_JSON_pack_data_auto ("purse_sig", + &purse_sig), + GNUNET_JSON_pack_data_auto ("h_contract_terms", + &h_contract_terms), + GNUNET_JSON_pack_data_auto ("merge_pub", + &merge_pub)); + return GNUNET_DB_STATUS_HARD_ERROR; + } + /* 2) deposit all coins */ + for (unsigned int i = 0; inum_coins; i++) + { + struct Coin *coin = &pcc->coins[i]; + bool balance_ok = false; + + qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls, + &pcc->purse_pub, + &coin->cpi.coin_pub, + &coin->amount, + &coin->coin_sig, + &coin->amount_minus_fee, + &balance_ok); + if (qs < 0) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; + TALER_LOG_WARNING ( + "Failed to store purse deposit information in database\n"); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "purse create deposit"); + return qs; + } + if (! balance_ok) + { + *mhd_ret + = TEH_RESPONSE_reply_coin_insufficient_funds ( + connection, + TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, + &coin->pci.coin_pub); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + /* 3) if present, persist contract */ + in_conflict = true; + qs = TEH_plugin->insert_contract (TEH_plugin->cls, + &pcc->purse_pub, + &pcc->contract_pub, + pcc->econtract_size, + pcc->econtract, + &in_conflict); if (qs < 0) { if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -215,27 +328,21 @@ create_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_STORE_FAILED, - "purse create"); + "purse create contract"); return qs; } if (in_conflict) { - TEH_plugin->rollback (TEH_plugin->cls); + /* FIXME-DESIGN: we have no proof that the + client is at fault here! + => need 2nd client signature over the encrypted + contract (and ECDHE public key)!!! */ *mhd_ret - = TEH_RESPONSE_reply_coin_insufficient_funds ( + = TEH_RESPONSE_reply_with_error ( connection, - TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_CONTRACT, - &coin->pci.coin_pub); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (! balance_ok) - { - TEH_plugin->rollback (TEH_plugin->cls); - *mhd_ret - = TEH_RESPONSE_reply_coin_insufficient_funds ( - connection, - TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, - &coin->pci.coin_pub); + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_CONTRACT_STORED, + NULL); return GNUNET_DB_STATUS_HARD_ERROR; } return qs; @@ -289,7 +396,7 @@ parse_coin (struct MHD_Connection *connection, } if (GNUNET_OK != - TALER_wallet_purse_deposit_verify ("http://FIXME-URL", + TALER_wallet_purse_deposit_verify (TEH_base_url, &pcc->purse_pub, &pcc->amount, &coin->coin_pub, @@ -301,7 +408,7 @@ parse_coin (struct MHD_Connection *connection, TALER_MHD_reply_with_error (connection, MHD_HTTP_UNAUTHORIZED, TALER_EC_EXCHANGE_PURSE_CREATE_COIN_SIGNATURE_INVALID, - NULL)) + TEH_base_url)) ? GNUNET_NO : GNUNET_SYSERR; } /* check denomination exists and is valid */ @@ -317,6 +424,18 @@ parse_coin (struct MHD_Connection *connection, 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 */ @@ -366,6 +485,20 @@ parse_coin (struct MHD_Connection *connection, } 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_subtact (&coin->amount_minus_fee, + &coin->amount, + &coin->deposit_fee)); /* check coin signature */ switch (dk->denom_pub.cipher) { @@ -391,6 +524,17 @@ parse_coin (struct MHD_Connection *connection, 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; @@ -425,10 +569,10 @@ parse_coin (struct MHD_Connection *connection, MHD_RESULT -TEH_handler_purses_create (struct MHD_Connection *connection, - const struct - TALER_PurseContractPublicKeyP *purse_pub, - const json_t *root) +TEH_handler_purses_create ( + struct MHD_Connection *connection, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const json_t *root) { struct PurseCreateContext pcc = { .purse_pub = purse_pub, @@ -461,6 +605,7 @@ TEH_handler_purses_create (struct MHD_Connection *connection, &pcc.purse_expiration), GNUNET_JSON_spec_end () }; + const struct TEH_GlobalFee *gf; { enum GNUNET_GenericReturnValue res; @@ -479,7 +624,9 @@ TEH_handler_purses_create (struct MHD_Connection *connection, return MHD_YES; /* failure */ } } - + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TEH_currency, + &pcc.deposit_total)); if (GNUNET_TIME_timestamp_cmp (pcc.purse_expiration, <, pcc.exchange_timestamp)) @@ -511,6 +658,17 @@ TEH_handler_purses_create (struct MHD_Connection *connection, TALER_EC_EXCHANGE_GENERIC_PARAMETER_MALFORMED, "deposits"); } + gf = TEH_keys_global_fee_by_time (TEH_keys_get_state (), + pcc.exchange_timestamp); + if (NULL == gf) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Cannot create purse: global fees not configured!\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING, + NULL); + } /* parse deposits */ pcc.coins = GNUNET_new_array (struct Coin, pcc.num_coins); @@ -531,8 +689,7 @@ TEH_handler_purses_create (struct MHD_Connection *connection, } } - // FIXME: get purse_fee! - if (0 < TALER_amount_cmp (&purse_fee, + if (0 < TALER_amount_cmp (&gf->fees.purse, &pcc.deposit_total)) { GNUNET_break_op (0); @@ -543,7 +700,6 @@ TEH_handler_purses_create (struct MHD_Connection *connection, TALER_EC_EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE, NULL); } - TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != @@ -560,7 +716,7 @@ TEH_handler_purses_create (struct MHD_Connection *connection, GNUNET_free (pcc.coins); return TALER_MHD_reply_with_error (connection, MHD_HTTP_UNAUTHORIZED, - TALER_EC_EXCHANGE_PURSE_CREATE_COIN_SIGNATURE_INVALID, + TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID, NULL); } @@ -568,6 +724,7 @@ TEH_handler_purses_create (struct MHD_Connection *connection, TEH_plugin->preflight (TEH_plugin->cls)) { GNUNET_break (0); + GNUNET_JSON_parse_free (spec); GNUNET_free (pcc.coins); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c index 5765181b2..d5ecd3386 100644 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ b/src/exchange/taler-exchange-httpd_withdraw.c @@ -256,7 +256,6 @@ withdraw_transaction (void *cls, } if (! below_limit) { - TEH_plugin->rollback (TEH_plugin->cls); *mhd_ret = TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_ACCEPTED, diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index ec26a6dd0..8f0f5e22c 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -12668,6 +12668,10 @@ postgres_insert_partner (void *cls, * @param pub_ckey ephemeral key for DH used to encrypt the contract * @param econtract_size number of bytes in @a econtract * @param econtract the encrypted contract + * @param[out] in_conflict set to true if @a econtract + * conflicts with an existing contract; + * in this case, the return value will be + * #GNUNET_DB_STATUS_SUCCESS_ONE despite the failure * @return transaction status code */ static enum GNUNET_DB_QueryStatus @@ -12675,7 +12679,8 @@ postgres_insert_contract (void *cls, const struct TALER_PurseContractPublicKeyP *purse_pub, const struct TALER_ContractDiffiePublicP *pub_ckey, size_t econtract_size, - const void *econtract) + const void *econtract, + bool *in_conflict) { GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; @@ -12715,6 +12720,10 @@ postgres_select_contract (void *cls, * @param age_limit age limit to enforce for payments into the purse * @param amount target amount (with fees) to be put into the purse * @param purse_sig signature with @a purse_pub's private key affirming the above + * @param[out] in_conflict set to true if the meta data + * conflicts with an existing purse; + * in this case, the return value will be + * #GNUNET_DB_STATUS_SUCCESS_ONE despite the failure * @return transaction status code */ static enum GNUNET_DB_QueryStatus @@ -12726,7 +12735,8 @@ postgres_insert_purse_request ( const struct TALER_PrivateContractHashP *h_contract_terms, uint32_t age_limit, const struct TALER_Amount *amount, - const struct TALER_PurseContractSignatureP *purse_sig) + const struct TALER_PurseContractSignatureP *purse_sig, + bool *in_conflict) { GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; @@ -12806,6 +12816,11 @@ postgres_select_purse_by_merge_pub ( * @param coin_pub coin to deposit (debit) * @param amount fraction of the coin's value to deposit * @param coin_sig signature affirming the operation + * @param amount_minus_fee amount to add to the purse + * @param[out] balance_ok set to false if the coin's + * remaining balance is below @a amount; + * in this case, the return value will be + * #GNUNET_DB_STATUS_SUCCESS_ONE despite the failure * @return transaction status code */ static enum GNUNET_DB_QueryStatus @@ -12814,7 +12829,9 @@ postgres_do_purse_deposit ( const struct TALER_PurseContractPublicKeyP *purse_pub, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_Amount *amount, - const struct TALER_CoinSpendSignatureP *coin_sig) + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct TALER_Amount *amount_minus_fee, + bool *balance_ok) { GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index bafcca08e..6b554dfb1 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -3811,6 +3811,33 @@ TALER_exchange_online_reserve_closed_verify ( const struct TALER_ExchangeSignatureP *sig); +enum TALER_ErrorCode +TALER_exchange_online_purse_created_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp exchange_time, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_Amount *amount_without_fee, + const struct TALER_Amount *total_deposited, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePublicKeyP *merge_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig); + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_purse_created_verify ( + struct GNUNET_TIME_Timestamp exchange_time, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_Amount *amount_without_fee, + const struct TALER_Amount *total_deposited, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePublicKeyP *merge_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig); + + /* ********************* offline signing ************************** */ diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 6b86dc3ce..488ffd519 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -4389,6 +4389,10 @@ struct TALER_EXCHANGEDB_Plugin * @param pub_ckey ephemeral key for DH used to encrypt the contract * @param econtract_size number of bytes in @a econtract * @param econtract the encrypted contract + * @param[out] in_conflict set to true if @a econtract + * conflicts with an existing contract; + * in this case, the return value will be + * #GNUNET_DB_STATUS_SUCCESS_ONE despite the failure * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -4396,7 +4400,8 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_PurseContractPublicKeyP *purse_pub, const struct TALER_ContractDiffiePublicP *pub_ckey, size_t econtract_size, - const void *econtract); + const void *econtract, + bool *in_conflict); /** @@ -4428,6 +4433,10 @@ struct TALER_EXCHANGEDB_Plugin * @param age_limit age limit to enforce for payments into the purse * @param amount target amount (with fees) to be put into the purse * @param purse_sig signature with @a purse_pub's private key affirming the above + * @param[out] in_conflict set to true if the meta data + * conflicts with an existing purse; + * in this case, the return value will be + * #GNUNET_DB_STATUS_SUCCESS_ONE despite the failure * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -4439,7 +4448,8 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_PrivateContractHashP *h_contract_terms, uint32_t age_limit, const struct TALER_Amount *amount, - const struct TALER_PurseContractSignatureP *purse_sig); + const struct TALER_PurseContractSignatureP *purse_sig, + bool *in_conflict); /** @@ -4507,6 +4517,11 @@ struct TALER_EXCHANGEDB_Plugin * @param coin_pub coin to deposit (debit) * @param amount fraction of the coin's value to deposit * @param coin_sig signature affirming the operation + * @param amount_minus_fee amount to add to the purse + * @param[out] balance_ok set to false if the coin's + * remaining balance is below @a amount; + * in this case, the return value will be + * #GNUNET_DB_STATUS_SUCCESS_ONE despite the failure * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -4515,7 +4530,9 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_PurseContractPublicKeyP *purse_pub, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_Amount *amount, - const struct TALER_CoinSpendSignatureP *coin_sig); + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct TALER_Amount *amount_minus_fee, + bool *balance_ok); /** diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index 52c294cef..70c917b67 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -176,6 +176,11 @@ */ #define TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS 1044 +/** + * Signature by which the exchange affirms that a purse + * was created with a certain amount deposited into it. + */ +#define TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION 1045 /**********************/ /* Auditor signatures */ diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c index 4bfa822f1..eeec0d613 100644 --- a/src/util/exchange_signatures.c +++ b/src/util/exchange_signatures.c @@ -1261,4 +1261,117 @@ TALER_exchange_online_reserve_closed_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it has + * received funds deposited into a purse. + */ +struct TALER_PurseCreateDepositConfirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When did the exchange receive the deposits. + */ + struct GNUNET_TIME_TimestampNBO exchange_time; + + /** + * When will the purse expire? + */ + struct GNUNET_TIME_TimestampNBO purse_expiration; + + /** + * How much should the purse ultimately contain. + */ + struct TALER_AmountNBO amount_without_fee; + + /** + * How much was deposited so far. + */ + struct TALER_AmountNBO total_deposited; + + /** + * Public key of the purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Public key of the merge capability. + */ + struct TALER_PurseMergePublicKeyP merge_pub; + + /** + * Hash of the contract of the purse. + */ + struct TALER_PrivateContractHashP h_contract_terms; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_purse_created_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp exchange_time, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_Amount *amount_without_fee, + const struct TALER_Amount *total_deposited, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePublicKeyP *merge_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_PurseCreateDepositConfirmationPS dc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION), + .purpose.size = htonl (sizeof (dc)), + .h_contract_terms = *h_contract_terms, + .purse_pub = *purse_pub, + .merge_pub = *merge_pub, + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time) + }; + + return scb (&dc.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_purse_created_verify ( + struct GNUNET_TIME_Timestamp exchange_time, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_Amount *amount_without_fee, + const struct TALER_Amount *total_deposited, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePublicKeyP *merge_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_PurseCreateDepositConfirmationPS dc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION), + .purpose.size = htonl (sizeof (dc)), + .h_contract_terms = *h_contract_terms, + .purse_pub = *purse_pub, + .merge_pub = *merge_pub, + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time) + }; + + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION, + &dc, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + /* end of exchange_signatures.c */