Skip to content

Commit 720238c

Browse files
committed
Merge #1511: Introduce per-txin sighash midstate cache for legacy/p2sh/segwitv0 scripts
eb64d24 Merge bitcoin/bitcoin#32473: Introduce per-txin sighash midstate cache for legacy/p2sh/segwitv0 scripts (merge-script) Pull request description: Backport of bitcoin/bitcoin#32473 Additional cache index bit added for sighash combination with `SIGHASH_RANGEPROOF` ACKs for top commit: delta1: ACK eb64d24 Tree-SHA512: 94014e726ff16473809672f3e01a8e4d11c4abd38b3ae3ae64b8b9ae61be2e3c246157dca5a3a3afc4b85cc8ec69e23181af8f11c36e0b43cfac2dcb3e2e8d70
2 parents 6664587 + eb64d24 commit 720238c

File tree

6 files changed

+311
-34
lines changed

6 files changed

+311
-34
lines changed

src/script/interpreter.cpp

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2822,11 +2822,59 @@ bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, cons
28222822
return true;
28232823
}
28242824

2825+
int SigHashCache::CacheIndex(int32_t hash_type) const noexcept
2826+
{
2827+
// Note that we do not distinguish between BASE and WITNESS_V0 to determine the cache index,
2828+
// because no input can simultaneously use both.
2829+
return 8 * !!(hash_type & SIGHASH_RANGEPROOF) + // bit 3
2830+
3 * !!(hash_type & SIGHASH_ANYONECANPAY) + // bit 2
2831+
2 * ((hash_type & 0x1f) == SIGHASH_SINGLE) + // bit 1
2832+
1 * ((hash_type & 0x1f) == SIGHASH_NONE); // bit 0
2833+
}
2834+
2835+
bool SigHashCache::Load(int32_t hash_type, const CScript& script_code, CHashWriter& writer) const noexcept
2836+
{
2837+
auto& entry = m_cache_entries[CacheIndex(hash_type)];
2838+
if (entry.has_value()) {
2839+
if (script_code == entry->first) {
2840+
writer.~CHashWriter();
2841+
new (&writer) CHashWriter(entry->second);
2842+
return true;
2843+
}
2844+
}
2845+
return false;
2846+
}
2847+
2848+
void SigHashCache::Store(int32_t hash_type, const CScript& script_code, const CHashWriter& writer) noexcept
2849+
{
2850+
auto& entry = m_cache_entries[CacheIndex(hash_type)];
2851+
entry.emplace(script_code, writer);
2852+
}
2853+
28252854
template <class T>
2826-
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, unsigned int flags, const PrecomputedTransactionData* cache)
2855+
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, unsigned int flags, const PrecomputedTransactionData* cache, SigHashCache* sighash_cache)
28272856
{
28282857
assert(nIn < txTo.vin.size());
28292858

2859+
if (sigversion != SigVersion::WITNESS_V0) {
2860+
// Check for invalid use of SIGHASH_SINGLE
2861+
if ((nHashType & 0x1f) == SIGHASH_SINGLE) {
2862+
if (nIn >= txTo.vout.size()) {
2863+
// nOut out of range
2864+
return uint256::ONE;
2865+
}
2866+
}
2867+
}
2868+
2869+
CHashWriter ss(SER_GETHASH, 0);
2870+
2871+
// Try to compute using cached SHA256 midstate.
2872+
if (sighash_cache && sighash_cache->Load(nHashType, scriptCode, ss)) {
2873+
// Add sighash type and hash.
2874+
ss << nHashType;
2875+
return ss.GetHash();
2876+
}
2877+
28302878
if (sigversion == SigVersion::WITNESS_V0) {
28312879
uint256 hashPrevouts;
28322880
uint256 hashSequence;
@@ -2855,24 +2903,23 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
28552903
hashRangeproofs = cacheready ? cache->hashRangeproofs : GetRangeproofsHash(txTo);
28562904
}
28572905
} else if ((nHashType & 0x1f) == SIGHASH_SINGLE && nIn < txTo.vout.size()) {
2858-
CHashWriter ss(SER_GETHASH, 0);
2859-
ss << txTo.vout[nIn];
2860-
hashOutputs = ss.GetHash();
2906+
CHashWriter inner_ss(SER_GETHASH, 0);
2907+
inner_ss << txTo.vout[nIn];
2908+
hashOutputs = inner_ss.GetHash();
28612909

28622910
if (fRangeproof) {
2863-
CHashWriter ss(SER_GETHASH, 0);
2911+
CHashWriter inner_ss(SER_GETHASH, 0);
28642912
if (nIn < txTo.witness.vtxoutwit.size()) {
2865-
ss << txTo.witness.vtxoutwit[nIn].vchRangeproof;
2866-
ss << txTo.witness.vtxoutwit[nIn].vchSurjectionproof;
2913+
inner_ss << txTo.witness.vtxoutwit[nIn].vchRangeproof;
2914+
inner_ss << txTo.witness.vtxoutwit[nIn].vchSurjectionproof;
28672915
} else {
2868-
ss << (unsigned char) 0;
2869-
ss << (unsigned char) 0;
2916+
inner_ss << (unsigned char) 0;
2917+
inner_ss << (unsigned char) 0;
28702918
}
2871-
hashRangeproofs = ss.GetHash();
2919+
hashRangeproofs = inner_ss.GetHash();
28722920
}
28732921
}
28742922

