Skip to content

Commit eb92a9a

Browse files
committed
wallet: prevent backdating locktime too far
1 parent 79e8247 commit eb92a9a

File tree

2 files changed

+24
-3
lines changed

2 files changed

+24
-3
lines changed

src/wallet/feebumper.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,10 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
308308
// Write back transaction
309309
mtx = CMutableTransaction(*txr.tx);
310310

311+
// Set the locktime of the new transaction to the same
312+
// locktime as the transaction being replaced
313+
mtx.nLockTime = wtx.tx->nLockTime;
314+
311315
return Result::OK;
312316
}
313317

src/wallet/spend.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,8 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256&
764764
* current chain tip unless we are not synced with the current chain
765765
*/
766766
static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
767-
interfaces::Chain& chain, const uint256& block_hash, int block_height)
767+
interfaces::Chain& chain, const uint256& block_hash, int block_height,
768+
unsigned int min_locktime)
768769
{
769770
// All inputs must be added by now
770771
assert(!tx.vin.empty());
@@ -796,8 +797,11 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng
796797
// e.g. high-latency mix networks and some CoinJoin implementations, have
797798
// better privacy.
798799
if (rng_fast.randrange(10) == 0) {
799-
tx.nLockTime = std::max(0, int(tx.nLockTime) - int(rng_fast.randrange(100)));
800+
// Ensure that the back-dated timelock does not go beneath the min_timelock
801+
int locktime_range = std::min(100, int(block_height - min_locktime));
802+
if (locktime_range > 0) tx.nLockTime = std::max(int(min_locktime), int(tx.nLockTime) - int(rng_fast.randrange(locktime_range)));
800803
}
804+
assert(tx.nLockTime >= min_locktime);
801805
} else {
802806
// If our chain is lagging behind, we can't discourage fee sniping nor help
803807
// the privacy of high-latency transactions. To avoid leaking a potentially
@@ -1011,7 +1015,20 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
10111015
for (const auto& coin : selected_coins) {
10121016
txNew.vin.push_back(CTxIn(coin->outpoint, CScript(), nSequence));
10131017
}
1014-
DiscourageFeeSniping(txNew, rng_fast, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight());
1018+
1019+
// Figure out the highest locktime of all of the unconfirmed inputs
1020+
// so that the nLockTime for this transaction does not go beneath that.
1021+
// If the nLockTime goes beneath any of the previous transactions'
1022+
// locktimes, that can be a wallet fingerprint.
1023+
std::set<unsigned int> input_tx_locktimes{0}; // If there aren't any unconfirmed inputs, the minimum locktime is 0
1024+
for (const auto& coin: selected_coins) {
1025+
if (coin->depth == 0) {
1026+
const CWalletTx* coin_wtx{wallet.GetWalletTx(coin->outpoint.hash)};
1027+
if (coin_wtx) input_tx_locktimes.insert(coin_wtx->tx->nLockTime);
1028+
}
1029+
}
1030+
1031+
DiscourageFeeSniping(txNew, rng_fast, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight(), /*min_locktime=*/*input_tx_locktimes.rbegin());
10151032

10161033
// Calculate the transaction fee
10171034
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);

0 commit comments

Comments
 (0)