cryptonote_basic: cache prunable hash for pruned txs
What changed, and why it matters
This commit adds a small internal optimization to Monero's transaction handling: when a transaction is pruned (stripped of some data to save space), the hash of the removed 'prunable' part is now saved inside the transaction object. This avoids having to recompute or re-supply that hash later. The change is accompanied by a unit test that verifies the cached hash matches the independently computed one. There is no direct security vulnerability visible in the diff; it appears to be a correctness/performance improvement for pruned transaction validation.
Treat as a routine correctness/optimization commit. Review the set_prunable_hash implementation and cache invalidation logic in the transaction class to ensure the cached value is cleared when the transaction object is mutated, but no immediate security action is indicated.
Security signals we found
No input validation changes
No cryptographic algorithm changes
No privilege or authorization changes
No network-exposed surface changes
Adds unit test coverage for pruned transaction hash reconstruction
Caches an existing computed hash rather than changing how it is computed
Evidence from the diff
In cryptonote_format_utils.cpp, get_pruned_transaction_hash() now calls t.set_prunable_hash(pruned_data_hash) when the ringCT type is not RCTTypeNull, caching the prunable hash in the transaction object. A unit test loads a real transaction blob, computes its full and prunable hashes, prunes the transaction, reconstructs the full hash from the pruned transaction plus the prunable hash, and asserts that the cached prunable hash is valid and matches a fresh computation. The change is additive and test-only; no existing behavior is removed or weakened.
Changed components
src/cryptonote_basic/cryptonote_format_utils.cpptests/unit_tests/cryptonote_format_utils.cppInspect captured patch +29 / −0
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index 8cad702..725f4a4 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -1508,7 +1508,10 @@ namespace cryptonote
if (t.rct_signatures.type == rct::RCTTypeNull)
hashes[2] = crypto::null_hash;
else
+ {
hashes[2] = pruned_data_hash;
+ t.set_prunable_hash(pruned_data_hash);
+ }
// the tx hash is the hash of the 3 hashes
res = cn_fast_hash(hashes, sizeof(hashes));
diff --git a/tests/unit_tests/cryptonote_format_utils.cpp b/tests/unit_tests/cryptonote_format_utils.cpp
index 8e41c0e..201521e 100644
--- a/tests/unit_tests/cryptonote_format_utils.cpp
+++ b/tests/unit_tests/cryptonote_format_utils.cpp
@@ -169,6 +169,32 @@ TEST(cn_format_utils, payment_id_tx_extra_nonce_rejects_wrong_tag_or_size)
EXPECT_FALSE(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(wrong_size, parsed_encrypted_payment_id));
}
+TEST(cn_format_utils, pruned_transaction_hash_caches_prunable_hash)
+{
+ const auto tx_path = unit_test::data_dir / "txs" / "bpp_tx_e89415.bin";
+ cryptonote::blobdata tx_blob;
+ ASSERT_TRUE(epee::file_io_utils::load_file_to_string(tx_path.string(), tx_blob));
+
+ cryptonote::transaction tx;
+ ASSERT_TRUE(cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx));
+ const cryptonote::blobdata_ref tx_blob_ref{tx_blob};
+ const crypto::hash tx_hash = cryptonote::get_transaction_hash(tx);
+ const crypto::hash prunable_hash = cryptonote::get_transaction_prunable_hash(tx, &tx_blob_ref);
+
+ cryptonote::blobdata pruned_tx_blob = tx_blob;
+ ASSERT_TRUE(cryptonote::prune_transaction_blob(pruned_tx_blob));
+
+ cryptonote::transaction pruned_tx;
+ ASSERT_TRUE(cryptonote::parse_and_validate_tx_base_from_blob(pruned_tx_blob, pruned_tx));
+ ASSERT_NE(rct::RCTTypeNull, pruned_tx.rct_signatures.type);
+ crypto::hash reconstructed_hash;
+ ASSERT_TRUE(cryptonote::get_pruned_transaction_hash(pruned_tx, prunable_hash, reconstructed_hash));
+ EXPECT_EQ(tx_hash, reconstructed_hash);
+ ASSERT_TRUE(pruned_tx.is_prunable_hash_valid());
+ const cryptonote::blobdata_ref pruned_tx_blob_ref{pruned_tx_blob};
+ EXPECT_EQ(prunable_hash, cryptonote::get_transaction_prunable_hash(pruned_tx, &pruned_tx_blob_ref));
+}
+
TEST(cn_format_utils, block_longhash_202612_arbitrary_blob)
{
const cryptonote::blobdata blob = "not the historical block 202612 hashing blob";
Why this scored 19/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.