2875-
CHashWriter ss(SER_GETHASH, 0);
28762923
// Version
28772924
ss << txTo.nVersion;
28782925
// Input prevouts/nSequence (none/all, depending on flags)
@@ -2905,26 +2952,19 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
29052952
}
29062953
// Locktime
29072954
ss << txTo.nLockTime;
2908-
// Sighash type
2909-
ss << nHashType;
2910-
2911-
return ss.GetHash();
2955+
} else {
2956+
// Wrapper to serialize only the necessary parts of the transaction being signed
2957+
CTransactionSignatureSerializer<T> txTmp(txTo, scriptCode, nIn, nHashType, flags);
2958+
ss << txTmp;
29122959
}
29132960

2914-
// Check for invalid use of SIGHASH_SINGLE
2915-
if ((nHashType & 0x1f) == SIGHASH_SINGLE) {
2916-
if (nIn >= txTo.vout.size()) {
2917-
// nOut out of range
2918-
return uint256::ONE;
2919-
}
2961+
// If a cache object was provided, store the midstate there.
2962+
if (sighash_cache != nullptr) {
2963+
sighash_cache->Store(nHashType, scriptCode, ss);
29202964
}
29212965

2922-
// Wrapper to serialize only the necessary parts of the transaction being signed
2923-
CTransactionSignatureSerializer<T> txTmp(txTo, scriptCode, nIn, nHashType, flags);
2924-
29252966
// Serialize and hash
2926-
CHashWriter ss(SER_GETHASH, 0);
2927-
ss << txTmp << nHashType;
2967+
ss << nHashType;
29282968
return ss.GetHash();
29292969
}
29302970

@@ -2957,7 +2997,7 @@ bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vecto
29572997
// Witness sighashes need the amount.
29582998
if (sigversion == SigVersion::WITNESS_V0 && amount.IsNull()) return HandleMissingData(m_mdb);
29592999

2960-
uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, flags, this->txdata);
3000+
uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, flags, this->txdata, &m_sighash_cache);
29613001

29623002
if (!VerifyECDSASignature(vchSig, pubkey, sighash))
29633003
return false;

src/script/interpreter.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,27 @@ extern const CHashWriter HASHER_TAPSIGHASH_ELEMENTS; //!< Hasher with tag "TapSi
289289
extern const CHashWriter HASHER_TAPLEAF_ELEMENTS; //!< Hasher with tag "TapLeaf" pre-fed to it.
290290
extern const CHashWriter HASHER_TAPBRANCH_ELEMENTS; //!< Hasher with tag "TapBranch" pre-fed to it.
291291

