-correctly implement CS idempotency check on withdraw

This commit is contained in:
Christian Grothoff 2022-02-15 17:07:13 +01:00
parent 8ecbdeb55b
commit ef938e0f7a
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
8 changed files with 220 additions and 92 deletions

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER 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 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 terms of the GNU Affero General Public License as published by the Free Software
@ -40,9 +40,9 @@
struct RecoupContext 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 * Set by #recoup_transaction() to the reserve that will
@ -273,9 +273,9 @@ verify_and_execute_recoup (
blinded_planchet.details.cs_blinded_planchet.nonce blinded_planchet.details.cs_blinded_planchet.nonce
= *nonce; = *nonce;
if (GNUNET_OK != if (GNUNET_OK !=
TALER_coin_ev_hash (&blinded_planchet, TALER_withdraw_request_hash (&blinded_planchet,
&coin->denom_pub_hash, &coin->denom_pub_hash,
&pc.h_blind)) &pc.wih))
{ {
GNUNET_break (0); GNUNET_break (0);
return TALER_MHD_reply_with_error (connection, return TALER_MHD_reply_with_error (connection,
@ -308,10 +308,10 @@ verify_and_execute_recoup (
{ {
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls, qs = TEH_plugin->get_reserve_by_wih (TEH_plugin->cls,
&pc.h_blind, &pc.wih,
&pc.reserve_pub, &pc.reserve_pub,
&pc.reserve_out_serial_id); &pc.reserve_out_serial_id);
if (0 > qs) if (0 > qs)
{ {
GNUNET_break (0); GNUNET_break (0);
@ -319,13 +319,13 @@ verify_and_execute_recoup (
connection, connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED, TALER_EC_GENERIC_DB_FETCH_FAILED,
"get_reserve_by_h_blind"); "get_reserve_by_wih");
} }
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Recoup requested for unknown envelope %s\n", "Recoup requested for unknown envelope %s\n",
GNUNET_h2s (&pc.h_blind.hash)); GNUNET_h2s (&pc.wih.hash));
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
connection, connection,
MHD_HTTP_NOT_FOUND, MHD_HTTP_NOT_FOUND,

View File

@ -91,6 +91,11 @@ reply_withdraw_insufficient_funds (
struct WithdrawContext struct WithdrawContext
{ {
/**
* Hash that uniquely identifies the withdraw request.
*/
struct TALER_WithdrawIdentificationHash wih;
/** /**
* Hash of the (blinded) message to be signed by the Exchange. * Hash of the (blinded) message to be signed by the Exchange.
*/ */
@ -155,6 +160,7 @@ withdraw_transaction (void *cls,
now = GNUNET_TIME_timestamp_get (); now = GNUNET_TIME_timestamp_get ();
qs = TEH_plugin->do_withdraw (TEH_plugin->cls, qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
&wc->wih,
&wc->collectable, &wc->collectable,
now, now,
&found, &found,
@ -294,7 +300,7 @@ check_request_idempotent (struct TEH_RequestContext *rc,
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls, qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
&wc->collectable.h_coin_envelope, &wc->wih,
&wc->collectable); &wc->collectable);
if (0 > qs) if (0 > qs)
{ {
@ -496,7 +502,18 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
NULL); 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! */ /* Sign before transaction! */
ec = TEH_keys_denomination_sign ( ec = TEH_keys_denomination_sign (
@ -535,10 +552,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
/* Clean up and send back final response */ /* Clean up and send back final response */
GNUNET_JSON_parse_free (spec); 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; MHD_RESULT ret;

View File

@ -196,7 +196,8 @@ CREATE INDEX IF NOT EXISTS reserves_close_by_reserve_pub_index
CREATE TABLE IF NOT EXISTS reserves_out CREATE TABLE IF NOT EXISTS reserves_out
(reserve_out_serial_id BIGSERIAL -- UNIQUE (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) ,denominations_serial INT8 NOT NULL REFERENCES denominations (denominations_serial)
,denom_sig BYTEA NOT NULL ,denom_sig BYTEA NOT NULL
,reserve_uuid INT8 NOT NULL -- REFERENCES reserves (reserve_uuid) ON DELETE CASCADE ,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_val INT8 NOT NULL
,amount_with_fee_frac INT4 NOT NULL ,amount_with_fee_frac INT4 NOT NULL
) )
PARTITION BY HASH (h_blind_ev); PARTITION BY HASH (wih);
COMMENT ON TABLE reserves_out COMMENT ON TABLE reserves_out
IS 'Withdraw operations performed on reserves.'; 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 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).'; 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 COMMENT ON COLUMN reserves_out.denominations_serial
@ -637,7 +640,7 @@ COMMENT ON TABLE recoup
COMMENT ON COLUMN recoup.known_coin_id 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!'; 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 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 COMMENT ON COLUMN recoup.coin_sig
IS 'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP'; IS 'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP';
COMMENT ON COLUMN recoup.coin_blind 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( CREATE OR REPLACE FUNCTION exchange_do_withdraw(
IN in_wih BYTEA,
IN amount_val INT8, IN amount_val INT8,
IN amount_frac INT4, IN amount_frac INT4,
IN h_denom_pub BYTEA, IN h_denom_pub BYTEA,
@ -825,7 +829,8 @@ CREATE OR REPLACE FUNCTION exchange_do_withdraw(
OUT balance_ok BOOLEAN, OUT balance_ok BOOLEAN,
OUT kycok BOOLEAN, OUT kycok BOOLEAN,
OUT account_uuid INT8, OUT account_uuid INT8,
OUT ruuid INT8) OUT ruuid INT8,
OUT out_denom_sig BYTEA)
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
@ -838,7 +843,7 @@ DECLARE
reserve_frac INT4; reserve_frac INT4;
BEGIN BEGIN
-- Shards: reserves by reserve_pub (SELECT) -- 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 by reserve_pub (UPDATE)
-- reserves_in by reserve_pub (SELECT) -- reserves_in by reserve_pub (SELECT)
-- wire_targets by wire_target_serial_id -- wire_targets by wire_target_serial_id
@ -887,6 +892,7 @@ END IF;
-- the query successful due to idempotency. -- the query successful due to idempotency.
INSERT INTO reserves_out INSERT INTO reserves_out
(h_blind_ev (h_blind_ev
,wih
,denominations_serial ,denominations_serial
,denom_sig ,denom_sig
,reserve_uuid ,reserve_uuid
@ -896,6 +902,7 @@ INSERT INTO reserves_out
,amount_with_fee_frac) ,amount_with_fee_frac)
VALUES VALUES
(h_coin_envelope (h_coin_envelope
,in_wih
,denom_serial ,denom_serial
,denom_sig ,denom_sig
,ruuid ,ruuid
@ -908,6 +915,25 @@ ON CONFLICT DO NOTHING;
IF NOT FOUND IF NOT FOUND
THEN THEN
-- idempotent query, all constraints must be satisfied -- 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; reserve_found=TRUE;
balance_ok=TRUE; balance_ok=TRUE;
kycok=TRUE; kycok=TRUE;
@ -967,9 +993,13 @@ SELECT
WHERE reserve_pub=rpub WHERE reserve_pub=rpub
LIMIT 1; -- limit 1 should not be required (without p2p transfers) 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 $$; 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'; 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';

View File

@ -560,13 +560,6 @@ prepare_statements (struct PostgresClosure *pg)
" ON (wire_source_serial_id = wire_target_serial_id)" " ON (wire_source_serial_id = wire_target_serial_id)"
" WHERE reserve_pub=$1;", " WHERE reserve_pub=$1;",
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 /* Used in #postgres_do_withdraw() to store
the signature of a blinded coin with the blinded coin's the signature of a blinded coin with the blinded coin's
details before returning it during /reserve/withdraw. We store details before returning it during /reserve/withdraw. We store
@ -582,9 +575,10 @@ prepare_statements (struct PostgresClosure *pg)
",kycok AS kyc_ok" ",kycok AS kyc_ok"
",account_uuid AS payment_target_uuid" ",account_uuid AS payment_target_uuid"
",ruuid" ",ruuid"
",out_denom_sig"
" FROM exchange_do_withdraw" " FROM exchange_do_withdraw"
" ($1,$2,$3,$4,$5,$6,$7,$8,$9);", " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);",
9), 10),
/* Used in #postgres_do_withdraw_limit_check() to check /* Used in #postgres_do_withdraw_limit_check() to check
if the withdrawals remain below the limit under which if the withdrawals remain below the limit under which
KYC is not required. */ KYC is not required. */
@ -659,6 +653,7 @@ prepare_statements (struct PostgresClosure *pg)
",reserve_sig" ",reserve_sig"
",reserves.reserve_pub" ",reserves.reserve_pub"
",execution_date" ",execution_date"
",h_blind_ev"
",amount_with_fee_val" ",amount_with_fee_val"
",amount_with_fee_frac" ",amount_with_fee_frac"
",denom.fee_withdraw_val" ",denom.fee_withdraw_val"
@ -668,7 +663,7 @@ prepare_statements (struct PostgresClosure *pg)
" USING (reserve_uuid)" " USING (reserve_uuid)"
" JOIN denominations denom" " JOIN denominations denom"
" USING (denominations_serial)" " USING (denominations_serial)"
" WHERE h_blind_ev=$1;", " WHERE wih=$1;",
1), 1),
/* Used during #postgres_get_reserve_history() to /* Used during #postgres_get_reserve_history() to
obtain all of the /reserve/withdraw operations that obtain all of the /reserve/withdraw operations that
@ -1671,16 +1666,16 @@ prepare_statements (struct PostgresClosure *pg)
" ON (denoms.denominations_serial = coins.denominations_serial)" " ON (denoms.denominations_serial = coins.denominations_serial)"
" WHERE coins.coin_pub=$1;", " WHERE coins.coin_pub=$1;",
1), 1),
/* Used in #postgres_get_reserve_by_h_blind() */ /* Used in #postgres_get_reserve_by_wih() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"reserve_by_h_blind", "reserve_by_wih",
"SELECT" "SELECT"
" reserves.reserve_pub" " reserves.reserve_pub"
",reserve_out_serial_id" ",reserve_out_serial_id"
" FROM reserves_out" " FROM reserves_out"
" JOIN reserves" " JOIN reserves"
" USING (reserve_uuid)" " USING (reserve_uuid)"
" WHERE h_blind_ev=$1" " WHERE wih=$1"
" LIMIT 1;", " LIMIT 1;",
1), 1),
/* Used in #postgres_get_old_coin_by_h_blind() */ /* 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. * key of the hash of the blinded message.
* *
* @param cls the `struct PostgresClosure` with the plugin-specific state * @param cls the `struct PostgresClosure` with the plugin-specific state
* @param h_blind hash of the blinded coin to be signed (will match * @param wih hash that uniquely identifies the withdraw operation
* `h_coin_envelope` in the @a collectable to be returned)
* @param collectable corresponding collectable coin (blind signature) * @param collectable corresponding collectable coin (blind signature)
* if a coin is found * if a coin is found
* @return statement execution status * @return statement execution status
@ -4313,12 +4307,12 @@ postgres_reserves_in_insert (void *cls,
static enum GNUNET_DB_QueryStatus static enum GNUNET_DB_QueryStatus
postgres_get_withdraw_info ( postgres_get_withdraw_info (
void *cls, void *cls,
const struct TALER_BlindedCoinHash *h_blind, const struct TALER_WithdrawIdentificationHash *wih,
struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable) struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = { 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 GNUNET_PQ_query_param_end
}; };
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
@ -4330,24 +4324,15 @@ postgres_get_withdraw_info (
&collectable->reserve_sig), &collectable->reserve_sig),
GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
&collectable->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", TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
&collectable->amount_with_fee), &collectable->amount_with_fee),
TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw", TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
&collectable->withdraw_fee), &collectable->withdraw_fee),
GNUNET_PQ_result_spec_end 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, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"get_withdraw_info", "get_withdraw_info",
params, params,
@ -4360,8 +4345,8 @@ postgres_get_withdraw_info (
* and possibly persisting the withdrawal details. * and possibly persisting the withdrawal details.
* *
* @param cls the `struct PostgresClosure` with the plugin-specific state * @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
* if a coin is found * @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 now current time (rounded)
* @param[out] found set to true if the reserve was found * @param[out] found set to true if the reserve was found
* @param[out] balance_ok set to true if the balance was sufficient * @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 static enum GNUNET_DB_QueryStatus
postgres_do_withdraw ( postgres_do_withdraw (
void *cls, void *cls,
const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, const struct TALER_WithdrawIdentificationHash *wih,
struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
struct GNUNET_TIME_Timestamp now, struct GNUNET_TIME_Timestamp now,
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
@ -4382,6 +4368,7 @@ postgres_do_withdraw (
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Timestamp gc; struct GNUNET_TIME_Timestamp gc;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (wih),
TALER_PQ_query_param_amount (&collectable->amount_with_fee), 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->denom_pub_hash),
GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub), 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_timestamp (&gc),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
enum GNUNET_DB_QueryStatus qs;
bool no_out_sig;
struct TALER_BlindedDenominationSignature out_sig;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_bool ("reserve_found", GNUNET_PQ_result_spec_bool ("reserve_found",
found), found),
@ -4403,18 +4393,33 @@ postgres_do_withdraw (
&kyc->payment_target_uuid), &kyc->payment_target_uuid),
GNUNET_PQ_result_spec_uint64 ("ruuid", GNUNET_PQ_result_spec_uint64 ("ruuid",
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 GNUNET_PQ_result_spec_end
}; };
#if 0
memset (&out_sig,
0,
sizeof (out_sig));
#endif
gc = GNUNET_TIME_absolute_to_timestamp ( gc = GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_absolute_add (now.abs_time, GNUNET_TIME_absolute_add (now.abs_time,
pg->legal_reserve_expiration_time)); pg->legal_reserve_expiration_time));
kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW; kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_withdraw", "call_withdraw",
params, params,
rs); 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. * from given the hash of the blinded coin.
* *
* @param cls closure * @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_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 * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in reserves_out
* @return transaction status code * @return transaction status code
*/ */
static enum GNUNET_DB_QueryStatus static enum GNUNET_DB_QueryStatus
postgres_get_reserve_by_h_blind (void *cls, postgres_get_reserve_by_wih (
const struct TALER_BlindedCoinHash *h_blind_ev, void *cls,
struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_WithdrawIdentificationHash *wih,
uint64_t *reserve_out_serial_id) struct TALER_ReservePublicKeyP *reserve_pub,
uint64_t *reserve_out_serial_id)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = { 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 GNUNET_PQ_query_param_end
}; };
struct GNUNET_PQ_ResultSpec rs[] = { 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, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"reserve_by_h_blind", "reserve_by_wih",
params, params,
rs); rs);
} }
@ -11663,8 +11669,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &postgres_select_recoup_refresh_above_serial_id; = &postgres_select_recoup_refresh_above_serial_id;
plugin->select_reserve_closed_above_serial_id plugin->select_reserve_closed_above_serial_id
= &postgres_select_reserve_closed_above_serial_id; = &postgres_select_reserve_closed_above_serial_id;
plugin->get_reserve_by_h_blind plugin->get_reserve_by_wih
= &postgres_get_reserve_by_h_blind; = &postgres_get_reserve_by_wih;
plugin->get_old_coin_by_h_blind plugin->get_old_coin_by_h_blind
= &postgres_get_old_coin_by_h_blind; = &postgres_get_old_coin_by_h_blind;
plugin->insert_denomination_revocation plugin->insert_denomination_revocation

View File

@ -1346,6 +1346,7 @@ run (void *cls)
struct GNUNET_TIME_Timestamp now; struct GNUNET_TIME_Timestamp now;
struct TALER_WireSaltP salt; struct TALER_WireSaltP salt;
struct TALER_CoinPubHash c_hash; struct TALER_CoinPubHash c_hash;
struct TALER_WithdrawIdentificationHash wih;
uint64_t known_coin_id; uint64_t known_coin_id;
uint64_t rrc_serial; uint64_t rrc_serial;
struct TALER_EXCHANGEDB_Refresh refresh; struct TALER_EXCHANGEDB_Refresh refresh;
@ -1383,7 +1384,7 @@ run (void *cls)
plugin->create_tables (plugin->cls)) plugin->create_tables (plugin->cls))
{ {
result = 77; result = 77;
goto drop; goto cleanup;
} }
plugin->preflight (plugin->cls); plugin->preflight (plugin->cls);
FAILIF (GNUNET_OK != FAILIF (GNUNET_OK !=
@ -1499,9 +1500,14 @@ run (void *cls)
&cbc.denom_pub_hash, &cbc.denom_pub_hash,
&cbc.h_coin_envelope)); &cbc.h_coin_envelope));
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_denom_sign_blinded (&cbc.sig, TALER_withdraw_request_hash (&pd.blinded_planchet,
&dkp->priv, &cbc.denom_pub_hash,
&pd.blinded_planchet)); &wih)); GNUNET_assert (
GNUNET_OK ==
TALER_denom_sign_blinded (
&cbc.sig,
&dkp->priv,
&pd.blinded_planchet));
TALER_blinded_planchet_free (&pd.blinded_planchet); TALER_blinded_planchet_free (&pd.blinded_planchet);
} }
} }
@ -1511,6 +1517,7 @@ run (void *cls)
GNUNET_assert (GNUNET_OK == GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (CURRENCY, TALER_amount_set_zero (CURRENCY,
&cbc.withdraw_fee)); &cbc.withdraw_fee));
{ {
bool found; bool found;
bool balance_ok; bool balance_ok;
@ -1519,6 +1526,7 @@ run (void *cls)
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->do_withdraw (plugin->cls, plugin->do_withdraw (plugin->cls,
&wih,
&cbc, &cbc,
now, now,
&found, &found,
@ -1540,16 +1548,16 @@ run (void *cls)
value.fraction, value.fraction,
value.currency)); value.currency));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_reserve_by_h_blind (plugin->cls, plugin->get_reserve_by_wih (plugin->cls,
&cbc.h_coin_envelope, &wih,
&reserve_pub3, &reserve_pub3,
&reserve_out_serial_id)); &reserve_out_serial_id));
FAILIF (0 != GNUNET_memcmp (&reserve_pub, FAILIF (0 != GNUNET_memcmp (&reserve_pub,
&reserve_pub3)); &reserve_pub3));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_withdraw_info (plugin->cls, plugin->get_withdraw_info (plugin->cls,
&cbc.h_coin_envelope, &wih,
&cbc2)); &cbc2));
FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig, FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig,
&cbc.reserve_sig)); &cbc.reserve_sig));
@ -2400,6 +2408,7 @@ drop:
rh = NULL; rh = NULL;
GNUNET_break (GNUNET_OK == GNUNET_break (GNUNET_OK ==
plugin->drop_tables (plugin->cls)); plugin->drop_tables (plugin->cls));
cleanup:
if (NULL != dkp) if (NULL != dkp)
destroy_denom_key_pair (dkp); destroy_denom_key_pair (dkp);
if (NULL != revealed_coins) if (NULL != revealed_coins)

