diff --git a/src/Makefile.test.include b/src/Makefile.test.include --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -92,6 +92,7 @@ test/scriptflags.h \ test/scriptnum_tests.cpp \ test/serialize_tests.cpp \ + test/sigcache_tests.cpp \ test/sigencoding_tests.cpp \ test/sighash_tests.cpp \ test/sighashtype_tests.cpp \ diff --git a/src/script/sigcache.h b/src/script/sigcache.h --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -43,6 +43,9 @@ private: bool store; + bool IsCached(const std::vector &vchSig, const CPubKey &vchPubKey, + const uint256 &sighash) const; + public: CachingTransactionSignatureChecker(const CTransaction *txToIn, unsigned int nInIn, @@ -54,6 +57,8 @@ bool VerifySignature(const std::vector &vchSig, const CPubKey &vchPubKey, const uint256 &sighash) const override; + + friend class TestCachingTransactionSignatureChecker; }; void InitSignatureCache(); diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -80,20 +80,35 @@ (nElems * sizeof(uint256)) >> 20, nMaxCacheSize >> 20, nElems); } -bool CachingTransactionSignatureChecker::VerifySignature( - const std::vector &vchSig, const CPubKey &pubkey, - const uint256 &sighash) const { +template +bool RunMemoizedCheck(const std::vector &vchSig, const CPubKey &pubkey, + const uint256 &sighash, bool storeOrErase, const F &fun) { uint256 entry; signatureCache.ComputeEntry(entry, sighash, vchSig, pubkey); - if (signatureCache.Get(entry, !store)) { + if (signatureCache.Get(entry, !storeOrErase)) { return true; } - if (!TransactionSignatureChecker::VerifySignature(vchSig, pubkey, - sighash)) { + if (!fun()) { return false; } - if (store) { + if (storeOrErase) { signatureCache.Set(entry); } return true; } + +bool CachingTransactionSignatureChecker::IsCached( + const std::vector &vchSig, const CPubKey &pubkey, + const uint256 &sighash) const { + return RunMemoizedCheck(vchSig, pubkey, sighash, true, + [] { return false; }); +} + +bool CachingTransactionSignatureChecker::VerifySignature( + const std::vector &vchSig, const CPubKey &pubkey, + const uint256 &sighash) const { + return RunMemoizedCheck(vchSig, pubkey, sighash, store, [&] { + return TransactionSignatureChecker::VerifySignature(vchSig, pubkey, + sighash); + }); +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -108,6 +108,7 @@ scriptflags.cpp scriptnum_tests.cpp serialize_tests.cpp + sigcache_tests.cpp sigencoding_tests.cpp sighash_tests.cpp sighashtype_tests.cpp diff --git a/src/test/sigcache_tests.cpp b/src/test/sigcache_tests.cpp new file mode 100644 --- /dev/null +++ b/src/test/sigcache_tests.cpp @@ -0,0 +1,119 @@ +// Copyright (c) 2012-2015 The Bitcoin Core developers +// Copyright (c) 2019- The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// (based on key_tests.cpp) + +#include "script/sigcache.h" + +#include "base58.h" +#include "dstencode.h" +#include "key.h" +#include "test/test_bitcoin.h" +#include "utilstrencodings.h" + +#include + +#include +#include + +static const std::string strSecret1 = + "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"; +static const std::string strSecret1C = + "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"; + +class TestCachingTransactionSignatureChecker { + /** + * Sigcache is only accessible via CachingTransactionSignatureChecker + */ + CachingTransactionSignatureChecker *pchecker; + +public: + TestCachingTransactionSignatureChecker( + CachingTransactionSignatureChecker &checkerarg) { + pchecker = &checkerarg; + } + + inline bool VerifyAndStore(const std::vector &vchSig, + const CPubKey &pubkey, const uint256 &sighash) { + return pchecker->VerifySignature(vchSig, pubkey, sighash); + } + + inline bool IsCached(const std::vector &vchSig, + const CPubKey &pubkey, const uint256 &sighash) { + return pchecker->IsCached(vchSig, pubkey, sighash); + } +}; + +BOOST_FIXTURE_TEST_SUITE(sigcache_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(test1) { + /** + * Making CachingTransactionSignatureChecke requires a tx. So we make a + * dummy transaction (doesn't matter what it is) to construct it. + */ + CDataStream stream( + ParseHex( + "010000000122739e70fbee987a8be1788395a2f2e6ad18ccb7ff611cd798071539" + "dde3c38e000000000151ffffffff010000000000000000016a00000000"), + SER_NETWORK, PROTOCOL_VERSION); + CTransaction dummyTx(deserialize, stream); + PrecomputedTransactionData txdata(dummyTx); + CachingTransactionSignatureChecker checker(&dummyTx, 0, 0 * SATOSHI, true, + txdata); + + TestCachingTransactionSignatureChecker testChecker(checker); + + CBitcoinSecret bsecret1, bsecret1C; + BOOST_CHECK(bsecret1.SetString(strSecret1)); + BOOST_CHECK(bsecret1C.SetString(strSecret1C)); + + CKey key1 = bsecret1.GetKey(); + BOOST_CHECK(key1.IsCompressed() == false); + CKey key1C = bsecret1C.GetKey(); + BOOST_CHECK(key1C.IsCompressed() == true); + + CPubKey pubkey1 = key1.GetPubKey(); + CPubKey pubkey1C = key1C.GetPubKey(); + + for (int n = 0; n < 16; n++) { + std::string strMsg = strprintf("Sigcache test1 %i: xx", n); + uint256 hashMsg = Hash(strMsg.begin(), strMsg.end()); + uint256 hashMsg2 = Hash(strMsg.begin() + 1, strMsg.end()); + + std::vector sig; + BOOST_CHECK(key1.SignECDSA(hashMsg, sig)); + std::vector sig2; + BOOST_CHECK(key1.SignECDSA(hashMsg2, sig2)); + + // cross-check + BOOST_CHECK(!testChecker.VerifyAndStore(sig2, pubkey1, hashMsg)); + BOOST_CHECK(!testChecker.VerifyAndStore(sig, pubkey1, hashMsg2)); + // that should not have put them in cache... + BOOST_CHECK(!testChecker.IsCached(sig2, pubkey1, hashMsg)); + BOOST_CHECK(!testChecker.IsCached(sig, pubkey1, hashMsg2)); + + // check that it's not in cache at start + BOOST_CHECK(!testChecker.IsCached(sig, pubkey1, hashMsg)); + BOOST_CHECK(!testChecker.IsCached(sig2, pubkey1, hashMsg2)); + // Insert into cache + BOOST_CHECK(testChecker.VerifyAndStore(sig, pubkey1, hashMsg)); + BOOST_CHECK(testChecker.VerifyAndStore(sig2, pubkey1, hashMsg2)); + // check that it's in + BOOST_CHECK(testChecker.IsCached(sig, pubkey1, hashMsg)); + BOOST_CHECK(testChecker.IsCached(sig2, pubkey1, hashMsg2)); + // check that different signature hits different entry + BOOST_CHECK(!testChecker.IsCached(sig2, pubkey1, hashMsg)); + // check that compressed pubkey hits different entry + BOOST_CHECK(!testChecker.IsCached(sig, pubkey1C, hashMsg)); + // check that different message hits different entry + BOOST_CHECK(!testChecker.IsCached(sig, pubkey1, hashMsg2)); + + // compressed key is for same privkey, so verifying works: + BOOST_CHECK(testChecker.VerifyAndStore(sig, pubkey1C, hashMsg)); + // now we *should* get a hit + BOOST_CHECK(testChecker.IsCached(sig, pubkey1C, hashMsg)); + } +} + +BOOST_AUTO_TEST_SUITE_END()