From 5c1e798a8e9df15f8fbd120e57fc67e585f13843 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 10 Sep 2014 16:16:09 +0200 Subject: [PATCH] Make signature cache optional --- src/Makefile.am | 2 + src/bitcoin-tx.cpp | 2 +- src/main.cpp | 2 +- src/main.h | 1 + src/script/interpreter.cpp | 74 +++----------------------------- src/script/interpreter.h | 34 ++++++++------- src/script/sigcache.cpp | 88 ++++++++++++++++++++++++++++++++++++++ src/script/sigcache.h | 34 +++++++++++++++ src/script/sign.cpp | 6 +-- 9 files changed, 155 insertions(+), 88 deletions(-) create mode 100644 src/script/sigcache.cpp create mode 100644 src/script/sigcache.h diff --git a/src/Makefile.am b/src/Makefile.am index bd6f1ba0d..155adfef7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -103,6 +103,7 @@ BITCOIN_CORE_H = \ script/compressor.h \ script/interpreter.h \ script/script.h \ + script/sigcache.h \ script/sign.h \ script/standard.h \ serialize.h \ @@ -218,6 +219,7 @@ libbitcoin_common_a_SOURCES = \ script/compressor.cpp \ script/interpreter.cpp \ script/script.cpp \ + script/sigcache.cpp \ script/sign.cpp \ script/standard.cpp \ $(BITCOIN_CORE_H) diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index a61b4fe29..a198eb586 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -435,7 +435,7 @@ static void MutateTxSign(CMutableTransaction& tx, const string& flagStr) BOOST_FOREACH(const CTransaction& txv, txVariants) { txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); } - if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, STANDARD_SCRIPT_VERIFY_FLAGS)) + if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, SignatureChecker(mergedTx, i))) fComplete = false; } diff --git a/src/main.cpp b/src/main.cpp index 55485c86f..5faa0086f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -645,7 +645,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) // IsStandard() will have already returned false // and this method isn't called. vector > stack; - if (!EvalScript(stack, tx.vin[i].scriptSig, tx, i, false)) + if (!EvalScript(stack, tx.vin[i].scriptSig, false, BaseSignatureChecker())) return false; if (whichType == TX_SCRIPTHASH) diff --git a/src/main.h b/src/main.h index 7e849505d..ded6c1553 100644 --- a/src/main.h +++ b/src/main.h @@ -17,6 +17,7 @@ #include "net.h" #include "pow.h" #include "script/script.h" +#include "script/sigcache.h" #include "script/standard.h" #include "sync.h" #include "txmempool.h" diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 048d298f0..f0b426839 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -9,14 +9,10 @@ #include "crypto/ripemd160.h" #include "crypto/sha1.h" #include "crypto/sha2.h" -#include "random.h" #include "script/script.h" #include "uint256.h" #include "util.h" -#include -#include - using namespace std; typedef vector valtype; @@ -132,7 +128,7 @@ bool IsCanonicalSignature(const valtype &vchSig, unsigned int flags) { return true; } -bool EvalScript(vector >& stack, const CScript& script, unsigned int flags, const SignatureChecker& checker) +bool EvalScript(vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker) { CScript::const_iterator pc = script.begin(); CScript::const_iterator pend = script.end(); @@ -921,65 +917,13 @@ uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsig return ss.GetHash(); } -// Valid signature cache, to avoid doing expensive ECDSA signature checking -// twice for every transaction (once when accepted into memory pool, and -// again when accepted into the block chain) -class CSignatureCache +bool SignatureChecker::VerifySignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash, int flags) const { -private: - // sigdata_type is (signature hash, signature, public key): - typedef boost::tuple, CPubKey> sigdata_type; - std::set< sigdata_type> setValid; - boost::shared_mutex cs_sigcache; - -public: - bool - Get(const uint256 &hash, const std::vector& vchSig, const CPubKey& pubKey) - { - boost::shared_lock lock(cs_sigcache); - - sigdata_type k(hash, vchSig, pubKey); - std::set::iterator mi = setValid.find(k); - if (mi != setValid.end()) - return true; - return false; - } - - void Set(const uint256 &hash, const std::vector& vchSig, const CPubKey& pubKey) - { - // DoS prevention: limit cache size to less than 10MB - // (~200 bytes per cache entry times 50,000 entries) - // Since there are a maximum of 20,000 signature operations per block - // 50,000 is a reasonable default. - int64_t nMaxCacheSize = GetArg("-maxsigcachesize", 50000); - if (nMaxCacheSize <= 0) return; - - boost::unique_lock lock(cs_sigcache); - - while (static_cast(setValid.size()) > nMaxCacheSize) - { - // Evict a random entry. Random because that helps - // foil would-be DoS attackers who might try to pre-generate - // and re-use a set of valid signatures just-slightly-greater - // than our cache size. - uint256 randomHash = GetRandHash(); - std::vector unused; - std::set::iterator it = - setValid.lower_bound(sigdata_type(randomHash, unused, unused)); - if (it == setValid.end()) - it = setValid.begin(); - setValid.erase(*it); - } - - sigdata_type k(hash, vchSig, pubKey); - setValid.insert(k); - } -}; + return pubkey.Verify(sighash, vchSig); +} bool SignatureChecker::CheckSig(const vector& vchSigIn, const vector& vchPubKey, const CScript& scriptCode, int flags) const { - static CSignatureCache signatureCache; - CPubKey pubkey(vchPubKey); if (!pubkey.IsValid()) return false; @@ -993,19 +937,13 @@ bool SignatureChecker::CheckSig(const vector& vchSigIn, const vec uint256 sighash = SignatureHash(scriptCode, txTo, nIn, nHashType); - if (signatureCache.Get(sighash, vchSig, pubkey)) - return true; - - if (!pubkey.Verify(sighash, vchSig)) + if (!VerifySignature(vchSig, pubkey, sighash, flags)) return false; - if (!(flags & SCRIPT_VERIFY_NOCACHE)) - signatureCache.Set(sighash, vchSig, pubkey); - return true; } -bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const SignatureChecker& checker) +bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const BaseSignatureChecker& checker) { vector > stack, stackCopy; if (!EvalScript(stack, scriptSig, flags, checker)) diff --git a/src/script/interpreter.h b/src/script/interpreter.h index f3fd49a3b..801f7e0ce 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -10,9 +10,10 @@ #include #include +class uint256; +class CPubKey; class CScript; class CTransaction; -class uint256; /** Signature hash types/flags */ enum @@ -39,29 +40,32 @@ bool IsCanonicalSignature(const std::vector &vchSig, unsigned int uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); -class SignatureChecker +class BaseSignatureChecker +{ +public: + virtual bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, int nFlags) const + { + return false; + } + + virtual ~BaseSignatureChecker() {} +}; + +class SignatureChecker : public BaseSignatureChecker { private: const CTransaction& txTo; unsigned int nIn; +protected: + virtual bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash, int flags) const; + public: SignatureChecker(const CTransaction& txToIn, unsigned int nInIn) : txTo(txToIn), nIn(nInIn) {} bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, int nFlags) const; }; -bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const SignatureChecker& checker); -bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const SignatureChecker& checker); - -// Wrappers using a default SignatureChecker. -bool inline EvalScript(std::vector >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, unsigned int flags) -{ - return EvalScript(stack, script, flags, SignatureChecker(txTo, nIn)); -} - -bool inline VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, unsigned int flags) -{ - return VerifyScript(scriptSig, scriptPubKey, flags, SignatureChecker(txTo, nIn)); -} +bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker); +bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const BaseSignatureChecker& checker); #endif // H_BITCOIN_SCRIPT_INTERPRETER diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp new file mode 100644 index 000000000..a1dec64db --- /dev/null +++ b/src/script/sigcache.cpp @@ -0,0 +1,88 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "sigcache.h" + +#include "key.h" +#include "random.h" +#include "uint256.h" +#include "util.h" + +#include +#include + +namespace { + +// Valid signature cache, to avoid doing expensive ECDSA signature checking +// twice for every transaction (once when accepted into memory pool, and +// again when accepted into the block chain) +class CSignatureCache +{ +private: + // sigdata_type is (signature hash, signature, public key): + typedef boost::tuple, CPubKey> sigdata_type; + std::set< sigdata_type> setValid; + boost::shared_mutex cs_sigcache; + +public: + bool + Get(const uint256 &hash, const std::vector& vchSig, const CPubKey& pubKey) + { + boost::shared_lock lock(cs_sigcache); + + sigdata_type k(hash, vchSig, pubKey); + std::set::iterator mi = setValid.find(k); + if (mi != setValid.end()) + return true; + return false; + } + + void Set(const uint256 &hash, const std::vector& vchSig, const CPubKey& pubKey) + { + // DoS prevention: limit cache size to less than 10MB + // (~200 bytes per cache entry times 50,000 entries) + // Since there are a maximum of 20,000 signature operations per block + // 50,000 is a reasonable default. + int64_t nMaxCacheSize = GetArg("-maxsigcachesize", 50000); + if (nMaxCacheSize <= 0) return; + + boost::unique_lock lock(cs_sigcache); + + while (static_cast(setValid.size()) > nMaxCacheSize) + { + // Evict a random entry. Random because that helps + // foil would-be DoS attackers who might try to pre-generate + // and re-use a set of valid signatures just-slightly-greater + // than our cache size. + uint256 randomHash = GetRandHash(); + std::vector unused; + std::set::iterator it = + setValid.lower_bound(sigdata_type(randomHash, unused, unused)); + if (it == setValid.end()) + it = setValid.begin(); + setValid.erase(*it); + } + + sigdata_type k(hash, vchSig, pubKey); + setValid.insert(k); + } +}; + +} + +bool CachingSignatureChecker::VerifySignature(const std::vector& vchSig, const CPubKey& pubkey, const uint256& sighash, int flags) const +{ + static CSignatureCache signatureCache; + + if (signatureCache.Get(sighash, vchSig, pubkey)) + return true; + + if (!SignatureChecker::VerifySignature(vchSig, pubkey, sighash, flags)) + return false; + + if (!(flags & SCRIPT_VERIFY_NOCACHE)) + signatureCache.Set(sighash, vchSig, pubkey); + return true; +} diff --git a/src/script/sigcache.h b/src/script/sigcache.h new file mode 100644 index 000000000..8faa7ae01 --- /dev/null +++ b/src/script/sigcache.h @@ -0,0 +1,34 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef H_BITCOIN_SCRIPT_SIGCACHE +#define H_BITCOIN_SCRIPT_SIGCACHE + +#include "script/interpreter.h" + +#include + +class CPubKey; + +class CachingSignatureChecker : public SignatureChecker +{ +public: + CachingSignatureChecker(const CTransaction& txToIn, unsigned int nInIn) : SignatureChecker(txToIn, nInIn) {} + + bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash, int flags) const; +}; + +// Wrappers using a default SignatureChecker. +bool inline EvalScript(std::vector >& stack, const CScript& script, const CTransaction& txTo, unsigned int nIn, unsigned int flags) +{ + return EvalScript(stack, script, flags, CachingSignatureChecker(txTo, nIn)); +} + +bool inline VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, unsigned int flags) +{ + return VerifyScript(scriptSig, scriptPubKey, flags, CachingSignatureChecker(txTo, nIn)); +} + +#endif diff --git a/src/script/sign.cpp b/src/script/sign.cpp index a17fb5878..a524ac8e5 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -123,7 +123,7 @@ bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutabl } // Test solution - return VerifyScript(txin.scriptSig, fromPubKey, txTo, nIn, STANDARD_SCRIPT_VERIFY_FLAGS); + return VerifyScript(txin.scriptSig, fromPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, SignatureChecker(txTo, nIn)); } bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType) @@ -252,9 +252,9 @@ CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, unsign Solver(scriptPubKey, txType, vSolutions); vector stack1; - EvalScript(stack1, scriptSig1, CTransaction(), 0, SCRIPT_VERIFY_STRICTENC); + EvalScript(stack1, scriptSig1, SCRIPT_VERIFY_STRICTENC, BaseSignatureChecker()); vector stack2; - EvalScript(stack2, scriptSig2, CTransaction(), 0, SCRIPT_VERIFY_STRICTENC); + EvalScript(stack2, scriptSig2, SCRIPT_VERIFY_STRICTENC, BaseSignatureChecker()); return CombineSignatures(scriptPubKey, txTo, nIn, txType, vSolutions, stack1, stack2); }