diff options
author | Christian Grothoff <christian@grothoff.org> | 2022-06-05 14:07:23 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2022-06-05 14:07:23 +0200 |
commit | b9963f75255e416ca79b2f5c3081bde4ba78fab0 (patch) | |
tree | 9575e9558b6f8960798c09dec681dc277f96babc /src/lib | |
parent | 6c81796d6f39e932d58b4fc1729b472d0e46e3d1 (diff) | |
download | exchange-b9963f75255e416ca79b2f5c3081bde4ba78fab0.tar.xz |
complete P2P/W2W conflict handling, deduplicate code across handlers
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/exchange_api_common.c | 368 | ||||
-rw-r--r-- | src/lib/exchange_api_common.h | 123 | ||||
-rw-r--r-- | src/lib/exchange_api_deposit.c | 101 | ||||
-rw-r--r-- | src/lib/exchange_api_melt.c | 187 | ||||
-rw-r--r-- | src/lib/exchange_api_purse_create_with_deposit.c | 203 | ||||
-rw-r--r-- | src/lib/exchange_api_purse_deposit.c | 172 | ||||
-rw-r--r-- | src/lib/exchange_api_recoup.c | 106 | ||||
-rw-r--r-- | src/lib/exchange_api_recoup_refresh.c | 117 | ||||
-rw-r--r-- | src/lib/exchange_api_refund.c | 32 |
9 files changed, 807 insertions, 602 deletions
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 567239eed..59f5bab8a 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -22,6 +22,7 @@ #include "platform.h" #include "taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_common.h" #include "exchange_api_handle.h" #include "taler_signatures.h" @@ -685,11 +686,6 @@ struct CoinHistoryParseContext const struct TALER_CoinSpendPublicKeyP *coin_pub; /** - * Hash of @e dk, set from parsing. - */ - struct TALER_DenominationHashP *h_denom_pub; - - /** * Where to sum up total refunds. */ struct TALER_Amount rtotal; @@ -749,8 +745,6 @@ help_deposit (struct CoinHistoryParseContext *pc, &h_contract_terms), GNUNET_JSON_spec_fixed_auto ("h_wire", &h_wire), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - pc->h_denom_pub), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("h_age_commitment", &hac), @@ -784,7 +778,7 @@ help_deposit (struct CoinHistoryParseContext *pc, &h_contract_terms, no_hac ? NULL : &hac, NULL /* h_extensions! */, - pc->h_denom_pub, + &pc->dk->h_key, wallet_timestamp, &merchant_pub, refund_deadline, @@ -836,8 +830,6 @@ help_melt (struct CoinHistoryParseContext *pc, &sig), GNUNET_JSON_spec_fixed_auto ("rc", &rc), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - pc->h_denom_pub), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("h_age_commitment", &h_age_commitment), @@ -876,7 +868,7 @@ help_melt (struct CoinHistoryParseContext *pc, amount, &melt_fee, &rc, - pc->h_denom_pub, + &pc->dk->h_key, no_hac ? NULL : &h_age_commitment, @@ -1008,8 +1000,6 @@ help_recoup (struct CoinHistoryParseContext *pc, &coin_sig), GNUNET_JSON_spec_fixed_auto ("coin_blind", &coin_bks), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - pc->h_denom_pub), GNUNET_JSON_spec_timestamp ("timestamp", ×tamp), GNUNET_JSON_spec_end () @@ -1036,7 +1026,7 @@ help_recoup (struct CoinHistoryParseContext *pc, return GNUNET_SYSERR; } if (GNUNET_OK != - TALER_wallet_recoup_verify (pc->h_denom_pub, + TALER_wallet_recoup_verify (&pc->dk->h_key, &coin_bks, pc->coin_pub, &coin_sig)) @@ -1081,8 +1071,6 @@ help_recoup_refresh (struct CoinHistoryParseContext *pc, &old_coin_pub), GNUNET_JSON_spec_fixed_auto ("coin_blind", &coin_bks), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - pc->h_denom_pub), GNUNET_JSON_spec_timestamp ("timestamp", ×tamp), GNUNET_JSON_spec_end () @@ -1109,7 +1097,7 @@ help_recoup_refresh (struct CoinHistoryParseContext *pc, return GNUNET_SYSERR; } if (GNUNET_OK != - TALER_wallet_recoup_verify (pc->h_denom_pub, + TALER_wallet_recoup_verify (&pc->dk->h_key, &coin_bks, pc->coin_pub, &coin_sig)) @@ -1250,12 +1238,11 @@ help_purse_deposit (struct CoinHistoryParseContext *pc, enum GNUNET_GenericReturnValue TALER_EXCHANGE_verify_coin_history ( const struct TALER_EXCHANGE_DenomPublicKey *dk, - const char *currency, const struct TALER_CoinSpendPublicKeyP *coin_pub, json_t *history, - struct TALER_DenominationHashP *h_denom_pub, struct TALER_Amount *total) { + const char *currency = dk->value.currency; const struct { const char *type; @@ -1273,8 +1260,7 @@ TALER_EXCHANGE_verify_coin_history ( struct CoinHistoryParseContext pc = { .dk = dk, .coin_pub = coin_pub, - .total = total, - .h_denom_pub = h_denom_pub + .total = total }; size_t len; @@ -1528,11 +1514,10 @@ TALER_EXCHANGE_check_purse_merge_conflict_ ( { struct TALER_PurseMergeSignatureP merge_sig; struct GNUNET_TIME_Timestamp merge_timestamp; - const char *partner_url = exchange_url; + const char *partner_url = NULL; struct TALER_ReservePublicKeyP reserve_pub; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( - // FIXME: partner_url or partner_base_url? GNUNET_JSON_spec_string ("partner_url", &partner_url), NULL), @@ -1554,6 +1539,8 @@ TALER_EXCHANGE_check_purse_merge_conflict_ ( GNUNET_break_op (0); return GNUNET_SYSERR; } + if (NULL == partner_url) + partner_url = exchange_url; payto_uri = make_payto (partner_url, &reserve_pub); if (GNUNET_OK != @@ -1581,18 +1568,55 @@ TALER_EXCHANGE_check_purse_merge_conflict_ ( } -/** - * Check proof of a contract conflict. - * - * DESIGN-FIXME: this 'proof' doesn't really proof a conflict! - * - * @param ccontract_sig conflicting signature (must - * not match the signature from the proof) - * @param purse_pub public key of the purse - * @param exchange_url the base URL of this exchange - * @param proof the proof to check - * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts with @a purse_sig - */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_purse_coin_conflict_ ( + const struct TALER_PurseContractPublicKeyP *purse_pub, + const char *exchange_url, + const json_t *proof, + struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_CoinSpendSignatureP *coin_sig) +{ + const char *partner_url = NULL; + struct TALER_Amount amount; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + coin_sig), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + coin_pub), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("partner_url", + &partner_url), + NULL), + TALER_JSON_spec_amount_any ("amount", + &amount), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (proof, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (NULL == partner_url) + partner_url = exchange_url; + if (GNUNET_OK != + TALER_wallet_purse_deposit_verify ( + partner_url, + purse_pub, + &amount, + coin_pub, + coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + enum GNUNET_GenericReturnValue TALER_EXCHANGE_check_purse_econtract_conflict_ ( const struct TALER_PurseContractSignatureP *ccontract_sig, @@ -1642,4 +1666,278 @@ TALER_EXCHANGE_check_purse_econtract_conflict_ ( } +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_amount_conflict_ ( + const struct TALER_EXCHANGE_Keys *keys, + const json_t *proof, + struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_Amount *remaining) +{ + json_t *history; + struct TALER_Amount total; + struct TALER_DenominationHashP h_denom_pub; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + coin_pub), + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &h_denom_pub), + GNUNET_JSON_spec_json ("history", + &history), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (proof, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + dki = TALER_EXCHANGE_get_denomination_key_by_hash ( + keys, + &h_denom_pub); + if (NULL == dki) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (dki, + coin_pub, + history, + &total)) + { + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + json_decref (history); + if (0 > + TALER_amount_subtract (remaining, + &dki->value, + &total)) + { + /* Strange 'proof': coin was double-spent + before our transaction?! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify that @a coin_sig does NOT appear in + * the history of @a proof and thus whatever transaction + * is authorized by @a coin_sig is a conflict with + * @a proof. + * + * @param proof a proof to check + * @param coin_sig signature that must not be in @a proof + * @return #GNUNET_OK if @a coin_sig is not in @a proof + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_signature_conflict_ ( + const json_t *proof, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + json_t *history; + size_t off; + json_t *entry; + + history = json_object_get (proof, + "history"); + if (NULL == history) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_array_foreach (history, off, entry) + { + struct TALER_CoinSpendSignatureP cs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &cs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (entry, + spec, + NULL, NULL)) + continue; /* entry without coin signature */ + if (0 == + GNUNET_memcmp (&cs, + coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_denomination_conflict_ ( + const json_t *proof, + const struct TALER_DenominationHashP *ch_denom_pub) +{ + struct TALER_DenominationHashP h_denom_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &h_denom_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (proof, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 == + GNUNET_memcmp (ch_denom_pub, + &h_denom_pub)) + { + GNUNET_break_op (0); + return GNUNET_OK; + } + /* indeed, proof with different denomination key provided */ + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_conflict_ ( + const struct TALER_EXCHANGE_Keys *keys, + const json_t *proof, + const struct TALER_EXCHANGE_DenomPublicKey *dk, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct TALER_Amount *required) +{ + enum TALER_ErrorCode ec; + + ec = TALER_JSON_get_error_code (proof); + switch (ec) + { + case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: + { + struct TALER_Amount left; + struct TALER_CoinSpendPublicKeyP pcoin_pub; + + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_amount_conflict_ ( + keys, + proof, + &pcoin_pub, + &left)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 != + GNUNET_memcmp (&pcoin_pub, + coin_pub)) + { + /* conflict is for a different coin! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (-1 != + TALER_amount_cmp (&left, + required)) + { + /* Balance was sufficient after all; recoup MAY have still been possible */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_signature_conflict_ ( + proof, + coin_sig)) + { + /* Not a conflicting transaction: ours is included! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + break; + } + case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: + { + struct TALER_Amount left; + struct TALER_CoinSpendPublicKeyP pcoin_pub; + + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_amount_conflict_ ( + keys, + proof, + &pcoin_pub, + &left)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 != + GNUNET_memcmp (&pcoin_pub, + coin_pub)) + { + /* conflict is for a different coin! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_denomination_conflict_ ( + proof, + &dk->h_key)) + { + /* Eh, same denomination, hence no conflict */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + break; + } + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_min_denomination_ ( + const struct TALER_EXCHANGE_Keys *keys, + struct TALER_Amount *min) +{ + bool have_min = false; + for (unsigned int i = 0; i<keys->num_denom_keys; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dk = &keys->denom_keys[i]; + + if (! have_min) + { + *min = dk->value; + have_min = true; + continue; + } + if (1 != TALER_amount_cmp (min, + &dk->value)) + continue; + *min = dk->value; + } + if (! have_min) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + /* end of exchange_api_common.c */ diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h index 9c6d45029..9cbdf547f 100644 --- a/src/lib/exchange_api_common.h +++ b/src/lib/exchange_api_common.h @@ -23,6 +23,7 @@ #define EXCHANGE_API_COMMON_H #include "taler_json_lib.h" +#include "taler_exchange_service.h" /** @@ -33,7 +34,7 @@ * @param purse_pub the public key (must match * the signature from the proof) * @param proof the proof to check - * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts with @a purse_sig + * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts with @a cpurse_sig */ enum GNUNET_GenericReturnValue TALER_EXCHANGE_check_purse_create_conflict_ ( @@ -51,7 +52,7 @@ TALER_EXCHANGE_check_purse_create_conflict_ ( * the signature from the proof) * @param exchange_url the base URL of this exchange * @param proof the proof to check - * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts with @a purse_sig + * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and @a merge_pub and conflicts with @a cmerge_sig */ enum GNUNET_GenericReturnValue TALER_EXCHANGE_check_purse_merge_conflict_ ( @@ -63,16 +64,38 @@ TALER_EXCHANGE_check_purse_merge_conflict_ ( /** - * Check proof of a contract conflict. + * Check @a proof that claims this coin was spend + * differently on the same purse already. Note that + * the caller must still check that @a coin_pub is + * in the list of coins that were used, and that + * @a coin_sig is different from the signature the + * caller used. * - * DESIGN-FIXME: this 'proof' doesn't really proof a conflict! + * @param purse_pub the public key of the purse + * @param exchange_url base URL of our exchange + * @param proof the proof to check + * @param[out] coin_pub set to the conflicting coin + * @param[out] coin_sig set to the conflicting signature + * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and showing that @a coin_pub was spent using @a coin_sig. + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_purse_coin_conflict_ ( + const struct TALER_PurseContractPublicKeyP *purse_pub, + const char *exchange_url, + const json_t *proof, + struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_CoinSpendSignatureP *coin_sig); + + +/** + * Check proof of a contract conflict. * * @param ccontract_sig conflicting signature (must * not match the signature from the proof) * @param purse_pub public key of the purse * @param exchange_url the base URL of this exchange * @param proof the proof to check - * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts with @a purse_sig + * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts with @a ccontract_sig */ enum GNUNET_GenericReturnValue TALER_EXCHANGE_check_purse_econtract_conflict_ ( @@ -81,4 +104,94 @@ TALER_EXCHANGE_check_purse_econtract_conflict_ ( const json_t *proof); +/** + * Check proof of a coin spend value conflict. + * + * @param keys exchange /keys structure + * @param proof the proof to check + * @param[out] coin_pub set to the public key of the + * coin that is claimed to have an insufficient + * balance + * @param[out] remaining set to the remaining balance + * of the coin as provided by the proof + * @return #GNUNET_OK if the @a proof is OK for @a purse_pub demonstrating that @a coin_pub has only @a remaining balance. + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_amount_conflict_ ( + const struct TALER_EXCHANGE_Keys *keys, + const json_t *proof, + struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_Amount *remaining); + + +/** + * Verify that @a proof contains a coin history + * that demonstrates that @a coin_pub was previously + * used with a denomination key that is different + * from @a ch_denom_pub. Note that the coin history + * MUST have been checked before using + * #TALER_EXCHANGE_check_coin_amount_conflict_(). + * + * @param proof a proof to check + * @param ch_denom_pub hash of the conflicting denomination + * @return #GNUNET_OK if @a ch_denom_pub differs from the + * denomination hash given by the history of the coin + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_denomination_conflict_ ( + const json_t *proof, + const struct TALER_DenominationHashP *ch_denom_pub); + + +/** + * Verify that @a coin_sig does NOT appear in + * the history of @a proof and thus whatever transaction + * is authorized by @a coin_sig is a conflict with + * @a proof. + * + * @param proof a proof to check + * @param coin_sig signature that must not be in @a proof + * @return #GNUNET_OK if @a coin_sig is not in @a proof + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_signature_conflict_ ( + const json_t *proof, + const struct TALER_CoinSpendSignatureP *coin_sig); + + +/** + * Check that the provided @a proof indeeds indicates + * a conflict for @a coin_pub. + * + * @param keys exchange keys + * @param proof provided conflict proof + * @param dk denomination of @a coin_pub that the client + * used + * @param coin_pub public key of the coin + * @param coin_sig signature over operation that conflicted + * @param required balance required on the coin for the operation + * @return #GNUNET_OK if @a proof holds + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_conflict_ ( + const struct TALER_EXCHANGE_Keys *keys, + const json_t *proof, + const struct TALER_EXCHANGE_DenomPublicKey *dk, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct TALER_Amount *required); + + +/** + * Find the smallest denomination amount in @e keys. + * + * @param keys keys to search + * @param[out] min set to the smallest amount + * @return #GNUNET_SYSERR if there are no denominations in @a keys + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_min_denomination_ ( + const struct TALER_EXCHANGE_Keys *keys, + struct TALER_Amount *min); + #endif diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index 67f595bfb..58b1c3a4a 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -29,6 +29,7 @@ #include "taler_json_lib.h" #include "taler_auditor_service.h" #include "taler_exchange_service.h" +#include "exchange_api_common.h" #include "exchange_api_handle.h" #include "taler_signatures.h" #include "exchange_api_curl_defaults.h" @@ -129,6 +130,11 @@ struct TALER_EXCHANGE_DepositHandle struct TALER_CoinSpendPublicKeyP coin_pub; /** + * Our signature for the deposit operation. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** * The Merchant's public key. Allows the merchant to later refund * the transaction or to inquire about the wire transfer identifier. */ @@ -227,77 +233,6 @@ auditor_cb (void *cls, /** - * Verify that the signatures on the "403 FORBIDDEN" response from the - * exchange demonstrating customer double-spending are valid. - * - * @param dh deposit handle - * @param json json reply with the signature(s) and transaction history - * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not - */ -static enum GNUNET_GenericReturnValue -verify_deposit_signature_conflict ( - const struct TALER_EXCHANGE_DepositHandle *dh, - const json_t *json) -{ - json_t *history; - struct TALER_Amount total; - enum TALER_ErrorCode ec; - struct TALER_DenominationHashP h_denom_pub; - - memset (&h_denom_pub, - 0, - sizeof (h_denom_pub)); - history = json_object_get (json, - "history"); - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (&dh->dki, - dh->dki.value.currency, - &dh->coin_pub, - history, - &h_denom_pub, - &total)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - ec = TALER_JSON_get_error_code (json); - switch (ec) - { - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - if (0 > - TALER_amount_add (&total, - &total, - &dh->amount_with_fee)) - { - /* clearly not OK if our transaction would have caused - the overflow... */ - return GNUNET_OK; - } - - if (0 >= TALER_amount_cmp (&total, - &dh->dki.value)) - { - /* transaction should have still fit */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* everything OK, proof of double-spending was provided */ - return GNUNET_OK; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - if (0 != GNUNET_memcmp (&dh->dki.h_key, - &h_denom_pub)) - return GNUNET_OK; /* indeed, proof with different denomination key provided */ - /* invalid proof provided */ - return GNUNET_SYSERR; - default: - /* unexpected error code */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } -} - - -/** * Function called when we're done processing the * HTTP /deposit request. * @@ -316,8 +251,10 @@ handle_deposit_finished (void *cls, .hr.reply = j, .hr.http_status = (unsigned int) response_code }; + const struct TALER_EXCHANGE_Keys *keys; dh->job = NULL; + keys = TALER_EXCHANGE_get_keys (dh->exchange); switch (response_code) { case 0: @@ -409,19 +346,21 @@ handle_deposit_finished (void *cls, happen, we should pass the JSON reply to the application */ break; case MHD_HTTP_CONFLICT: - /* Double spending; check signatures on transaction history */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); if (GNUNET_OK != - verify_deposit_signature_conflict (dh, - j)) + TALER_EXCHANGE_check_coin_conflict_ ( + keys, + j, + &dh->dki, + &dh->coin_pub, + &dh->coin_sig, + &dh->amount_with_fee)) { GNUNET_break_op (0); dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; - } - else - { - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; } break; case MHD_HTTP_GONE: @@ -692,6 +631,8 @@ TALER_EXCHANGE_deposit ( dh->exchange = exchange; dh->cb = cb; dh->cb_cls = cb_cls; + dh->coin_sig = *coin_sig; + dh->coin_pub = *coin_pub; dh->url = TEAH_path_to_url (exchange, arg_str); if (NULL == dh->url) diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c index 80c759704..ff720d2ff 100644 --- a/src/lib/exchange_api_melt.c +++ b/src/lib/exchange_api_melt.c @@ -27,6 +27,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_json_lib.h" #include "taler_exchange_service.h" +#include "exchange_api_common.h" #include "exchange_api_handle.h" #include "taler_signatures.h" #include "exchange_api_curl_defaults.h" @@ -102,6 +103,11 @@ struct TALER_EXCHANGE_MeltHandle struct TALER_CoinSpendPublicKeyP coin_pub; /** + * Signature affirming the melt. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** * @brief Public information about the coin's denomination key */ const struct TALER_EXCHANGE_DenomPublicKey *dki; @@ -184,143 +190,6 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh, /** - * Verify that the signatures on the "409 CONFLICT" response from the - * exchange demonstrating customer denomination key differences - * resulting from coin private key reuse are valid. - * - * @param mh melt handle - * @param json json reply with the signature(s) and transaction history - * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not - */ -static enum GNUNET_GenericReturnValue -verify_melt_signature_denom_conflict (struct TALER_EXCHANGE_MeltHandle *mh, - const json_t *json) - -{ - json_t *history; - struct TALER_Amount total; - struct TALER_DenominationHashP h_denom_pub; - - memset (&h_denom_pub, - 0, - sizeof (h_denom_pub)); - history = json_object_get (json, - "history"); - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (mh->dki, - mh->dki->value.currency, - &mh->coin_pub, - history, - &h_denom_pub, - &total)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 != GNUNET_memcmp (&mh->dki->h_key, - &h_denom_pub)) - return GNUNET_OK; /* indeed, proof with different denomination key provided */ - /* invalid proof provided */ - return GNUNET_SYSERR; -} - - -/** - * Verify that the signatures on the "409 CONFLICT" response from the - * exchange demonstrating customer double-spending are valid. - * - * @param mh melt handle - * @param json json reply with the signature(s) and transaction history - * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not - */ -static enum GNUNET_GenericReturnValue -verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh, - const json_t *json) -{ - json_t *history; - struct TALER_Amount total; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("history", - &history), - GNUNET_JSON_spec_end () - }; - const struct MeltedCoin *mc; - enum TALER_ErrorCode ec; - struct TALER_DenominationHashP h_denom_pub; - - /* parse JSON reply */ - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* Find out which coin was deemed problematic by the exchange */ - mc = &mh->md.melted_coin; - /* verify coin history */ - memset (&h_denom_pub, - 0, - sizeof (h_denom_pub)); - history = json_object_get (json, - "history"); - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (mh->dki, - mc->original_value.currency, - &mh->coin_pub, - history, - &h_denom_pub, - &total)) - { - GNUNET_break_op (0); - json_decref (history); - return GNUNET_SYSERR; - } - json_decref (history); - - ec = TALER_JSON_get_error_code (json); - switch (ec) - { - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - /* check if melt operation was really too expensive given history */ - if (0 > - TALER_amount_add (&total, - &total, - &mc->melt_amount_with_fee)) - { - /* clearly not OK if our transaction would have caused - the overflow... */ - return GNUNET_OK; - } - - if (0 >= TALER_amount_cmp (&total, - &mc->original_value)) - { - /* transaction should have still fit */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - - /* everything OK, valid proof of double-spending was provided */ - return GNUNET_OK; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - if (0 != GNUNET_memcmp (&mh->dki->h_key, - &h_denom_pub)) - return GNUNET_OK; /* indeed, proof with different denomination key provided */ - /* invalid proof provided */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - default: - /* unexpected error code */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } -} - - -/** * Function called when we're done processing the * HTTP /coins/$COIN_PUB/melt request. * @@ -339,8 +208,10 @@ handle_melt_finished (void *cls, .hr.reply = j, .hr.http_status = (unsigned int) response_code }; + const struct TALER_EXCHANGE_Keys *keys; mh->job = NULL; + keys = TALER_EXCHANGE_get_keys (mh->exchange); switch (response_code) { case 0: @@ -372,36 +243,19 @@ handle_melt_finished (void *cls, break; case MHD_HTTP_CONFLICT: mr.hr.ec = TALER_JSON_get_error_code (j); - switch (mr.hr.ec) + mr.hr.hint = TALER_JSON_get_error_hint (j); + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_conflict_ ( + keys, + j, + mh->dki, + &mh->coin_pub, + &mh->coin_sig, + &mh->md.melted_coin.melt_amount_with_fee)) { - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - /* Double spending; check signatures on transaction history */ - if (GNUNET_OK != - verify_melt_signature_spend_conflict (mh, - j)) - { - GNUNET_break_op (0); - mr.hr.http_status = 0; - mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE; - mr.hr.hint = TALER_JSON_get_error_hint (j); - } - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - if (GNUNET_OK != - verify_melt_signature_denom_conflict (mh, - j)) - { - GNUNET_break_op (0); - mr.hr.http_status = 0; - mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE; - mr.hr.hint = TALER_JSON_get_error_hint (j); - } - break; - default: GNUNET_break_op (0); mr.hr.http_status = 0; - mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE; - mr.hr.hint = TALER_JSON_get_error_hint (j); + mr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } break; @@ -456,7 +310,6 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh) json_t *melt_obj; CURL *eh; struct GNUNET_CURL_Context *ctx; - struct TALER_CoinSpendSignatureP confirm_sig; char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; struct TALER_DenominationHashP h_denom_pub; struct TALER_ExchangeWithdrawValues alg_values[mh->rd->fresh_pks_len]; @@ -480,7 +333,7 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh) &h_denom_pub, mh->md.melted_coin.h_age_commitment, &mh->md.melted_coin.coin_priv, - &confirm_sig); + &mh->coin_sig); GNUNET_CRYPTO_eddsa_key_get_public (&mh->md.melted_coin.coin_priv.eddsa_priv, &mh->coin_pub.eddsa_pub); melt_obj = GNUNET_JSON_PACK ( @@ -489,7 +342,7 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh) TALER_JSON_pack_denom_sig ("denom_sig", &mh->md.melted_coin.sig), GNUNET_JSON_pack_data_auto ("confirm_sig", - &confirm_sig), + &mh->coin_sig), TALER_JSON_pack_amount ("value_with_fee", &mh->md.melted_coin.melt_amount_with_fee), GNUNET_JSON_pack_data_auto ("rc", diff --git a/src/lib/exchange_api_purse_create_with_deposit.c b/src/lib/exchange_api_purse_create_with_deposit.c index f21b7d312..f2d80b942 100644 --- a/src/lib/exchange_api_purse_create_with_deposit.c +++ b/src/lib/exchange_api_purse_create_with_deposit.c @@ -35,6 +35,33 @@ /** + * Information we track per deposited coin. + */ +struct Deposit +{ + /** + * Coin's public key. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature made with the coin. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Coin's denomination. + */ + struct TALER_DenominationHashP h_denom_pub; + + /** + * How much did we say the coin contributed. + */ + struct TALER_Amount contribution; +}; + + +/** * @brief A purse create with deposit handle */ struct TALER_EXCHANGE_PurseCreateDepositHandle @@ -106,6 +133,16 @@ struct TALER_EXCHANGE_PurseCreateDepositHandle */ struct GNUNET_TIME_Timestamp purse_expiration; + /** + * Array of @e num_deposit deposits. + */ + struct Deposit *deposits; + + /** + * How many deposits did we make? + */ + unsigned int num_deposits; + }; @@ -128,8 +165,10 @@ handle_purse_create_deposit_finished (void *cls, .hr.reply = j, .hr.http_status = (unsigned int) response_code }; + const struct TALER_EXCHANGE_Keys *keys; pch->job = NULL; + keys = TALER_EXCHANGE_get_keys (pch->exchange); switch (response_code) { case 0: @@ -232,43 +271,151 @@ handle_purse_create_deposit_finished (void *cls, break; case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: { -#if FIXME - struct TALER_Amount total; - struct TALER_DenominationHashP h_denom_pub; - const struct TALER_EXCHANGE_DenomPublicKey *dk = NULL; + struct TALER_Amount left; + struct TALER_CoinSpendPublicKeyP pcoin_pub; + bool found = false; - // FIXME: parse coin_pub - // FIXME: lookup dk for that coin from our deposits array! - - if (NULL == dk) + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_amount_conflict_ ( + keys, + j, + &pcoin_pub, + &left)) { - /* not one of our coins */ GNUNET_break_op (0); dr.hr.http_status = 0; dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } + for (unsigned int i = 0; i<pch->num_deposits; i++) + { + struct Deposit *deposit = &pch->deposits[i]; + + if (0 != GNUNET_memcmp (&pcoin_pub, + &deposit->coin_pub)) + continue; + if (-1 != + TALER_amount_cmp (&left, + &deposit->contribution)) + { + /* Balance was sufficient after all; recoup MAY have still been possible */ + GNUNET_break_op (0); + continue; + } + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_signature_conflict_ ( + j, + &deposit->coin_sig)) + { + GNUNET_break_op (0); + continue; + } + found = true; + break; + } + if (! found) + { + /* conflict is for a different coin! */ + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + break; + } + case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: + { + struct TALER_Amount left; + struct TALER_CoinSpendPublicKeyP pcoin_pub; + bool found = false; + if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (dk, - pch->exchange->currency, - &coin_pub, - history, - &h_denom_pub, - &total)) + TALER_EXCHANGE_check_coin_amount_conflict_ ( + keys, + j, + &pcoin_pub, + &left)) { GNUNET_break_op (0); dr.hr.http_status = 0; dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - // FIXME: check total is too high for the request... -#endif + for (unsigned int i = 0; i<pch->num_deposits; i++) + { + struct Deposit *deposit = &pch->deposits[i]; + + if (0 != + GNUNET_memcmp (&pcoin_pub, + &deposit->coin_pub)) + continue; + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_denomination_conflict_ ( + j, + &deposit->h_denom_pub)) + { + /* Eh, same denomination, hence no conflict */ + GNUNET_break_op (0); + continue; + } + found = true; + } + if (! found) + { + /* conflict is for a different coin! */ + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + /* meta data conflict is real! */ break; } case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA: { - // FIXME! - break; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_CoinSpendSignatureP coin_sig; + bool found = false; + + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_coin_conflict_ ( + &pch->purse_pub, + pch->exchange->url, + j, + &coin_pub, + &coin_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + for (unsigned int i = 0; i<pch->num_deposits; i++) + { + struct Deposit *deposit = &pch->deposits[i]; + + if (0 != + GNUNET_memcmp (&coin_pub, + &deposit->coin_pub)) + continue; + if (0 == + GNUNET_memcmp (&coin_sig, + &deposit->coin_sig)) + { + GNUNET_break_op (0); + continue; + } + found = true; + break; + } + if (! found) + { + /* conflict is for a different coin! */ + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } } case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA: if (GNUNET_OK != @@ -408,6 +555,9 @@ TALER_EXCHANGE_purse_create_with_deposit ( GNUNET_free (pch); return NULL; } + pch->num_deposits = num_deposits; + pch->deposits = GNUNET_new_array (num_deposits, + struct Deposit); deposit_arr = json_array (); GNUNET_assert (NULL != deposit_arr); url = TEAH_path_to_url (exchange, @@ -418,9 +568,8 @@ TALER_EXCHANGE_purse_create_with_deposit ( for (unsigned int i = 0; i<num_deposits; i++) { const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i]; + struct Deposit *d = &pch->deposits[i]; json_t *jdeposit; - struct TALER_CoinSpendSignatureP coin_sig; - struct TALER_CoinSpendPublicKeyP coin_pub; #if FIXME_OEC struct TALER_AgeCommitmentHash agh; struct TALER_AgeCommitmentHash *aghp = NULL; @@ -441,14 +590,16 @@ TALER_EXCHANGE_purse_create_with_deposit ( return NULL; } #endif + d->contribution = deposit->amount; + d->h_denom_pub = deposit->h_denom_pub; GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); + &d->coin_pub.eddsa_pub); TALER_wallet_purse_deposit_sign ( url, &pch->purse_pub, &deposit->amount, &deposit->coin_priv, - &coin_sig); + &d->coin_sig); jdeposit = GNUNET_JSON_PACK ( #if FIXME_OEC GNUNET_JSON_pack_allow_null ( @@ -465,9 +616,9 @@ TALER_EXCHANGE_purse_create_with_deposit ( TALER_JSON_pack_denom_sig ("ub_sig", &deposit->denom_sig), GNUNET_JSON_pack_data_auto ("coin_sig", - &coin_sig), + &d->coin_sig), GNUNET_JSON_pack_data_auto ("coin_pub", - &coin_pub)); + &d->coin_pub)); GNUNET_assert (0 == json_array_append_new (deposit_arr, jdeposit)); @@ -529,6 +680,7 @@ TALER_EXCHANGE_purse_create_with_deposit ( curl_easy_cleanup (eh); json_decref (create_obj); GNUNET_free (pch->econtract.econtract); + GNUNET_free (pch->deposits); GNUNET_free (pch->url); GNUNET_free (pch); return NULL; @@ -558,6 +710,7 @@ TALER_EXCHANGE_purse_create_with_deposit_cancel ( } GNUNET_free (pch->econtract.econtract); GNUNET_free (pch->url); + GNUNET_free (pch->deposits); TALER_curl_easy_post_finished (&pch->ctx); GNUNET_free (pch); } diff --git a/src/lib/exchange_api_purse_deposit.c b/src/lib/exchange_api_purse_deposit.c index 2027ca0d8..6946419dc 100644 --- a/src/lib/exchange_api_purse_deposit.c +++ b/src/lib/exchange_api_purse_deposit.c @@ -28,6 +28,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_json_lib.h" #include "taler_exchange_service.h" +#include "exchange_api_common.h" #include "exchange_api_handle.h" #include "taler_signatures.h" #include "exchange_api_curl_defaults.h" @@ -44,6 +45,11 @@ struct Coin struct TALER_CoinSpendPublicKeyP coin_pub; /** + * Signature made with the coin. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** * Coin's denomination. */ struct TALER_DenominationHashP h_denom_pub; @@ -226,30 +232,17 @@ handle_purse_deposit_finished (void *cls, { case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA: { - const char *partner_url = NULL; struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_CoinSpendSignatureP coin_sig; - struct TALER_Amount amount; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &coin_sig), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &coin_pub), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("partner_url", - &partner_url), - NULL), - TALER_JSON_spec_amount ("amount", - keys->currency, - &amount), - GNUNET_JSON_spec_end () - }; bool found = false; if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) + TALER_EXCHANGE_check_purse_coin_conflict_ ( + &pch->purse_pub, + pch->base_url, + j, + &coin_pub, + &coin_sig)) { GNUNET_break_op (0); dr.hr.http_status = 0; @@ -257,29 +250,21 @@ handle_purse_deposit_finished (void *cls, break; } for (unsigned int i = 0; i<pch->num_deposits; i++) + { if (0 == GNUNET_memcmp (&coin_pub, &pch->coins[i].coin_pub)) { + if (0 == GNUNET_memcmp (&coin_sig, + &pch->coins[i].coin_sig)) + { + /* identical signature => not a conflict */ + continue; + } found = true; break; } - if (! found) - { - /* proof is about a coin we did not even deposit */ - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; } - if (NULL == partner_url) - partner_url = pch->base_url; - if (GNUNET_OK != - TALER_wallet_purse_deposit_verify ( - partner_url, - &pch->purse_pub, - &amount, - &coin_pub, - &coin_sig)) + if (! found) { GNUNET_break_op (0); dr.hr.http_status = 0; @@ -291,27 +276,18 @@ handle_purse_deposit_finished (void *cls, } case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: { - json_t *history; - struct TALER_Amount total; - struct TALER_DenominationHashP h_denom_pub; - const struct TALER_EXCHANGE_DenomPublicKey *dki; struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &coin_pub), - GNUNET_JSON_spec_json ("history", - &history), - GNUNET_JSON_spec_end () - }; + struct TALER_Amount remaining; bool found = false; const struct Coin *my_coin; if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) + TALER_EXCHANGE_check_coin_amount_conflict_ ( + keys, + j, + &coin_pub, + &remaining)) { - GNUNET_break_op (0); dr.hr.http_status = 0; dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; @@ -334,45 +310,22 @@ handle_purse_deposit_finished (void *cls, dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - dki = TALER_EXCHANGE_get_denomination_key_by_hash ( - keys, - &my_coin->h_denom_pub); - if (NULL == dki) - { - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; - GNUNET_break_op (0); - break; - } - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (dki, - dki->value.currency, - &coin_pub, - history, - &h_denom_pub, - &total)) + if (1 == TALER_amount_cmp (&remaining, + &my_coin->contribution)) { + /* transaction should have still fit */ GNUNET_break_op (0); dr.hr.http_status = 0; dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - json_decref (history); break; } - json_decref (history); - if (0 > - TALER_amount_add (&total, - &total, - &my_coin->contribution)) - { - /* clearly not OK if our transaction would have caused - the overflow... */ - break; - } - if (0 >= TALER_amount_cmp (&total, - &dki->value)) + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_signature_conflict_ ( + j, + &my_coin->coin_sig)) { - /* transaction should have still fit */ - GNUNET_break (0); + /* THIS transaction must not be in the conflicting history */ + GNUNET_break_op (0); dr.hr.http_status = 0; dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; @@ -382,27 +335,18 @@ handle_purse_deposit_finished (void *cls, } case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: { - json_t *history; - struct TALER_Amount total; - struct TALER_DenominationHashP h_denom_pub; - const struct Coin *my_coin; - const struct TALER_EXCHANGE_DenomPublicKey *dki; struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &coin_pub), - GNUNET_JSON_spec_json ("history", - &history), - GNUNET_JSON_spec_end () - }; + struct TALER_Amount remaining; bool found = false; + const struct Coin *my_coin; if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) + TALER_EXCHANGE_check_coin_amount_conflict_ ( + keys, + j, + &coin_pub, + &remaining)) { - GNUNET_break_op (0); dr.hr.http_status = 0; dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; @@ -425,31 +369,12 @@ handle_purse_deposit_finished (void *cls, dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - dki = TALER_EXCHANGE_get_denomination_key_by_hash ( - keys, - &my_coin->h_denom_pub); - memset (&h_denom_pub, - 0, - sizeof (h_denom_pub)); if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (dki, - dki->value.currency, - &coin_pub, - history, - &h_denom_pub, - &total)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - json_decref (history); - break; - } - json_decref (history); - if (0 == GNUNET_memcmp (&dki->h_key, - &h_denom_pub)) + TALER_EXCHANGE_check_coin_denomination_conflict_ ( + j, + &my_coin->h_denom_pub)) { - /* sorry, this proves nothing */ + /* no conflicting denomination detected */ GNUNET_break_op (0); dr.hr.http_status = 0; dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; @@ -562,7 +487,6 @@ TALER_EXCHANGE_purse_deposit ( const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i]; struct Coin *coin = &pch->coins[i]; json_t *jdeposit; - struct TALER_CoinSpendSignatureP coin_sig; #if FIXME_OEC struct TALER_AgeCommitmentHash agh; struct TALER_AgeCommitmentHash *aghp = NULL; @@ -593,7 +517,7 @@ TALER_EXCHANGE_purse_deposit ( &pch->purse_pub, &deposit->amount, &deposit->coin_priv, - &coin_sig); + &coin->coin_sig); jdeposit = GNUNET_JSON_PACK ( #if FIXME_OEC GNUNET_JSON_pack_allow_null ( @@ -612,7 +536,7 @@ TALER_EXCHANGE_purse_deposit ( GNUNET_JSON_pack_data_auto ("coin_pub", &coin->coin_pub), GNUNET_JSON_pack_data_auto ("coin_sig", - &coin_sig)); + &coin->coin_sig)); GNUNET_assert (0 == json_array_append_new (deposit_arr, jdeposit)); diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c index 5c197e2f6..49fb6fd55 100644 --- a/src/lib/exchange_api_recoup.c +++ b/src/lib/exchange_api_recoup.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2017-2021 Taler Systems SA + Copyright (C) 2017-2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -27,6 +27,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_json_lib.h" #include "taler_exchange_service.h" +#include "exchange_api_common.h" #include "exchange_api_handle.h" #include "taler_signatures.h" #include "exchange_api_curl_defaults.h" @@ -60,6 +61,11 @@ struct TALER_EXCHANGE_RecoupHandle struct TALER_EXCHANGE_DenomPublicKey pk; /** + * Our signature requesting the recoup. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** * Handle for the request. */ struct GNUNET_CURL_Job *job; @@ -139,8 +145,10 @@ handle_recoup_finished (void *cls, .reply = j, .http_status = (unsigned int) response_code }; + const struct TALER_EXCHANGE_Keys *keys; ph->job = NULL; + keys = TALER_EXCHANGE_get_keys (ph->exchange); switch (response_code) { case 0: @@ -166,76 +174,34 @@ handle_recoup_finished (void *cls, break; case MHD_HTTP_CONFLICT: { - /* Insufficient funds, proof attached */ - json_t *history; - struct TALER_Amount total; - struct TALER_DenominationHashP h_denom_pub; - const struct TALER_EXCHANGE_DenomPublicKey *dki; - enum TALER_ErrorCode ec; - - dki = &ph->pk; - history = json_object_get (j, - "history"); + struct TALER_Amount min_key; + + hr.ec = TALER_JSON_get_error_code (j); + hr.hint = TALER_JSON_get_error_hint (j); if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (dki, - dki->fees.deposit.currency, - &ph->coin_pub, - history, - &h_denom_pub, - &total)) + TALER_EXCHANGE_get_min_denomination_ (keys, + &min_key)) { - GNUNET_break_op (0); - hr.http_status = 0; + GNUNET_break (0); hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + hr.http_status = 0; + break; } - else - { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); - } - ec = TALER_JSON_get_error_code (j); - switch (ec) + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_conflict_ ( + keys, + j, + &ph->pk, + &ph->coin_pub, + &ph->coin_sig, + &min_key)) { - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - if (0 > TALER_amount_cmp (&total, - &dki->value)) - { - /* recoup MAY have still been possible */ - /* FIXME: This code may falsely complain, as we do not - know that the smallest denomination offered by the - exchange is here. We should look at the key - structure of ph->exchange, and find the smallest - _currently withdrawable_ denomination and check - if the value remaining would suffice... */ - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - if (0 == GNUNET_memcmp (&ph->pk.h_key, - &h_denom_pub)) - { - /* invalid proof provided */ - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - /* valid error from exchange */ - break; - default: - GNUNET_break_op (0); - hr.http_status = 0; + GNUNET_break (0); hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + hr.http_status = 0; break; } - ph->cb (ph->cb_cls, - &hr, - NULL); - TALER_EXCHANGE_recoup_cancel (ph); - return; + break; } case MHD_HTTP_FORBIDDEN: /* Nothing really to verify, exchange says one of the signatures is @@ -291,8 +257,6 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange, { struct TALER_EXCHANGE_RecoupHandle *ph; struct GNUNET_CURL_Context *ctx; - struct TALER_CoinSpendSignatureP coin_sig; - struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_DenominationHashP h_denom_pub; json_t *recoup_obj; CURL *eh; @@ -302,7 +266,7 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange, GNUNET_assert (GNUNET_YES == TEAH_handle_is_ready (exchange)); - + ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle); TALER_planchet_setup_coin_priv (ps, exchange_vals, &coin_priv); @@ -310,13 +274,13 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange, exchange_vals, &bks); GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); + &ph->coin_pub.eddsa_pub); TALER_denom_pub_hash (&pk->key, &h_denom_pub); TALER_wallet_recoup_sign (&h_denom_pub, &bks, &coin_priv, - &coin_sig); + &ph->coin_sig); recoup_obj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("denom_pub_hash", &h_denom_pub), @@ -325,7 +289,7 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange, TALER_JSON_pack_exchange_withdraw_values ("ewv", exchange_vals), GNUNET_JSON_pack_data_auto ("coin_sig", - &coin_sig), + &ph->coin_sig), GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", &bks)); if (TALER_DENOMINATION_CS == denom_sig->cipher) @@ -352,7 +316,7 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange, char *end; end = GNUNET_STRINGS_data_to_string ( - &coin_pub, + &ph->coin_pub, sizeof (struct TALER_CoinSpendPublicKeyP), pub_str, sizeof (pub_str)); @@ -363,8 +327,6 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange, pub_str); } - ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle); - ph->coin_pub = coin_pub; ph->exchange = exchange; ph->pk = *pk; memset (&ph->pk.key, diff --git a/src/lib/exchange_api_recoup_refresh.c b/src/lib/exchange_api_recoup_refresh.c index 8ae8f9764..d551df743 100644 --- a/src/lib/exchange_api_recoup_refresh.c +++ b/src/lib/exchange_api_recoup_refresh.c @@ -27,6 +27,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_json_lib.h" #include "taler_exchange_service.h" +#include "exchange_api_common.h" #include "exchange_api_handle.h" #include "taler_signatures.h" #include "exchange_api_curl_defaults.h" @@ -79,6 +80,11 @@ struct TALER_EXCHANGE_RecoupRefreshHandle */ struct TALER_CoinSpendPublicKeyP coin_pub; + /** + * Signature affirming the recoup-refresh operation. + */ + struct TALER_CoinSpendSignatureP coin_sig; + }; @@ -140,8 +146,10 @@ handle_recoup_refresh_finished (void *cls, .reply = j, .http_status = (unsigned int) response_code }; + const struct TALER_EXCHANGE_Keys *keys; ph->job = NULL; + keys = TALER_EXCHANGE_get_keys (ph->exchange); switch (response_code) { case 0: @@ -180,76 +188,34 @@ handle_recoup_refresh_finished (void *cls, break; case MHD_HTTP_CONFLICT: { - /* Insufficient funds, proof attached */ - json_t *history; - struct TALER_Amount total; - struct TALER_DenominationHashP h_denom_pub; - const struct TALER_EXCHANGE_DenomPublicKey *dki; - enum TALER_ErrorCode ec; - - dki = &ph->pk; - history = json_object_get (j, - "history"); + struct TALER_Amount min_key; + + hr.ec = TALER_JSON_get_error_code (j); + hr.hint = TALER_JSON_get_error_hint (j); if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (dki, - dki->fees.deposit.currency, - &ph->coin_pub, - history, - &h_denom_pub, - &total)) + TALER_EXCHANGE_get_min_denomination_ (keys, + &min_key)) { - GNUNET_break_op (0); - hr.http_status = 0; + GNUNET_break (0); hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + hr.http_status = 0; + break; } - else - { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); - } - ec = TALER_JSON_get_error_code (j); - switch (ec) + if (GNUNET_OK != + TALER_EXCHANGE_check_coin_conflict_ ( + keys, + j, + &ph->pk, + &ph->coin_pub, + &ph->coin_sig, + &min_key)) { - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - if (0 > TALER_amount_cmp (&total, - &dki->value)) - { - /* recoup MAY have still been possible */ - /* FIXME: This code may falsely complain, as we do not - know that the smallest denomination offered by the - exchange is here. We should look at the key - structure of ph->exchange, and find the smallest - _currently withdrawable_ denomination and check - if the value remaining would suffice... */ - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - if (0 == GNUNET_memcmp (&ph->pk.h_key, - &h_denom_pub)) - { - /* invalid proof provided */ - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - /* valid error from exchange */ - break; - default: - GNUNET_break_op (0); - hr.http_status = 0; + GNUNET_break (0); hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + hr.http_status = 0; break; } - ph->cb (ph->cb_cls, - &hr, - NULL); - TALER_EXCHANGE_recoup_refresh_cancel (ph); - return; + break; } case MHD_HTTP_GONE: /* Kind of normal: the money was already sent to the merchant @@ -295,8 +261,6 @@ TALER_EXCHANGE_recoup_refresh ( { struct TALER_EXCHANGE_RecoupRefreshHandle *ph; struct GNUNET_CURL_Context *ctx; - struct TALER_CoinSpendSignatureP coin_sig; - struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_DenominationHashP h_denom_pub; json_t *recoup_obj; CURL *eh; @@ -307,6 +271,14 @@ TALER_EXCHANGE_recoup_refresh ( GNUNET_assert (NULL != recoup_cb); GNUNET_assert (GNUNET_YES == TEAH_handle_is_ready (exchange)); + ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle); + ph->exchange = exchange; + ph->pk = *pk; + memset (&ph->pk.key, + 0, + sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */ + ph->cb = recoup_cb; + ph->cb_cls = recoup_cb_cls; TALER_planchet_setup_coin_priv (ps, exchange_vals, &coin_priv); @@ -314,13 +286,13 @@ TALER_EXCHANGE_recoup_refresh ( exchange_vals, &bks); GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); + &ph->coin_pub.eddsa_pub); TALER_denom_pub_hash (&pk->key, &h_denom_pub); TALER_wallet_recoup_refresh_sign (&h_denom_pub, &bks, &coin_priv, - &coin_sig); + &ph->coin_sig); recoup_obj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("denom_pub_hash", &h_denom_pub), @@ -329,7 +301,7 @@ TALER_EXCHANGE_recoup_refresh ( TALER_JSON_pack_exchange_withdraw_values ("ewv", exchange_vals), GNUNET_JSON_pack_data_auto ("coin_sig", - &coin_sig), + &ph->coin_sig), GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", &bks)); @@ -358,7 +330,7 @@ TALER_EXCHANGE_recoup_refresh ( char *end; end = GNUNET_STRINGS_data_to_string ( - &coin_pub, + &ph->coin_pub, sizeof (struct TALER_CoinSpendPublicKeyP), pub_str, sizeof (pub_str)); @@ -369,15 +341,6 @@ TALER_EXCHANGE_recoup_refresh ( pub_str); } - ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle); - ph->coin_pub = coin_pub; - ph->exchange = exchange; - ph->pk = *pk; - memset (&ph->pk.key, - 0, - sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */ - ph->cb = recoup_cb; - ph->cb_cls = recoup_cb_cls; ph->url = TEAH_path_to_url (exchange, arg_str); if (NULL == ph->url) diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c index 004661b00..09a21883d 100644 --- a/src/lib/exchange_api_refund.c +++ b/src/lib/exchange_api_refund.c @@ -173,9 +173,12 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, const json_t *json) { json_t *history; + struct TALER_DenominationHashP h_denom_pub; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_json ("history", &history), + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &h_denom_pub), GNUNET_JSON_spec_end () }; size_t len; @@ -234,7 +237,6 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, struct TALER_AgeCommitmentHash h_age_commitment; bool no_hac; // struct TALER_ExtensionContractHashP h_extensions; // FIXME! - struct TALER_DenominationHashP h_denom_pub; struct GNUNET_TIME_Timestamp wallet_timestamp; struct TALER_MerchantPublicKeyP merchant_pub; struct GNUNET_TIME_Timestamp refund_deadline; @@ -246,8 +248,6 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, &h_contract_terms), GNUNET_JSON_spec_fixed_auto ("h_wire", &h_wire), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - &h_denom_pub), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("h_age_commitment", &h_age_commitment), @@ -429,24 +429,22 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, } } + if (have_refund) { - if (have_refund) + if (0 > + TALER_amount_add (&rtotal, + &rtotal, + &rh->refund_amount)) { - if (0 > - TALER_amount_add (&rtotal, - &rtotal, - &rh->refund_amount)) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - } - else - { - rtotal = rh->refund_amount; + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; } } + else + { + rtotal = rh->refund_amount; + } if (-1 == TALER_amount_cmp (&dtotal, &rtotal)) { |