refactor: Use uint64_t over size_t for serialized-size values
What changed, and why it matters
This is a code cleanup change that swaps the variable type `size_t` for `uint64_t` in a handful of places that store serialized data sizes. On 64-bit computers the two types are the same size, so nothing changes. On 32-bit computers, `uint64_t` is larger and could in theory prevent a rare overflow if a serialized object ever exceeded about 4 GB. The commit message explicitly calls this a defensive refactor and says the practical risk is theoretical and unexpected. There is no reported bug, exploit, or security incident tied to this change.
Treat as a low-risk defensive refactor. No urgent action is required. Reviewers may verify that no implicit narrowing conversions were introduced and that downstream consumers of the changed variables still compile cleanly on 32-bit and 64-bit targets. Consider whether any callers still store the result in a `size_t`, which could reintroduce truncation.
Security signals we found
Defensive type widening from size_t to uint64_t for serialized-size values
Potential 32-bit integer overflow mitigation in serialized-size arithmetic
No demonstrated exploit or active vulnerability
No change to serialization format, consensus rules, or network behavior
Commit message describes the security relevance as theoretical and unexpected
Evidence from the diff
The commit refactors serialized-size variables from size_t to uint64_t across nine files. GetSerializeSize returns a size_t, so on 64-bit platforms the refactor is a no-op. On 32-bit platforms, size_t is 32 bits and uint64_t is 64 bits, so the change widens the type used for downstream arithmetic and comparisons. The affected sites include block-filter index file-size checks, dust-threshold calculation, getblockstats UTXO size accounting, miner test package-size calculations, wallet recipient serialization size, and a few test/fuzz sites. The change is purely defensive; no overflow vulnerability is demonstrated, and the commit message frames it as avoiding the need to reason about architecture-specific limits.
Changed components
src/coins.cppsrc/index/blockfilterindex.cppsrc/policy/policy.cppsrc/rpc/blockchain.cppsrc/test/flatfile_tests.cppsrc/test/fuzz/miniscript.cppsrc/test/miner_tests.cppsrc/test/miniscript_tests.cppsrc/wallet/spend.cppInspect captured patch +13 / −13
diff --git a/src/coins.cpp b/src/coins.cpp
index 090d36dd..554a3ebe 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -349,8 +349,8 @@ void CCoinsViewCache::SanityCheck() const
assert(recomputed_usage == cachedCoinsUsage);
}
-static const size_t MIN_TRANSACTION_OUTPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut());
-static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT;
+static const uint64_t MIN_TRANSACTION_OUTPUT_WEIGHT{WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut())};
+static const uint64_t MAX_OUTPUTS_PER_BLOCK{MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT};
const Coin& AccessByTxid(const CCoinsViewCache& view, const Txid& txid)
{
diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp
index 2ccae3a2..a6c63e5b 100644
--- a/src/index/blockfilterindex.cpp
+++ b/src/index/blockfilterindex.cpp
@@ -202,9 +202,9 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter&
{
assert(filter.GetFilterType() == GetFilterType());
- size_t data_size =
+ uint64_t data_size{
GetSerializeSize(filter.GetBlockHash()) +
- GetSerializeSize(filter.GetEncodedFilter());
+ GetSerializeSize(filter.GetEncodedFilter())};
// If writing the filter would overflow the file, flush and move to the next one.
if (pos.nPos + data_size > MAX_FLTR_FILE_SIZE) {
diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp
index 3da6cb74..e749b14f 100644
--- a/src/policy/policy.cpp
+++ b/src/policy/policy.cpp
@@ -42,7 +42,7 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
if (txout.scriptPubKey.IsUnspendable())
return 0;
- size_t nSize = GetSerializeSize(txout);
+ uint64_t nSize{GetSerializeSize(txout)};
int witnessversion = 0;
std::vector<unsigned char> witnessprogram;
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 831bf5fe..4ef7bc96 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -2033,7 +2033,7 @@ static RPCHelpMan getblockstats()
for (const CTxOut& out : tx->vout) {
tx_total_out += out.nValue;
- size_t out_size = GetSerializeSize(out) + PER_UTXO_OVERHEAD;
+ uint64_t out_size{GetSerializeSize(out) + PER_UTXO_OVERHEAD};
utxo_size_inc += out_size;
// The Genesis block and the repeated BIP30 block coinbases don't change the UTXO
@@ -2085,7 +2085,7 @@ static RPCHelpMan getblockstats()
const CTxOut& prevoutput = coin.out;
tx_total_in += prevoutput.nValue;
- size_t prevout_size = GetSerializeSize(prevoutput) + PER_UTXO_OVERHEAD;
+ uint64_t prevout_size{GetSerializeSize(prevoutput) + PER_UTXO_OVERHEAD};
utxo_size_inc -= prevout_size;
utxo_size_inc_actual -= prevout_size;
}
diff --git a/src/test/flatfile_tests.cpp b/src/test/flatfile_tests.cpp
index d94cab64..5ef603ca 100644
--- a/src/test/flatfile_tests.cpp
+++ b/src/test/flatfile_tests.cpp
@@ -39,8 +39,8 @@ BOOST_AUTO_TEST_CASE(flatfile_open)
std::string line2("Digital signatures provide part of the solution, but the main benefits are "
"lost if a trusted third party is still required to prevent double-spending.");
- size_t pos1 = 0;
- size_t pos2 = pos1 + GetSerializeSize(line1);
+ uint64_t pos1{0};
+ uint64_t pos2{pos1 + GetSerializeSize(line1)};
// Write first line to file.
{
diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp
index aea31c6f..42195313 100644
--- a/src/test/fuzz/miniscript.cpp
+++ b/src/test/fuzz/miniscript.cpp
@@ -1120,7 +1120,7 @@ void TestNode(const MsCtx script_ctx, const NodeRef& node, FuzzedDataProvider& p
assert(mal_success);
assert(stack_nonmal == stack_mal);
// Compute witness size (excluding script push, control block, and witness count encoding).
- const size_t wit_size = GetSerializeSize(stack_nonmal) - GetSizeOfCompactSize(stack_nonmal.size());
+ const uint64_t wit_size{GetSerializeSize(stack_nonmal) - GetSizeOfCompactSize(stack_nonmal.size())};
assert(wit_size <= *node->GetWitnessSize());
// Test non-malleable satisfaction.
diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp
index 94ade598..a3c576dd 100644
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -188,7 +188,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee
Txid hashFreeTx = tx.GetHash();
AddToMempool(tx_mempool, entry.Fee(0).FromTx(tx));
- size_t freeTxSize = ::GetSerializeSize(TX_WITH_WITNESS(tx));
+ uint64_t freeTxSize{::GetSerializeSize(TX_WITH_WITNESS(tx))};
// Calculate a fee on child transaction that will put the package just
// below the block min tx fee (assuming 1 child tx of the same size).
diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp
index 0a32727f..4d31968c 100644
--- a/src/test/miniscript_tests.cpp
+++ b/src/test/miniscript_tests.cpp
@@ -372,7 +372,7 @@ void TestSatisfy(const KeyConverter& converter, const std::string& testcase, con
CScriptWitness witness_nonmal;
const bool nonmal_success = node->Satisfy(satisfier, witness_nonmal.stack, true) == miniscript::Availability::YES;
// Compute witness size (excluding script push, control block, and witness count encoding).
- const size_t wit_size = GetSerializeSize(witness_nonmal.stack) - GetSizeOfCompactSize(witness_nonmal.stack.size());
+ const uint64_t wit_size{GetSerializeSize(witness_nonmal.stack) - GetSizeOfCompactSize(witness_nonmal.stack.size())};
SatisfactionToWitness(converter.MsContext(), witness_nonmal, script, builder);
if (nonmal_success) {
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 146fb49e..bd7bb01f 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -1034,7 +1034,7 @@ void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
}
}
-size_t GetSerializeSizeForRecipient(const CRecipient& recipient)
+uint64_t GetSerializeSizeForRecipient(const CRecipient& recipient)
{
return ::GetSerializeSize(CTxOut(recipient.nAmount, GetScriptForDestination(recipient.dest)));
}
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.