Move signature cache from CKey::Verify to CheckSig in script.cpp
authorGavin Andresen <gavinandresen@gmail.com>
Tue, 22 May 2012 17:56:14 +0000 (13:56 -0400)
committerGavin Andresen <gavinandresen@gmail.com>
Tue, 19 Jun 2012 19:36:34 +0000 (15:36 -0400)
More than doubles the speed of verifying already-cached signatures
that use compressed pubkeys:
Before: ~200 microseconds
After:  ~80 microseconds
(no caching at all: ~3,300 microseconds per signature)

Also encapsulates the signature cache code in a class
and fixes a signed/unsigned comparison warning.

src/key.cpp
src/script.cpp

index ac7ac4d..b6c3f28 100644 (file)
@@ -4,7 +4,6 @@
 
 #include <map>
 
-#include <boost/tuple/tuple.hpp>
 #include <openssl/ecdsa.h>
 #include <openssl/obj_mac.h>
 
@@ -351,85 +350,23 @@ bool CKey::SetCompactSignature(uint256 hash, const std::vector<unsigned char>& v
     return false;
 }
 
-// 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)
-
-// sigdata_type is (signature hash, signature, public key):
-typedef boost::tuple<uint256, std::vector<unsigned char>, std::vector<unsigned char> > sigdata_type;
-static std::set< sigdata_type> setValidSigCache;
-static CCriticalSection cs_sigcache;
-
-static bool
-GetValidSigCache(uint256 hash, const std::vector<unsigned char>& vchSig, const std::vector<unsigned char>& pubKey)
-{
-    LOCK(cs_sigcache);
-
-    sigdata_type k(hash, vchSig, pubKey);
-    std::set<sigdata_type>::iterator mi = setValidSigCache.find(k);
-    if (mi != setValidSigCache.end())
-        return true;
-    return false;
-}
-
-static void
-SetValidSigCache(uint256 hash, const std::vector<unsigned char>& vchSig, const std::vector<unsigned char>& 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 nMaxCacheSize = GetArg("-maxsigcachesize", 50000);
-    if (nMaxCacheSize <= 0) return;
-
-    LOCK(cs_sigcache);
-
-    while (setValidSigCache.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<unsigned char> unused;
-        std::set<sigdata_type>::iterator it =
-            setValidSigCache.lower_bound(sigdata_type(randomHash, unused, unused));
-        if (it == setValidSigCache.end())
-            it = setValidSigCache.begin();
-        setValidSigCache.erase(*it);
-    }
-
-    sigdata_type k(hash, vchSig, pubKey);
-    setValidSigCache.insert(k);
-}
-
-
 bool CKey::Verify(uint256 hash, const std::vector<unsigned char>& vchSig)
 {
-    if (GetValidSigCache(hash, vchSig, GetPubKey()))
-        return true;
-
     // -1 = error, 0 = bad sig, 1 = good
     if (ECDSA_verify(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], vchSig.size(), pkey) != 1)
         return false;
 
-    // good sig
-    SetValidSigCache(hash, vchSig, GetPubKey());
     return true;
 }
 
 bool CKey::VerifyCompact(uint256 hash, const std::vector<unsigned char>& vchSig)
 {
-    if (GetValidSigCache(hash, vchSig, GetPubKey()))
-        return true;
-
     CKey key;
     if (!key.SetCompactSignature(hash, vchSig))
         return false;
     if (GetPubKey() != key.GetPubKey())
         return false;
 
-    SetValidSigCache(hash, vchSig, GetPubKey());
     return true;
 }
 
index 65e9b7c..56c2e50 100644 (file)
@@ -3,6 +3,7 @@
 // Distributed under the MIT/X11 software license, see the accompanying
 // file license.txt or http://www.opensource.org/licenses/mit-license.php.
 #include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
 
 using namespace std;
 using namespace boost;
@@ -12,6 +13,7 @@ using namespace boost;
 #include "bignum.h"
 #include "key.h"
 #include "main.h"
+#include "util.h"
 
 bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType);
 
@@ -1099,12 +1101,67 @@ uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int
 }
 
 
+// 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<uint256, std::vector<unsigned char>, std::vector<unsigned char> > sigdata_type;
+    std::set< sigdata_type> setValid;
+    CCriticalSection cs_sigcache;
+
+public:
+    bool
+    Get(uint256 hash, const std::vector<unsigned char>& vchSig, const std::vector<unsigned char>& pubKey)
+    {
+        LOCK(cs_sigcache);
+
+        sigdata_type k(hash, vchSig, pubKey);
+        std::set<sigdata_type>::iterator mi = setValid.find(k);
+        if (mi != setValid.end())
+            return true;
+        return false;
+    }
+
+    void
+    Set(uint256 hash, const std::vector<unsigned char>& vchSig, const std::vector<unsigned char>& 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 nMaxCacheSize = GetArg("-maxsigcachesize", 50000);
+        if (nMaxCacheSize <= 0) return;
+
+        LOCK(cs_sigcache);
+
+        while (static_cast<int64>(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<unsigned char> unused;
+            std::set<sigdata_type>::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 CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CScript scriptCode,
               const CTransaction& txTo, unsigned int nIn, int nHashType)
 {
-    CKey key;
-    if (!key.SetPubKey(vchPubKey))
-        return false;
+    static CSignatureCache signatureCache;
 
     // Hash type is one byte tacked on to the end of the signature
     if (vchSig.empty())
@@ -1115,7 +1172,20 @@ bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CSc
         return false;
     vchSig.pop_back();
 
-    return key.Verify(SignatureHash(scriptCode, txTo, nIn, nHashType), vchSig);
+    uint256 sighash = SignatureHash(scriptCode, txTo, nIn, nHashType);
+
+    if (signatureCache.Get(sighash, vchSig, vchPubKey))
+        return true;
+
+    CKey key;
+    if (!key.SetPubKey(vchPubKey))
+        return false;
+
+    if (!key.Verify(sighash, vchSig))
+        return false;
+
+    signatureCache.Set(sighash, vchSig, vchPubKey);
+    return true;
 }