292+
/** Data structure to cache SHA256 midstates for the ECDSA sighash calculations
293+
* (bare, P2SH, P2WPKH, P2WSH). */
294+
class SigHashCache
295+
{
296+
/** For each sighash mode (ALL, SINGLE, NONE, ALL|ANYONE, SINGLE|ANYONE, NONE|ANYONE, ALL|RANGEPROOF, SINGLE|RANGEPROOF, NONE|RANGEPROOF, ALL|ANYONE|RANGEPROOF, SINGLE|ANYONE|RANGEPROOF, NONE|ANYONE|RANGEPROOF),
297+
* optionally store a scriptCode which the hash is for, plus a midstate for the SHA256
298+
* computation just before adding the hash_type itself. */
299+
std::optional<std::pair<CScript, CHashWriter>> m_cache_entries[16];
300+
301+
/** Given a hash_type, find which of the cache entries is to be used. */
302+
int CacheIndex(int32_t hash_type) const noexcept;
303+
304+
public:
305+
/** Load into writer the SHA256 midstate if found in this cache. */
306+
[[nodiscard]] bool Load(int32_t hash_type, const CScript& script_code, CHashWriter& writer) const noexcept;
307+
/** Store into this cache object the provided SHA256 midstate. */
308+
void Store(int32_t hash_type, const CScript& script_code, const CHashWriter& writer) noexcept;
309+
};
310+
292311
template <class T>
293-
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, unsigned int flags, const PrecomputedTransactionData* cache = nullptr);
312+
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, unsigned int flags, const PrecomputedTransactionData* cache = nullptr, SigHashCache* sighash_cache = nullptr);
294313

295314
class BaseSignatureChecker
296315
{
@@ -374,6 +393,7 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker
374393
unsigned int nIn;
375394
const CConfidentialValue amount;
376395
const PrecomputedTransactionData* txdata;
396+
mutable SigHashCache m_sighash_cache;
377397

378398
protected:
379399
virtual bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;

src/test/fuzz/script_interpreter.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <test/fuzz/FuzzedDataProvider.h>
88
#include <test/fuzz/fuzz.h>
99
#include <test/fuzz/util.h>
10+
#include <util/check.h>
1011

1112
#include <cstdint>
1213
#include <optional>
@@ -39,3 +40,29 @@ FUZZ_TARGET(script_interpreter)
3940
(void)CastToBool(ConsumeRandomLengthByteVector(fuzzed_data_provider));
4041
}
4142
}
43+
44+
/** Differential fuzzing for SignatureHash with and without cache. */
45+
46+
FUZZ_TARGET(sighash_cache)
47+
{
48+
FuzzedDataProvider provider(buffer.data(), buffer.size());
49+
50+
// Get inputs to the sighash function that won't change across types.
51+
const auto scriptcode{ConsumeScript(provider)};
52+
const auto tx{ConsumeTransaction(provider, std::nullopt)};
53+
if (tx.vin.empty()) return;
54+
const auto in_index{provider.ConsumeIntegralInRange<uint32_t>(0, tx.vin.size() - 1)};
55+
const auto amount{ConsumeMoney(provider)};
56+
const auto sigversion{(SigVersion)provider.ConsumeIntegralInRange(0, 1)};
57+
58+
// Check the sighash function will give the same result for 100 fuzzer-generated hash types whether or not a cache is
59+
// provided. The cache is conserved across types to exercise cache hits.
60+
SigHashCache sighash_cache{};
61+
for (int i{0}; i < 100; ++i) {
62+
const auto hash_type{((i & 2) == 0) ? provider.ConsumeIntegral<int8_t>() : provider.ConsumeIntegral<int32_t>()};
63+
const auto nocache_res{SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, 0)};
64+
const auto cache_res{SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, 0, nullptr, &sighash_cache)};
65+
Assert(nocache_res == cache_res);
66+
}
67+
}
68+

