From a351bfc4b4ca15ce7fd998cf9691e85cf84dc426 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 17 Feb 2022 15:10:14 +0100 Subject: -fix CS nonce reuse check logic --- src/exchangedb/exchange-0001.sql | 166 ++++++++++++++++++++++++++++++--------- 1 file changed, 130 insertions(+), 36 deletions(-) (limited to 'src/exchangedb/exchange-0001.sql') diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql index df07e0252..fdc47b7c9 100644 --- a/src/exchangedb/exchange-0001.sql +++ b/src/exchangedb/exchange-0001.sql @@ -1,6 +1,6 @@ -- -- This file is part of TALER --- Copyright (C) 2014--2021 Taler Systems SA +-- Copyright (C) 2014--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 @@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS denominations (denominations_serial BIGSERIAL UNIQUE ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64) ,denom_type INT4 NOT NULL DEFAULT (1) -- 1 == RSA (for now, remove default later!) - ,age_mask INT4 NOT NULL DEFAULT (0) + ,age_restrictions INT4 NOT NULL DEFAULT (0) ,denom_pub BYTEA NOT NULL ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) ,valid_from INT8 NOT NULL @@ -196,7 +196,6 @@ CREATE INDEX IF NOT EXISTS reserves_close_by_reserve_pub_index CREATE TABLE IF NOT EXISTS reserves_out (reserve_out_serial_id BIGSERIAL -- UNIQUE - ,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 @@ -206,11 +205,9 @@ CREATE TABLE IF NOT EXISTS reserves_out ,amount_with_fee_val INT8 NOT NULL ,amount_with_fee_frac INT4 NOT NULL ) - PARTITION BY HASH (wih); + PARTITION BY HASH (h_blind_ev); 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 @@ -643,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 wih of the recouped coin and provides the link to the credited reserve.'; + IS 'Identifies the h_blind_ev 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 @@ -750,6 +747,23 @@ COMMENT ON COLUMN wire_accounts.last_change -- and is of no concern to the auditor +CREATE TABLE IF NOT EXISTS cs_nonce_locks + (cs_nonce_lock_serial_id BIGSERIAL -- UNIQUE + ,nonce BYTEA PRIMARY KEY CHECK (LENGTH(nonce)=32) + ,op_hash BYTEA NOT NULL CHECK (LENGTH(op_hash)=64) + ,max_denomination_serial INT8 NOT NULL + ) + PARTITION BY HASH (nonce); +COMMENT ON TABLE cs_nonce_locks + IS 'ensures a Clause Schnorr client nonce is locked for use with an operation identified by a hash'; +COMMENT ON COLUMN cs_nonce_locks.nonce + IS 'actual nonce submitted by the client'; +COMMENT ON COLUMN cs_nonce_locks.op_hash + IS 'hash (RC for refresh, blind coin hash for withdraw) the nonce may be used with'; +COMMENT ON COLUMN cs_nonce_locks.max_denomination_serial + IS 'Maximum number of a CS denomination serial the nonce could be used with, for GC'; + + CREATE TABLE IF NOT EXISTS work_shards (shard_serial_id BIGSERIAL UNIQUE ,last_attempt INT8 NOT NULL @@ -818,7 +832,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 cs_nonce BYTEA, IN amount_val INT8, IN amount_frac INT4, IN h_denom_pub BYTEA, @@ -832,8 +846,7 @@ CREATE OR REPLACE FUNCTION exchange_do_withdraw( OUT balance_ok BOOLEAN, OUT kycok BOOLEAN, OUT account_uuid INT8, - OUT ruuid INT8, - OUT out_denom_sig BYTEA) + OUT ruuid INT8) LANGUAGE plpgsql AS $$ DECLARE @@ -851,7 +864,8 @@ BEGIN -- reserves_in by reserve_pub (SELECT) -- wire_targets by wire_target_serial_id -SELECT denominations_serial INTO denom_serial +SELECT denominations_serial + INTO denom_serial FROM denominations WHERE denom_pub_hash=h_denom_pub; @@ -867,6 +881,7 @@ THEN RETURN; END IF; + SELECT current_balance_val ,current_balance_frac @@ -887,7 +902,7 @@ THEN balance_ok=FALSE; kycok=FALSE; account_uuid=0; - ruuid=0; + ruuid=2; RETURN; END IF; @@ -895,7 +910,6 @@ END IF; -- the query successful due to idempotency. INSERT INTO reserves_out (h_blind_ev - ,wih ,denominations_serial ,denom_sig ,reserve_uuid @@ -905,7 +919,6 @@ INSERT INTO reserves_out ,amount_with_fee_frac) VALUES (h_coin_envelope - ,in_wih ,denom_serial ,denom_sig ,ruuid @@ -918,25 +931,6 @@ 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; @@ -983,6 +977,44 @@ WHERE reserve_found=TRUE; balance_ok=TRUE; + + +-- Special actions needed for a CS withdraw? +IF NOT NULL cs_nonce +THEN + -- Cache CS signature to prevent replays in the future + -- (and check if cached signature exists at the same time). + INSERT INTO cs_nonce_locks + (nonce + ,max_denomination_serial + ,op_hash) + VALUES + (cs_nonce + ,denom_serial + ,h_coin_envelope) + ON CONFLICT DO NOTHING; + + IF NOT FOUND + THEN + -- See if the existing entry is identical. + SELECT 1 + FROM cs_nonce_locks + WHERE nonce=cs_nonce + AND op_hash=h_coin_envelope; + IF NOT FOUND + THEN + reserve_found=FALSE; + balance_ok=FALSE; + kycok=FALSE; + account_uuid=0; + ruuid=1; -- FIXME: return error message more nicely! + ASSERT false, 'nonce reuse attempted by client'; + END IF; + END IF; +END IF; + + + -- Obtain KYC status based on the last wire transfer into -- this reserve. FIXME: likely not adequate for reserves that got P2P transfers! SELECT @@ -996,9 +1028,6 @@ 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 $$; @@ -1223,6 +1252,7 @@ END $$; CREATE OR REPLACE FUNCTION exchange_do_melt( + IN in_cs_rms BYTEA, IN in_amount_with_fee_val INT8, IN in_amount_with_fee_frac INT4, IN in_rc BYTEA, @@ -1236,6 +1266,8 @@ CREATE OR REPLACE FUNCTION exchange_do_melt( OUT out_noreveal_index INT4) LANGUAGE plpgsql AS $$ +DECLARE + denom_max INT8; BEGIN -- Shards: INSERT refresh_commitments (by rc) -- (rare:) SELECT refresh_commitments (by old_coin_pub) -- crosses shards! @@ -1333,6 +1365,56 @@ THEN RETURN; END IF; + + +-- Special actions needed for a CS melt? +IF NOT NULL in_cs_rms +THEN + -- Get maximum denominations serial value in + -- existence, this will determine how long the + -- nonce will be locked. + SELECT + denominations_serial + INTO + denom_max + FROM denominations + ORDER BY denominations_serial DESC + LIMIT 1; + + -- Cache CS signature to prevent replays in the future + -- (and check if cached signature exists at the same time). + INSERT INTO cs_nonce_locks + (nonce + ,max_denomination_serial + ,op_hash) + VALUES + (cs_rms + ,denom_serial + ,in_rc) + ON CONFLICT DO NOTHING; + + IF NOT FOUND + THEN + -- Record exists, make sure it is the same + SELECT 1 + FROM cs_nonce_locks + WHERE nonce=cs_rms + AND op_hash=in_rc; + + IF NOT FOUND + THEN + -- Nonce reuse detected + out_balance_ok=FALSE; + out_zombie_bad=FALSE; + out_noreveal_index=42; -- FIXME: return error message more nicely! + ASSERT false, 'nonce reuse attempted by client'; + END IF; + END IF; +END IF; + + + + -- Everything fine, return success! out_balance_ok=TRUE; out_noreveal_index=in_noreveal_index; @@ -1806,6 +1888,8 @@ DECLARE deposit_min INT8; -- minimum deposit still alive DECLARE reserve_out_min INT8; -- minimum reserve_out still alive +DECLARE + denom_min INT8; -- minimum denomination still alive BEGIN DELETE FROM prewire @@ -1901,6 +1985,16 @@ DELETE FROM refunds DELETE FROM aggregation_tracking WHERE deposit_serial_id < deposit_min; +SELECT + denominations_serial + INTO + denom_min + FROM denominations + ORDER BY denominations_serial ASC + LIMIT 1; + +DELETE FROM cs_nonce_locks + WHERE max_denomination_serial <= denom_min; END $$; -- cgit v1.2.3