Move signature cache from CKey::Verify to CheckSig in script.cpp

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.
This commit is contained in:
Gavin Andresen 2012-05-22 13:56:14 -04:00
parent d1edab602a
commit acf513cfe7
2 changed files with 75 additions and 69 deletions

View File

@ -4,13 +4,10 @@
#include <map> #include <map>
#include <boost/tuple/tuple.hpp>
#include <openssl/ecdsa.h> #include <openssl/ecdsa.h>
#include <openssl/obj_mac.h> #include <openssl/obj_mac.h>
#include "key.h" #include "key.h"
#include "sync.h"
#include "util.h"
// Generate a private key from just the secret parameter // Generate a private key from just the secret parameter
int EC_KEY_regenerate_key(EC_KEY *eckey, BIGNUM *priv_key) int EC_KEY_regenerate_key(EC_KEY *eckey, BIGNUM *priv_key)
@ -352,85 +349,23 @@ bool CKey::SetCompactSignature(uint256 hash, const std::vector<unsigned char>& v
return false; 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) 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 // -1 = error, 0 = bad sig, 1 = good
if (ECDSA_verify(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], vchSig.size(), pkey) != 1) if (ECDSA_verify(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], vchSig.size(), pkey) != 1)
return false; return false;
// good sig
SetValidSigCache(hash, vchSig, GetPubKey());
return true; return true;
} }
bool CKey::VerifyCompact(uint256 hash, const std::vector<unsigned char>& vchSig) bool CKey::VerifyCompact(uint256 hash, const std::vector<unsigned char>& vchSig)
{ {
if (GetValidSigCache(hash, vchSig, GetPubKey()))
return true;
CKey key; CKey key;
if (!key.SetCompactSignature(hash, vchSig)) if (!key.SetCompactSignature(hash, vchSig))
return false; return false;
if (GetPubKey() != key.GetPubKey()) if (GetPubKey() != key.GetPubKey())
return false; return false;
SetValidSigCache(hash, vchSig, GetPubKey());
return true; return true;
} }

View File

@ -3,6 +3,7 @@
// Distributed under the MIT/X11 software license, see the accompanying // Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/tuple/tuple.hpp>
using namespace std; using namespace std;
using namespace boost; using namespace boost;
@ -12,6 +13,8 @@ using namespace boost;
#include "bignum.h" #include "bignum.h"
#include "key.h" #include "key.h"
#include "main.h" #include "main.h"
#include "sync.h"
#include "util.h"
bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType);
@ -1099,12 +1102,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, bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CScript scriptCode,
const CTransaction& txTo, unsigned int nIn, int nHashType) const CTransaction& txTo, unsigned int nIn, int nHashType)
{ {
CKey key; static CSignatureCache signatureCache;
if (!key.SetPubKey(vchPubKey))
return false;
// Hash type is one byte tacked on to the end of the signature // Hash type is one byte tacked on to the end of the signature
if (vchSig.empty()) if (vchSig.empty())
@ -1115,7 +1173,20 @@ bool CheckSig(vector<unsigned char> vchSig, vector<unsigned char> vchPubKey, CSc
return false; return false;
vchSig.pop_back(); 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;
} }