View File

@ -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 * Hash used to represent the hash of the public
* key of a coin (without blinding). * key of a coin (without blinding).
@ -1308,6 +1324,22 @@ TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
struct TALER_BlindedCoinHash *bch); 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. * Compute the hash of a coin.
* *

View File

@ -2476,20 +2476,19 @@ struct TALER_EXCHANGEDB_Plugin
/** /**
* Locate the response for a withdraw request under the * Locate the response for a withdraw request under a hash that uniquely
* key of the hash of the blinded message. Used to ensure * identifies the withdraw operation. Used to ensure idempotency of the
* idempotency of the request. * request.
* *
* @param cls the @e cls of this struct with the plugin-specific state * @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 * @param wih hash that uniquely identifies the withdraw operation
* `h_coin_envelope` in the @a collectable to be returned) * @param[out] collectable corresponding collectable coin (blind signature)
* @param collectable corresponding collectable coin (blind signature)
* if a coin is found * if a coin is found
* @return statement execution status * @return statement execution status
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*get_withdraw_info)(void *cls, (*get_withdraw_info)(void *cls,
const struct TALER_BlindedCoinHash *h_blind, const struct TALER_WithdrawIdentificationHash *wih,
struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable); struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
@ -2498,7 +2497,8 @@ struct TALER_EXCHANGEDB_Plugin
* and possibly persisting the withdrawal details. * and possibly persisting the withdrawal details.
* *
* @param cls the `struct PostgresClosure` with the plugin-specific state * @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 * if a coin is found
* @param now current time (rounded) * @param now current time (rounded)
* @param[out] found set to true if the reserve was found * @param[out] found set to true if the reserve was found
@ -2510,7 +2510,8 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*do_withdraw)( (*do_withdraw)(
void *cls, void *cls,
const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, const struct TALER_WithdrawIdentificationHash *wih,
struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
struct GNUNET_TIME_Timestamp now, struct GNUNET_TIME_Timestamp now,
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
@ -3517,16 +3518,16 @@ struct TALER_EXCHANGEDB_Plugin
* from given the hash of the blinded coin. * from given the hash of the blinded coin.
* *
* @param cls closure * @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_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 * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in reserves_out
* @return transaction status code * @return transaction status code
*/ */
enum GNUNET_DB_QueryStatus enum GNUNET_DB_QueryStatus
(*get_reserve_by_h_blind)(void *cls, (*get_reserve_by_wih)(void *cls,
const struct TALER_BlindedCoinHash *h_blind_ev, const struct TALER_WithdrawIdentificationHash *wih,
struct TALER_ReservePublicKeyP *reserve_pub, struct TALER_ReservePublicKeyP *reserve_pub,
uint64_t *reserve_out_serial_id); uint64_t *reserve_out_serial_id);
/** /**

View File

@ -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 */ /* end of denom.c */