src/test/sighash_tests.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,94 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
207207
BOOST_CHECK_MESSAGE(sh.GetHex() == sigHashHex, strTest);
208208
}
209209
}
210+
211+
BOOST_AUTO_TEST_CASE(sighash_caching)
212+
{
213+
// Get a script, transaction and parameters as inputs to the sighash function.
214+
CScript scriptcode;
215+
RandomScript(scriptcode);
216+
CScript diff_scriptcode{scriptcode};
217+
diff_scriptcode << OP_1;
218+
CMutableTransaction tx;
219+
RandomTransaction(tx,false);
220+
const auto in_index{static_cast<uint32_t>(InsecureRandRange(tx.vin.size()))};
221+
const auto amount{CConfidentialValue(CAmount(10))};
222+
223+
// Exercise the sighash function under both legacy and segwit v0.
224+
for (const auto sigversion: {SigVersion::BASE, SigVersion::WITNESS_V0}) {
225+
// For each, run it against all the 6 standard hash types and a few additional random ones.
226+
std::vector<int32_t> hash_types{{SIGHASH_ALL, SIGHASH_SINGLE, SIGHASH_NONE, SIGHASH_ALL | SIGHASH_ANYONECANPAY,
227+
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, SIGHASH_NONE | SIGHASH_ANYONECANPAY,
228+
SIGHASH_ANYONECANPAY, 0, std::numeric_limits<int32_t>::max()}};
229+
for (int i{0}; i < 10; ++i) {
230+
hash_types.push_back(i % 2 == 0 ? static_cast<int32_t>(InsecureRandRange(256)) : static_cast<int32_t>(g_insecure_rand_ctx.rand32()));
231+
}
232+
233+
// Reuse the same cache across script types. This must not cause any issue as the cached value for one hash type must never
234+
// be confused for another (instantiating the cache within the loop instead would prevent testing this).
235+
SigHashCache cache;
236+
for (const auto hash_type: hash_types) {
237+
const bool expect_one{sigversion == SigVersion::BASE && ((hash_type & 0x1f) == SIGHASH_SINGLE) && in_index >= tx.vout.size()};
238+
239+
// The result of computing the sighash should be the same with or without cache.
240+
const auto sighash_with_cache{SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0},nullptr, &cache)};
241+
const auto sighash_no_cache{SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0}, nullptr, nullptr)};
242+
BOOST_CHECK_EQUAL(sighash_with_cache, sighash_no_cache);
243+
244+
// Calling the cached version again should return the same value again.
245+
BOOST_CHECK_EQUAL(sighash_with_cache, SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0}, nullptr, &cache));
246+
247+
// While here we might as well also check that the result for legacy is the same as for the old SignatureHash() function.
248+
if (sigversion == SigVersion::BASE) {
249+
BOOST_CHECK_EQUAL(sighash_with_cache, SignatureHashOld(scriptcode, CTransaction(tx), in_index, hash_type));
250+
}
251+
252+
// Calling with a different scriptcode (for instance in case a CODESEP is encountered) will not return the cache value but
253+
// overwrite it. The sighash will always be different except in case of legacy SIGHASH_SINGLE bug.
254+
const auto sighash_with_cache2{SignatureHash(diff_scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0}, nullptr, &cache)};
255+
const auto sighash_no_cache2{SignatureHash(diff_scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0}, nullptr, nullptr)};
256+
BOOST_CHECK_EQUAL(sighash_with_cache2, sighash_no_cache2);
257+
if (!expect_one) {
258+
BOOST_CHECK_NE(sighash_with_cache, sighash_with_cache2);
259+
} else {
260+
BOOST_CHECK_EQUAL(sighash_with_cache, sighash_with_cache2);
261+
BOOST_CHECK_EQUAL(sighash_with_cache, uint256::ONE);
262+
}
263+
264+
// Calling the cached version again should return the same value again.
265+
BOOST_CHECK_EQUAL(sighash_with_cache2, SignatureHash(diff_scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0}, nullptr, &cache));
266+
267+
// And if we store a different value for this scriptcode and hash type it will return that instead.
268+
{
269+
CHashWriter h{SER_GETHASH, 0};
270+
h << 42;
271+
cache.Store(hash_type, scriptcode, h);
272+
const auto stored_hash{h.GetHash()};
273+
BOOST_CHECK(cache.Load(hash_type, scriptcode, h));
274+
const auto loaded_hash{h.GetHash()};
275+
BOOST_CHECK_EQUAL(stored_hash, loaded_hash);
276+
}
277+
278+
// And using this mutated cache with the sighash function will return the new value (except in the legacy SIGHASH_SINGLE bug
279+
// case in which it'll return 1).
280+
if (!expect_one) {
281+
BOOST_CHECK_NE(SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0}, nullptr, &cache), sighash_with_cache);
282+
CHashWriter h{SER_GETHASH, 0};
283+
BOOST_CHECK(cache.Load(hash_type, scriptcode, h));
284+
h << hash_type;
285+
const auto new_hash{h.GetHash()};
286+
BOOST_CHECK_EQUAL(SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0}, nullptr, &cache), new_hash);
287+
} else {
288+
BOOST_CHECK_EQUAL(SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, int32_t{0}, nullptr, &cache), uint256::ONE);
289+
}
290+
291+
// Wipe the cache and restore the correct cached value for this scriptcode and hash_type before starting the next iteration.
292+
CHashWriter dummy{SER_GETHASH, 0};
293+
cache.Store(hash_type, diff_scriptcode, dummy);
294+
(void)SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, 0, nullptr, &cache);
295+
BOOST_CHECK(cache.Load(hash_type, scriptcode, dummy) || expect_one);
296+
}
297+
}
298+
}
299+
210300
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)