From ef938e0f7aca4232cbae322fdc7b68ed21fcd679 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 15 Feb 2022 17:07:13 +0100 Subject: -correctly implement CS idempotency check on withdraw --- src/exchange/taler-exchange-httpd_recoup.c | 24 +++---- src/exchange/taler-exchange-httpd_withdraw.c | 25 ++++++-- src/exchangedb/exchange-0001.sql | 42 ++++++++++-- src/exchangedb/plugin_exchangedb_postgres.c | 96 +++++++++++++++------------- src/exchangedb/test_exchangedb.c | 27 +++++--- src/include/taler_crypto_lib.h | 32 ++++++++++ src/include/taler_exchangedb_plugin.h | 29 +++++---- src/util/denom.c | 37 +++++++++++ 8 files changed, 220 insertions(+), 92 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c index c635769c6..ea319d11c 100644 --- a/src/exchange/taler-exchange-httpd_recoup.c +++ b/src/exchange/taler-exchange-httpd_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 Affero General Public License as published by the Free Software @@ -40,9 +40,9 @@ struct RecoupContext { /** - * Hash of the blinded coin. + * Hash identifying the withdraw request. */ - struct TALER_BlindedCoinHash h_blind; + struct TALER_WithdrawIdentificationHash wih; /** * Set by #recoup_transaction() to the reserve that will @@ -273,9 +273,9 @@ verify_and_execute_recoup ( blinded_planchet.details.cs_blinded_planchet.nonce = *nonce; if (GNUNET_OK != - TALER_coin_ev_hash (&blinded_planchet, - &coin->denom_pub_hash, - &pc.h_blind)) + TALER_withdraw_request_hash (&blinded_planchet, + &coin->denom_pub_hash, + &pc.wih)) { GNUNET_break (0); return TALER_MHD_reply_with_error (connection, @@ -308,10 +308,10 @@ verify_and_execute_recoup ( { enum GNUNET_DB_QueryStatus qs; - qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls, - &pc.h_blind, - &pc.reserve_pub, - &pc.reserve_out_serial_id); + qs = TEH_plugin->get_reserve_by_wih (TEH_plugin->cls, + &pc.wih, + &pc.reserve_pub, + &pc.reserve_out_serial_id); if (0 > qs) { GNUNET_break (0); @@ -319,13 +319,13 @@ verify_and_execute_recoup ( connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_reserve_by_h_blind"); + "get_reserve_by_wih"); } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Recoup requested for unknown envelope %s\n", - GNUNET_h2s (&pc.h_blind.hash)); + GNUNET_h2s (&pc.wih.hash)); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_NOT_FOUND, diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c index 3799187c1..a3ac1de33 100644 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ b/src/exchange/taler-exchange-httpd_withdraw.c @@ -91,6 +91,11 @@ reply_withdraw_insufficient_funds ( struct WithdrawContext { + /** + * Hash that uniquely identifies the withdraw request. + */ + struct TALER_WithdrawIdentificationHash wih; + /** * Hash of the (blinded) message to be signed by the Exchange. */ @@ -155,6 +160,7 @@ withdraw_transaction (void *cls, now = GNUNET_TIME_timestamp_get (); qs = TEH_plugin->do_withdraw (TEH_plugin->cls, + &wc->wih, &wc->collectable, now, &found, @@ -294,7 +300,7 @@ check_request_idempotent (struct TEH_RequestContext *rc, enum GNUNET_DB_QueryStatus qs; qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, - &wc->collectable.h_coin_envelope, + &wc->wih, &wc->collectable); if (0 > qs) { @@ -496,7 +502,18 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, NULL); } - // TODO: if CS: check nonce for reuse + if (GNUNET_OK != + TALER_withdraw_request_hash (&wc.blinded_planchet, + &wc.collectable.denom_pub_hash, + &wc.wih)) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + } /* Sign before transaction! */ ec = TEH_keys_denomination_sign ( @@ -535,10 +552,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, /* Clean up and send back final response */ GNUNET_JSON_parse_free (spec); - // FIXME: in CS-case, we MUST re-transmit any _existing_ signature - // (if database had a record matching the nonce) - // instead of sending a 'fresh' one back (as c0/c1 may differ in - // a client attack! { MHD_RESULT ret; diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql index 66856f60c..1111f3810 100644 --- a/src/exchangedb/exchange-0001.sql +++ b/src/exchangedb/exchange-0001.sql @@ -196,7 +196,8 @@ CREATE INDEX IF NOT EXISTS reserves_close_by_reserve_pub_index CREATE TABLE IF NOT EXISTS reserves_out (reserve_out_serial_id BIGSERIAL -- UNIQUE - ,h_blind_ev BYTEA PRIMARY KEY CHECK (LENGTH(h_blind_ev)=64) + ,wih BYTEA PRIMARY KEY CHECK (LENGTH(wih)=64) + ,h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64) -- UNIQUE ,denominations_serial INT8 NOT NULL REFERENCES denominations (denominations_serial) ,denom_sig BYTEA NOT NULL ,reserve_uuid INT8 NOT NULL -- REFERENCES reserves (reserve_uuid) ON DELETE CASCADE @@ -205,9 +206,11 @@ CREATE TABLE IF NOT EXISTS reserves_out ,amount_with_fee_val INT8 NOT NULL ,amount_with_fee_frac INT4 NOT NULL ) - PARTITION BY HASH (h_blind_ev); + PARTITION BY HASH (wih); COMMENT ON TABLE reserves_out IS 'Withdraw operations performed on reserves.'; +COMMENT ON COLUMN reserves_out.wih + IS 'Hash that uniquely identifies the withdraw request. Used to detect request replays (crucial for CS) and to check the withdraw existed during recoup.'; COMMENT ON COLUMN reserves_out.h_blind_ev IS 'Hash of the blinded coin, used as primary key here so that broken clients that use a non-random coin or blinding factor fail to withdraw (otherwise they would fail on deposit when the coin is not unique there).'; COMMENT ON COLUMN reserves_out.denominations_serial @@ -637,7 +640,7 @@ COMMENT ON TABLE recoup COMMENT ON COLUMN recoup.known_coin_id IS 'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!'; COMMENT ON COLUMN recoup.reserve_out_serial_id - IS 'Identifies the h_blind_ev of the recouped coin and provides the link to the credited reserve.'; + IS 'Identifies the wih of the recouped coin and provides the link to the credited reserve.'; COMMENT ON COLUMN recoup.coin_sig IS 'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP'; COMMENT ON COLUMN recoup.coin_blind @@ -812,6 +815,7 @@ CREATE INDEX IF NOT EXISTS revolving_work_shards_by_job_name_active_last_attempt CREATE OR REPLACE FUNCTION exchange_do_withdraw( + IN in_wih BYTEA, IN amount_val INT8, IN amount_frac INT4, IN h_denom_pub BYTEA, @@ -825,7 +829,8 @@ CREATE OR REPLACE FUNCTION exchange_do_withdraw( OUT balance_ok BOOLEAN, OUT kycok BOOLEAN, OUT account_uuid INT8, - OUT ruuid INT8) + OUT ruuid INT8, + OUT out_denom_sig BYTEA) LANGUAGE plpgsql AS $$ DECLARE @@ -838,7 +843,7 @@ DECLARE reserve_frac INT4; BEGIN -- Shards: reserves by reserve_pub (SELECT) --- reserves_out (INSERT, with CONFLICT detection) by h_blind_ev +-- reserves_out (INSERT, with CONFLICT detection) by wih -- reserves by reserve_pub (UPDATE) -- reserves_in by reserve_pub (SELECT) -- wire_targets by wire_target_serial_id @@ -887,6 +892,7 @@ END IF; -- the query successful due to idempotency. INSERT INTO reserves_out (h_blind_ev + ,wih ,denominations_serial ,denom_sig ,reserve_uuid @@ -896,6 +902,7 @@ INSERT INTO reserves_out ,amount_with_fee_frac) VALUES (h_coin_envelope + ,in_wih ,denom_serial ,denom_sig ,ruuid @@ -908,6 +915,25 @@ ON CONFLICT DO NOTHING; IF NOT FOUND THEN -- idempotent query, all constraints must be satisfied + + SELECT + denom_sig + INTO + out_denom_sig + FROM reserves_in + WHERE wih=in_wih + LIMIT 1; -- limit 1 should not be required (without p2p transfers) + + IF NOT FOUND + THEN + reserve_found=FALSE; + balance_ok=FALSE; + kycok=FALSE; + account_uuid=0; + ruuid=0; + ASSERT false, 'internal logic error'; + END IF; + reserve_found=TRUE; balance_ok=TRUE; kycok=TRUE; @@ -967,9 +993,13 @@ SELECT WHERE reserve_pub=rpub LIMIT 1; -- limit 1 should not be required (without p2p transfers) +-- Return denomination signature as result that +-- was given as the argument. +out_denom_sig=denom_sig; + END $$; -COMMENT ON FUNCTION exchange_do_withdraw(INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8) +COMMENT ON FUNCTION exchange_do_withdraw(BYTEA, INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8) IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and if so updates the database with the result'; diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index ce184f48d..98724fa04 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -560,13 +560,6 @@ prepare_statements (struct PostgresClosure *pg) " ON (wire_source_serial_id = wire_target_serial_id)" " WHERE reserve_pub=$1;", 1), - /* Lock withdraw table; NOTE: we may want to eventually shard the - deposit table to avoid this lock being the main point of - contention limiting transaction performance. */ - GNUNET_PQ_make_prepare ( - "lock_withdraw", - "LOCK TABLE reserves_out;", - 0), /* Used in #postgres_do_withdraw() to store the signature of a blinded coin with the blinded coin's details before returning it during /reserve/withdraw. We store @@ -582,9 +575,10 @@ prepare_statements (struct PostgresClosure *pg) ",kycok AS kyc_ok" ",account_uuid AS payment_target_uuid" ",ruuid" + ",out_denom_sig" " FROM exchange_do_withdraw" - " ($1,$2,$3,$4,$5,$6,$7,$8,$9);", - 9), + " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);", + 10), /* Used in #postgres_do_withdraw_limit_check() to check if the withdrawals remain below the limit under which KYC is not required. */ @@ -659,6 +653,7 @@ prepare_statements (struct PostgresClosure *pg) ",reserve_sig" ",reserves.reserve_pub" ",execution_date" + ",h_blind_ev" ",amount_with_fee_val" ",amount_with_fee_frac" ",denom.fee_withdraw_val" @@ -668,7 +663,7 @@ prepare_statements (struct PostgresClosure *pg) " USING (reserve_uuid)" " JOIN denominations denom" " USING (denominations_serial)" - " WHERE h_blind_ev=$1;", + " WHERE wih=$1;", 1), /* Used during #postgres_get_reserve_history() to obtain all of the /reserve/withdraw operations that @@ -1671,16 +1666,16 @@ prepare_statements (struct PostgresClosure *pg) " ON (denoms.denominations_serial = coins.denominations_serial)" " WHERE coins.coin_pub=$1;", 1), - /* Used in #postgres_get_reserve_by_h_blind() */ + /* Used in #postgres_get_reserve_by_wih() */ GNUNET_PQ_make_prepare ( - "reserve_by_h_blind", + "reserve_by_wih", "SELECT" " reserves.reserve_pub" ",reserve_out_serial_id" " FROM reserves_out" " JOIN reserves" " USING (reserve_uuid)" - " WHERE h_blind_ev=$1" + " WHERE wih=$1" " LIMIT 1;", 1), /* Used in #postgres_get_old_coin_by_h_blind() */ @@ -4304,8 +4299,7 @@ postgres_reserves_in_insert (void *cls, * key of the hash of the blinded message. * * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param h_blind hash of the blinded coin to be signed (will match - * `h_coin_envelope` in the @a collectable to be returned) + * @param wih hash that uniquely identifies the withdraw operation * @param collectable corresponding collectable coin (blind signature) * if a coin is found * @return statement execution status @@ -4313,12 +4307,12 @@ postgres_reserves_in_insert (void *cls, static enum GNUNET_DB_QueryStatus postgres_get_withdraw_info ( void *cls, - const struct TALER_BlindedCoinHash *h_blind, + const struct TALER_WithdrawIdentificationHash *wih, struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_blind), + GNUNET_PQ_query_param_auto_from_type (wih), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -4330,24 +4324,15 @@ postgres_get_withdraw_info ( &collectable->reserve_sig), GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", &collectable->reserve_pub), + GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev", + &collectable->h_coin_envelope), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &collectable->amount_with_fee), TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw", &collectable->withdraw_fee), GNUNET_PQ_result_spec_end }; -#if EXPLICIT_LOCKS - struct GNUNET_PQ_QueryParam no_params[] = { - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; - if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "lock_withdraw", - no_params))) - return qs; -#endif - collectable->h_coin_envelope = *h_blind; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "get_withdraw_info", params, @@ -4360,8 +4345,8 @@ postgres_get_withdraw_info ( * and possibly persisting the withdrawal details. * * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param collectable corresponding collectable coin (blind signature) - * if a coin is found + * @param wih hash that uniquely identifies the withdraw operation + * @param[in,out] collectable corresponding collectable coin (blind signature) if a coin is found; possibly updated if a (different) signature exists already * @param now current time (rounded) * @param[out] found set to true if the reserve was found * @param[out] balance_ok set to true if the balance was sufficient @@ -4372,7 +4357,8 @@ postgres_get_withdraw_info ( static enum GNUNET_DB_QueryStatus postgres_do_withdraw ( void *cls, - const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, + const struct TALER_WithdrawIdentificationHash *wih, + struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, struct GNUNET_TIME_Timestamp now, bool *found, bool *balance_ok, @@ -4382,6 +4368,7 @@ postgres_do_withdraw ( struct PostgresClosure *pg = cls; struct GNUNET_TIME_Timestamp gc; struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (wih), TALER_PQ_query_param_amount (&collectable->amount_with_fee), GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash), GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub), @@ -4392,6 +4379,9 @@ postgres_do_withdraw ( GNUNET_PQ_query_param_timestamp (&gc), GNUNET_PQ_query_param_end }; + enum GNUNET_DB_QueryStatus qs; + bool no_out_sig; + struct TALER_BlindedDenominationSignature out_sig; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_bool ("reserve_found", found), @@ -4403,18 +4393,33 @@ postgres_do_withdraw ( &kyc->payment_target_uuid), GNUNET_PQ_result_spec_uint64 ("ruuid", ruuid), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_blinded_denom_sig ("out_denom_sig", + &out_sig), + &no_out_sig), GNUNET_PQ_result_spec_end }; +#if 0 + memset (&out_sig, + 0, + sizeof (out_sig)); +#endif gc = GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add (now.abs_time, pg->legal_reserve_expiration_time)); kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW; - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "call_withdraw", - params, - rs); - + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "call_withdraw", + params, + rs); + if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && + (! no_out_sig) ) + { + TALER_blinded_denom_sig_free (&collectable->sig); + collectable->sig = out_sig; + } + return qs; } @@ -9373,20 +9378,21 @@ postgres_select_reserve_closed_above_serial_id ( * from given the hash of the blinded coin. * * @param cls closure - * @param h_blind_ev hash of the blinded coin + * @param wih hash that uniquely identifies the withdraw request * @param[out] reserve_pub set to information about the reserve (on success only) * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in reserves_out * @return transaction status code */ static enum GNUNET_DB_QueryStatus -postgres_get_reserve_by_h_blind (void *cls, - const struct TALER_BlindedCoinHash *h_blind_ev, - struct TALER_ReservePublicKeyP *reserve_pub, - uint64_t *reserve_out_serial_id) +postgres_get_reserve_by_wih ( + void *cls, + const struct TALER_WithdrawIdentificationHash *wih, + struct TALER_ReservePublicKeyP *reserve_pub, + uint64_t *reserve_out_serial_id) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_blind_ev), + GNUNET_PQ_query_param_auto_from_type (wih), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -9398,7 +9404,7 @@ postgres_get_reserve_by_h_blind (void *cls, }; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "reserve_by_h_blind", + "reserve_by_wih", params, rs); } @@ -11663,8 +11669,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &postgres_select_recoup_refresh_above_serial_id; plugin->select_reserve_closed_above_serial_id = &postgres_select_reserve_closed_above_serial_id; - plugin->get_reserve_by_h_blind - = &postgres_get_reserve_by_h_blind; + plugin->get_reserve_by_wih + = &postgres_get_reserve_by_wih; plugin->get_old_coin_by_h_blind = &postgres_get_old_coin_by_h_blind; plugin->insert_denomination_revocation diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index 9561df123..0622e0695 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -1346,6 +1346,7 @@ run (void *cls) struct GNUNET_TIME_Timestamp now; struct TALER_WireSaltP salt; struct TALER_CoinPubHash c_hash; + struct TALER_WithdrawIdentificationHash wih; uint64_t known_coin_id; uint64_t rrc_serial; struct TALER_EXCHANGEDB_Refresh refresh; @@ -1383,7 +1384,7 @@ run (void *cls) plugin->create_tables (plugin->cls)) { result = 77; - goto drop; + goto cleanup; } plugin->preflight (plugin->cls); FAILIF (GNUNET_OK != @@ -1499,9 +1500,14 @@ run (void *cls) &cbc.denom_pub_hash, &cbc.h_coin_envelope)); GNUNET_assert (GNUNET_OK == - TALER_denom_sign_blinded (&cbc.sig, - &dkp->priv, - &pd.blinded_planchet)); + TALER_withdraw_request_hash (&pd.blinded_planchet, + &cbc.denom_pub_hash, + &wih)); GNUNET_assert ( + GNUNET_OK == + TALER_denom_sign_blinded ( + &cbc.sig, + &dkp->priv, + &pd.blinded_planchet)); TALER_blinded_planchet_free (&pd.blinded_planchet); } } @@ -1511,6 +1517,7 @@ run (void *cls) GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (CURRENCY, &cbc.withdraw_fee)); + { bool found; bool balance_ok; @@ -1519,6 +1526,7 @@ run (void *cls) FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != plugin->do_withdraw (plugin->cls, + &wih, &cbc, now, &found, @@ -1540,16 +1548,16 @@ run (void *cls) value.fraction, value.currency)); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - plugin->get_reserve_by_h_blind (plugin->cls, - &cbc.h_coin_envelope, - &reserve_pub3, - &reserve_out_serial_id)); + plugin->get_reserve_by_wih (plugin->cls, + &wih, + &reserve_pub3, + &reserve_out_serial_id)); FAILIF (0 != GNUNET_memcmp (&reserve_pub, &reserve_pub3)); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != plugin->get_withdraw_info (plugin->cls, - &cbc.h_coin_envelope, + &wih, &cbc2)); FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig, &cbc.reserve_sig)); @@ -2400,6 +2408,7 @@ drop: rh = NULL; GNUNET_break (GNUNET_OK == plugin->drop_tables (plugin->cls)); +cleanup: if (NULL != dkp) destroy_denom_key_pair (dkp); if (NULL != revealed_coins) diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index f007d67af..ab5202baa 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -571,6 +571,22 @@ struct TALER_BlindedCoinHash }; +/** + * Hash used to uniquely represent a withdraw process so as to perform + * idempotency checks (and prevent clients from harmfully replaying withdraw + * operations with problematic variations on the inputs). In the CS case, + * this is a hash over the DK and nonce, while in the RSA case, it is simply a + * hash over the DK and the blinded coin. + */ +struct TALER_WithdrawIdentificationHash +{ + /** + * Actual hash value. + */ + struct GNUNET_HashCode hash; +}; + + /** * Hash used to represent the hash of the public * key of a coin (without blinding). @@ -1308,6 +1324,22 @@ TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet, struct TALER_BlindedCoinHash *bch); +/** + * Compute the hash to uniquely identify a withdraw + * request. + * + * @param blinded_planchet blinded planchet + * @param denom_hash hash of the denomination publick key + * @param[out] wih where to write the hash + * @return #GNUNET_OK when successful, #GNUNET_SYSERR if an internal error occured + */ +enum GNUNET_GenericReturnValue +TALER_withdraw_request_hash ( + const struct TALER_BlindedPlanchet *blinded_planchet, + const struct TALER_DenominationHash *denom_hash, + struct TALER_WithdrawIdentificationHash *wih); + + /** * Compute the hash of a coin. * diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index eea170c19..ec647e9c6 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -2476,20 +2476,19 @@ struct TALER_EXCHANGEDB_Plugin /** - * Locate the response for a withdraw request under the - * key of the hash of the blinded message. Used to ensure - * idempotency of the request. + * Locate the response for a withdraw request under a hash that uniquely + * identifies the withdraw operation. Used to ensure idempotency of the + * request. * * @param cls the @e cls of this struct with the plugin-specific state - * @param h_blind hash of the blinded coin to be signed (will match - * `h_coin_envelope` in the @a collectable to be returned) - * @param collectable corresponding collectable coin (blind signature) + * @param wih hash that uniquely identifies the withdraw operation + * @param[out] collectable corresponding collectable coin (blind signature) * if a coin is found * @return statement execution status */ enum GNUNET_DB_QueryStatus (*get_withdraw_info)(void *cls, - const struct TALER_BlindedCoinHash *h_blind, + const struct TALER_WithdrawIdentificationHash *wih, struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable); @@ -2498,7 +2497,8 @@ struct TALER_EXCHANGEDB_Plugin * and possibly persisting the withdrawal details. * * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param collectable corresponding collectable coin (blind signature) + * @param wih hash that uniquely identifies the withdraw operation + * @param[in,out] collectable corresponding collectable coin (blind signature) * if a coin is found * @param now current time (rounded) * @param[out] found set to true if the reserve was found @@ -2510,7 +2510,8 @@ struct TALER_EXCHANGEDB_Plugin enum GNUNET_DB_QueryStatus (*do_withdraw)( void *cls, - const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, + const struct TALER_WithdrawIdentificationHash *wih, + struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, struct GNUNET_TIME_Timestamp now, bool *found, bool *balance_ok, @@ -3517,16 +3518,16 @@ struct TALER_EXCHANGEDB_Plugin * from given the hash of the blinded coin. * * @param cls closure - * @param h_blind_ev hash of the blinded coin + * @param wih hash identifying the withdraw operation * @param[out] reserve_pub set to information about the reserve (on success only) * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in reserves_out * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*get_reserve_by_h_blind)(void *cls, - const struct TALER_BlindedCoinHash *h_blind_ev, - struct TALER_ReservePublicKeyP *reserve_pub, - uint64_t *reserve_out_serial_id); + (*get_reserve_by_wih)(void *cls, + const struct TALER_WithdrawIdentificationHash *wih, + struct TALER_ReservePublicKeyP *reserve_pub, + uint64_t *reserve_out_serial_id); /** diff --git a/src/util/denom.c b/src/util/denom.c index 2e7b1264b..783e9a364 100644 --- a/src/util/denom.c +++ b/src/util/denom.c @@ -828,4 +828,41 @@ TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet, } +enum GNUNET_GenericReturnValue +TALER_withdraw_request_hash ( + const struct TALER_BlindedPlanchet *blinded_planchet, + const struct TALER_DenominationHash *denom_hash, + struct TALER_WithdrawIdentificationHash *wih) +{ + struct GNUNET_HashContext *hash_context; + + hash_context = GNUNET_CRYPTO_hash_context_start (); + GNUNET_CRYPTO_hash_context_read (hash_context, + denom_hash, + sizeof(*denom_hash)); + switch (blinded_planchet->cipher) + { + case TALER_DENOMINATION_RSA: + GNUNET_CRYPTO_hash_context_read ( + hash_context, + blinded_planchet->details.rsa_blinded_planchet.blinded_msg, + blinded_planchet->details.rsa_blinded_planchet.blinded_msg_size); + break; + case TALER_DENOMINATION_CS: + GNUNET_CRYPTO_hash_context_read ( + hash_context, + &blinded_planchet->details.cs_blinded_planchet.nonce, + sizeof (struct TALER_CsNonce)); + break; + default: + GNUNET_break (0); + GNUNET_CRYPTO_hash_context_abort (hash_context); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash_context_finish (hash_context, + &wih->hash); + return GNUNET_OK; +} + + /* end of denom.c */ -- cgit v1.2.3