Browse Source

Merge branch 'optimize'

0.8
Gavin Andresen 13 years ago
parent
commit
d0e4051cd8
  1. 332
      src/key.cpp
  2. 268
      src/key.h
  3. 88
      src/main.cpp
  4. 135
      src/test/DoS_tests.cpp
  5. 6
      src/util.cpp
  6. 1
      src/util.h

332
src/key.cpp

@ -2,8 +2,15 @@
// 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 <openssl/ec.h> #include <map>
#include <boost/tuple/tuple.hpp>
#include <openssl/ecdsa.h> #include <openssl/ecdsa.h>
#include <openssl/obj_mac.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)
@ -115,3 +122,326 @@ err:
if (Q != NULL) EC_POINT_free(Q); if (Q != NULL) EC_POINT_free(Q);
return ret; return ret;
} }
void CKey::SetCompressedPubKey()
{
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
fCompressedPubKey = true;
}
void CKey::Reset()
{
fCompressedPubKey = false;
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (pkey == NULL)
throw key_error("CKey::CKey() : EC_KEY_new_by_curve_name failed");
fSet = false;
}
CKey::CKey()
{
Reset();
}
CKey::CKey(const CKey& b)
{
pkey = EC_KEY_dup(b.pkey);
if (pkey == NULL)
throw key_error("CKey::CKey(const CKey&) : EC_KEY_dup failed");
fSet = b.fSet;
}
CKey& CKey::operator=(const CKey& b)
{
if (!EC_KEY_copy(pkey, b.pkey))
throw key_error("CKey::operator=(const CKey&) : EC_KEY_copy failed");
fSet = b.fSet;
return (*this);
}
CKey::~CKey()
{
EC_KEY_free(pkey);
}
bool CKey::IsNull() const
{
return !fSet;
}
bool CKey::IsCompressed() const
{
return fCompressedPubKey;
}
void CKey::MakeNewKey(bool fCompressed)
{
if (!EC_KEY_generate_key(pkey))
throw key_error("CKey::MakeNewKey() : EC_KEY_generate_key failed");
if (fCompressed)
SetCompressedPubKey();
fSet = true;
}
bool CKey::SetPrivKey(const CPrivKey& vchPrivKey)
{
const unsigned char* pbegin = &vchPrivKey[0];
if (!d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size()))
return false;
fSet = true;
return true;
}
bool CKey::SetSecret(const CSecret& vchSecret, bool fCompressed)
{
EC_KEY_free(pkey);
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (pkey == NULL)
throw key_error("CKey::SetSecret() : EC_KEY_new_by_curve_name failed");
if (vchSecret.size() != 32)
throw key_error("CKey::SetSecret() : secret must be 32 bytes");
BIGNUM *bn = BN_bin2bn(&vchSecret[0],32,BN_new());
if (bn == NULL)
throw key_error("CKey::SetSecret() : BN_bin2bn failed");
if (!EC_KEY_regenerate_key(pkey,bn))
{
BN_clear_free(bn);
throw key_error("CKey::SetSecret() : EC_KEY_regenerate_key failed");
}
BN_clear_free(bn);
fSet = true;
if (fCompressed || fCompressedPubKey)
SetCompressedPubKey();
return true;
}
CSecret CKey::GetSecret(bool &fCompressed) const
{
CSecret vchRet;
vchRet.resize(32);
const BIGNUM *bn = EC_KEY_get0_private_key(pkey);
int nBytes = BN_num_bytes(bn);
if (bn == NULL)
throw key_error("CKey::GetSecret() : EC_KEY_get0_private_key failed");
int n=BN_bn2bin(bn,&vchRet[32 - nBytes]);
if (n != nBytes)
throw key_error("CKey::GetSecret(): BN_bn2bin failed");
fCompressed = fCompressedPubKey;
return vchRet;
}
CPrivKey CKey::GetPrivKey() const
{
int nSize = i2d_ECPrivateKey(pkey, NULL);
if (!nSize)
throw key_error("CKey::GetPrivKey() : i2d_ECPrivateKey failed");
CPrivKey vchPrivKey(nSize, 0);
unsigned char* pbegin = &vchPrivKey[0];
if (i2d_ECPrivateKey(pkey, &pbegin) != nSize)
throw key_error("CKey::GetPrivKey() : i2d_ECPrivateKey returned unexpected size");
return vchPrivKey;
}
bool CKey::SetPubKey(const std::vector<unsigned char>& vchPubKey)
{
const unsigned char* pbegin = &vchPubKey[0];
if (!o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.size()))
return false;
fSet = true;
if (vchPubKey.size() == 33)
SetCompressedPubKey();
return true;
}
std::vector<unsigned char> CKey::GetPubKey() const
{
int nSize = i2o_ECPublicKey(pkey, NULL);
if (!nSize)
throw key_error("CKey::GetPubKey() : i2o_ECPublicKey failed");
std::vector<unsigned char> vchPubKey(nSize, 0);
unsigned char* pbegin = &vchPubKey[0];
if (i2o_ECPublicKey(pkey, &pbegin) != nSize)
throw key_error("CKey::GetPubKey() : i2o_ECPublicKey returned unexpected size");
return vchPubKey;
}
bool CKey::Sign(uint256 hash, std::vector<unsigned char>& vchSig)
{
unsigned int nSize = ECDSA_size(pkey);
vchSig.resize(nSize); // Make sure it is big enough
if (!ECDSA_sign(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], &nSize, pkey))
{
vchSig.clear();
return false;
}
vchSig.resize(nSize); // Shrink to fit actual size
return true;
}
// create a compact signature (65 bytes), which allows reconstructing the used public key
// The format is one header byte, followed by two times 32 bytes for the serialized r and s values.
// The header byte: 0x1B = first key with even y, 0x1C = first key with odd y,
// 0x1D = second key with even y, 0x1E = second key with odd y
bool CKey::SignCompact(uint256 hash, std::vector<unsigned char>& vchSig)
{
bool fOk = false;
ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&hash, sizeof(hash), pkey);
if (sig==NULL)
return false;
vchSig.clear();
vchSig.resize(65,0);
int nBitsR = BN_num_bits(sig->r);
int nBitsS = BN_num_bits(sig->s);
if (nBitsR <= 256 && nBitsS <= 256)
{
int nRecId = -1;
for (int i=0; i<4; i++)
{
CKey keyRec;
keyRec.fSet = true;
if (fCompressedPubKey)
keyRec.SetCompressedPubKey();
if (ECDSA_SIG_recover_key_GFp(keyRec.pkey, sig, (unsigned char*)&hash, sizeof(hash), i, 1) == 1)
if (keyRec.GetPubKey() == this->GetPubKey())
{
nRecId = i;
break;
}
}
if (nRecId == -1)
throw key_error("CKey::SignCompact() : unable to construct recoverable key");
vchSig[0] = nRecId+27+(fCompressedPubKey ? 4 : 0);
BN_bn2bin(sig->r,&vchSig[33-(nBitsR+7)/8]);
BN_bn2bin(sig->s,&vchSig[65-(nBitsS+7)/8]);
fOk = true;
}
ECDSA_SIG_free(sig);
return fOk;
}
// reconstruct public key from a compact signature
// This is only slightly more CPU intensive than just verifying it.
// If this function succeeds, the recovered public key is guaranteed to be valid
// (the signature is a valid signature of the given data for that key)
bool CKey::SetCompactSignature(uint256 hash, const std::vector<unsigned char>& vchSig)
{
if (vchSig.size() != 65)
return false;
int nV = vchSig[0];
if (nV<27 || nV>=35)
return false;
ECDSA_SIG *sig = ECDSA_SIG_new();
BN_bin2bn(&vchSig[1],32,sig->r);
BN_bin2bn(&vchSig[33],32,sig->s);
EC_KEY_free(pkey);
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (nV >= 31)
{
SetCompressedPubKey();
nV -= 4;
}
if (ECDSA_SIG_recover_key_GFp(pkey, sig, (unsigned char*)&hash, sizeof(hash), nV - 27, 0) == 1)
{
fSet = true;
ECDSA_SIG_free(sig);
return true;
}
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;
}
bool CKey::IsValid()
{
if (!fSet)
return false;
bool fCompr;
CSecret secret = GetSecret(fCompr);
CKey key2;
key2.SetSecret(secret, fCompr);
return GetPubKey() == key2.GetPubKey();
}

268
src/key.h

@ -8,13 +8,11 @@
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include "allocators.h" #include "allocators.h"
#include "uint256.h" #include "uint256.h"
#include <openssl/ec.h> // for EC_KEY definition
// secp160k1 // secp160k1
// const unsigned int PRIVATE_KEY_SIZE = 192; // const unsigned int PRIVATE_KEY_SIZE = 192;
// const unsigned int PUBLIC_KEY_SIZE = 41; // const unsigned int PUBLIC_KEY_SIZE = 41;
@ -38,9 +36,6 @@
// see www.keylength.com // see www.keylength.com
// script supports up to 75 for single byte push // script supports up to 75 for single byte push
int extern EC_KEY_regenerate_key(EC_KEY *eckey, BIGNUM *priv_key);
int extern ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check);
class key_error : public std::runtime_error class key_error : public std::runtime_error
{ {
public: public:
@ -62,267 +57,50 @@ protected:
bool fSet; bool fSet;
bool fCompressedPubKey; bool fCompressedPubKey;
void SetCompressedPubKey() void SetCompressedPubKey();
{
EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED);
fCompressedPubKey = true;
}
public: public:
void Reset() void Reset();
{
fCompressedPubKey = false;
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (pkey == NULL)
throw key_error("CKey::CKey() : EC_KEY_new_by_curve_name failed");
fSet = false;
}
CKey()
{
Reset();
}
CKey(const CKey& b)
{
pkey = EC_KEY_dup(b.pkey);
if (pkey == NULL)
throw key_error("CKey::CKey(const CKey&) : EC_KEY_dup failed");
fSet = b.fSet;
}
CKey& operator=(const CKey& b)
{
if (!EC_KEY_copy(pkey, b.pkey))
throw key_error("CKey::operator=(const CKey&) : EC_KEY_copy failed");
fSet = b.fSet;
return (*this);
}
~CKey()
{
EC_KEY_free(pkey);
}
bool IsNull() const
{
return !fSet;
}
bool IsCompressed() const CKey();
{ CKey(const CKey& b);
return fCompressedPubKey;
}
void MakeNewKey(bool fCompressed) CKey& operator=(const CKey& b);
{
if (!EC_KEY_generate_key(pkey))
throw key_error("CKey::MakeNewKey() : EC_KEY_generate_key failed");
if (fCompressed)
SetCompressedPubKey();
fSet = true;
}
bool SetPrivKey(const CPrivKey& vchPrivKey) ~CKey();
{
const unsigned char* pbegin = &vchPrivKey[0];
if (!d2i_ECPrivateKey(&pkey, &pbegin, vchPrivKey.size()))
return false;
fSet = true;
return true;
}
bool SetSecret(const CSecret& vchSecret, bool fCompressed = false) bool IsNull() const;
{ bool IsCompressed() const;
EC_KEY_free(pkey);
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (pkey == NULL)
throw key_error("CKey::SetSecret() : EC_KEY_new_by_curve_name failed");
if (vchSecret.size() != 32)
throw key_error("CKey::SetSecret() : secret must be 32 bytes");
BIGNUM *bn = BN_bin2bn(&vchSecret[0],32,BN_new());
if (bn == NULL)
throw key_error("CKey::SetSecret() : BN_bin2bn failed");
if (!EC_KEY_regenerate_key(pkey,bn))
{
BN_clear_free(bn);
throw key_error("CKey::SetSecret() : EC_KEY_regenerate_key failed");
}
BN_clear_free(bn);
fSet = true;
if (fCompressed || fCompressedPubKey)
SetCompressedPubKey();
return true;
}
CSecret GetSecret(bool &fCompressed) const void MakeNewKey(bool fCompressed);
{ bool SetPrivKey(const CPrivKey& vchPrivKey);
CSecret vchRet; bool SetSecret(const CSecret& vchSecret, bool fCompressed = false);
vchRet.resize(32); CSecret GetSecret(bool &fCompressed) const;
const BIGNUM *bn = EC_KEY_get0_private_key(pkey); CPrivKey GetPrivKey() const;
int nBytes = BN_num_bytes(bn); bool SetPubKey(const std::vector<unsigned char>& vchPubKey);
if (bn == NULL) std::vector<unsigned char> GetPubKey() const;
throw key_error("CKey::GetSecret() : EC_KEY_get0_private_key failed");
int n=BN_bn2bin(bn,&vchRet[32 - nBytes]);
if (n != nBytes)
throw key_error("CKey::GetSecret(): BN_bn2bin failed");
fCompressed = fCompressedPubKey;
return vchRet;
}
CPrivKey GetPrivKey() const bool Sign(uint256 hash, std::vector<unsigned char>& vchSig);
{
int nSize = i2d_ECPrivateKey(pkey, NULL);
if (!nSize)
throw key_error("CKey::GetPrivKey() : i2d_ECPrivateKey failed");
CPrivKey vchPrivKey(nSize, 0);
unsigned char* pbegin = &vchPrivKey[0];
if (i2d_ECPrivateKey(pkey, &pbegin) != nSize)
throw key_error("CKey::GetPrivKey() : i2d_ECPrivateKey returned unexpected size");
return vchPrivKey;
}
bool SetPubKey(const std::vector<unsigned char>& vchPubKey)
{
const unsigned char* pbegin = &vchPubKey[0];
if (!o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.size()))
return false;
fSet = true;
if (vchPubKey.size() == 33)
SetCompressedPubKey();
return true;
}
std::vector<unsigned char> GetPubKey() const
{
int nSize = i2o_ECPublicKey(pkey, NULL);
if (!nSize)
throw key_error("CKey::GetPubKey() : i2o_ECPublicKey failed");
std::vector<unsigned char> vchPubKey(nSize, 0);
unsigned char* pbegin = &vchPubKey[0];
if (i2o_ECPublicKey(pkey, &pbegin) != nSize)
throw key_error("CKey::GetPubKey() : i2o_ECPublicKey returned unexpected size");
return vchPubKey;
}
bool Sign(uint256 hash, std::vector<unsigned char>& vchSig)
{
unsigned int nSize = ECDSA_size(pkey);
vchSig.resize(nSize); // Make sure it is big enough
if (!ECDSA_sign(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], &nSize, pkey))
{
vchSig.clear();
return false;
}
vchSig.resize(nSize); // Shrink to fit actual size
return true;
}
// create a compact signature (65 bytes), which allows reconstructing the used public key // create a compact signature (65 bytes), which allows reconstructing the used public key
// The format is one header byte, followed by two times 32 bytes for the serialized r and s values. // The format is one header byte, followed by two times 32 bytes for the serialized r and s values.
// The header byte: 0x1B = first key with even y, 0x1C = first key with odd y, // The header byte: 0x1B = first key with even y, 0x1C = first key with odd y,
// 0x1D = second key with even y, 0x1E = second key with odd y // 0x1D = second key with even y, 0x1E = second key with odd y
bool SignCompact(uint256 hash, std::vector<unsigned char>& vchSig) bool SignCompact(uint256 hash, std::vector<unsigned char>& vchSig);
{
bool fOk = false;
ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&hash, sizeof(hash), pkey);
if (sig==NULL)
return false;
vchSig.clear();
vchSig.resize(65,0);
int nBitsR = BN_num_bits(sig->r);
int nBitsS = BN_num_bits(sig->s);
if (nBitsR <= 256 && nBitsS <= 256)
{
int nRecId = -1;
for (int i=0; i<4; i++)
{
CKey keyRec;
keyRec.fSet = true;
if (fCompressedPubKey)
keyRec.SetCompressedPubKey();
if (ECDSA_SIG_recover_key_GFp(keyRec.pkey, sig, (unsigned char*)&hash, sizeof(hash), i, 1) == 1)
if (keyRec.GetPubKey() == this->GetPubKey())
{
nRecId = i;
break;
}
}
if (nRecId == -1)
throw key_error("CKey::SignCompact() : unable to construct recoverable key");
vchSig[0] = nRecId+27+(fCompressedPubKey ? 4 : 0);
BN_bn2bin(sig->r,&vchSig[33-(nBitsR+7)/8]);
BN_bn2bin(sig->s,&vchSig[65-(nBitsS+7)/8]);
fOk = true;
}
ECDSA_SIG_free(sig);
return fOk;
}
// reconstruct public key from a compact signature // reconstruct public key from a compact signature
// This is only slightly more CPU intensive than just verifying it. // This is only slightly more CPU intensive than just verifying it.
// If this function succeeds, the recovered public key is guaranteed to be valid // If this function succeeds, the recovered public key is guaranteed to be valid
// (the signature is a valid signature of the given data for that key) // (the signature is a valid signature of the given data for that key)
bool SetCompactSignature(uint256 hash, const std::vector<unsigned char>& vchSig) bool SetCompactSignature(uint256 hash, const std::vector<unsigned char>& vchSig);
{
if (vchSig.size() != 65)
return false;
int nV = vchSig[0];
if (nV<27 || nV>=35)
return false;
ECDSA_SIG *sig = ECDSA_SIG_new();
BN_bin2bn(&vchSig[1],32,sig->r);
BN_bin2bn(&vchSig[33],32,sig->s);
EC_KEY_free(pkey);
pkey = EC_KEY_new_by_curve_name(NID_secp256k1);
if (nV >= 31)
{
SetCompressedPubKey();
nV -= 4;
}
if (ECDSA_SIG_recover_key_GFp(pkey, sig, (unsigned char*)&hash, sizeof(hash), nV - 27, 0) == 1)
{
fSet = true;
ECDSA_SIG_free(sig);
return true;
}
return false;
}
bool Verify(uint256 hash, const std::vector<unsigned char>& vchSig) bool Verify(uint256 hash, const std::vector<unsigned char>& vchSig);
{
// -1 = error, 0 = bad sig, 1 = good
if (ECDSA_verify(0, (unsigned char*)&hash, sizeof(hash), &vchSig[0], vchSig.size(), pkey) != 1)
return false;
return true;
}
// Verify a compact signature // Verify a compact signature
bool VerifyCompact(uint256 hash, const std::vector<unsigned char>& vchSig) bool VerifyCompact(uint256 hash, const std::vector<unsigned char>& vchSig);
{
CKey key;
if (!key.SetCompactSignature(hash, vchSig))
return false;
if (GetPubKey() != key.GetPubKey())
return false;
return true;
}
bool IsValid()
{
if (!fSet)
return false;
bool fCompr; bool IsValid();
CSecret secret = GetSecret(fCompr);
CKey key2;
key2.SetSecret(secret, fCompr);
return GetPubKey() == key2.GetPubKey();
}
}; };
#endif #endif

88
src/main.cpp

@ -44,7 +44,7 @@ map<uint256, CBlock*> mapOrphanBlocks;
multimap<uint256, CBlock*> mapOrphanBlocksByPrev; multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
map<uint256, CDataStream*> mapOrphanTransactions; map<uint256, CDataStream*> mapOrphanTransactions;
multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; map<uint256, map<uint256, CDataStream*> > mapOrphanTransactionsByPrev;
// Constant stuff for coinbase transactions we create: // Constant stuff for coinbase transactions we create:
CScript COINBASE_FLAGS; CScript COINBASE_FLAGS;
@ -161,17 +161,37 @@ void static ResendWalletTransactions()
// mapOrphanTransactions // mapOrphanTransactions
// //
void AddOrphanTx(const CDataStream& vMsg) bool AddOrphanTx(const CDataStream& vMsg)
{ {
CTransaction tx; CTransaction tx;
CDataStream(vMsg) >> tx; CDataStream(vMsg) >> tx;
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
if (mapOrphanTransactions.count(hash)) if (mapOrphanTransactions.count(hash))
return; return false;
CDataStream* pvMsg = new CDataStream(vMsg);
CDataStream* pvMsg = mapOrphanTransactions[hash] = new CDataStream(vMsg); // Ignore big transactions, to avoid a
// send-big-orphans memory exhaustion attack. If a peer has a legitimate
// large transaction with a missing parent then we assume
// it will rebroadcast it later, after the parent transaction(s)
// have been mined or received.
// 10,000 orphans, each of which is at most 5,000 bytes big is
// at most 500 megabytes of orphans:
if (pvMsg->size() > 5000)
{
delete pvMsg;
printf("ignoring large orphan tx (size: %u, hash: %s)\n", pvMsg->size(), hash.ToString().substr(0,10).c_str());
return false;
}
mapOrphanTransactions[hash] = pvMsg;
BOOST_FOREACH(const CTxIn& txin, tx.vin) BOOST_FOREACH(const CTxIn& txin, tx.vin)
mapOrphanTransactionsByPrev.insert(make_pair(txin.prevout.hash, pvMsg)); mapOrphanTransactionsByPrev[txin.prevout.hash].insert(make_pair(hash, pvMsg));
printf("stored orphan tx %s (mapsz %u)\n", hash.ToString().substr(0,10).c_str(),
mapOrphanTransactions.size());
return true;
} }
void static EraseOrphanTx(uint256 hash) void static EraseOrphanTx(uint256 hash)
@ -183,14 +203,9 @@ void static EraseOrphanTx(uint256 hash)
CDataStream(*pvMsg) >> tx; CDataStream(*pvMsg) >> tx;
BOOST_FOREACH(const CTxIn& txin, tx.vin) BOOST_FOREACH(const CTxIn& txin, tx.vin)
{ {
for (multimap<uint256, CDataStream*>::iterator mi = mapOrphanTransactionsByPrev.lower_bound(txin.prevout.hash); mapOrphanTransactionsByPrev[txin.prevout.hash].erase(hash);
mi != mapOrphanTransactionsByPrev.upper_bound(txin.prevout.hash);) if (mapOrphanTransactionsByPrev[txin.prevout.hash].empty())
{ mapOrphanTransactionsByPrev.erase(txin.prevout.hash);
if ((*mi).second == pvMsg)
mapOrphanTransactionsByPrev.erase(mi++);
else
mi++;
}
} }
delete pvMsg; delete pvMsg;
mapOrphanTransactions.erase(hash); mapOrphanTransactions.erase(hash);
@ -202,9 +217,7 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans)
while (mapOrphanTransactions.size() > nMaxOrphans) while (mapOrphanTransactions.size() > nMaxOrphans)
{ {
// Evict a random orphan: // Evict a random orphan:
std::vector<unsigned char> randbytes(32); uint256 randomhash = GetRandHash();
RAND_bytes(&randbytes[0], 32);
uint256 randomhash(randbytes);
map<uint256, CDataStream*>::iterator it = mapOrphanTransactions.lower_bound(randomhash); map<uint256, CDataStream*>::iterator it = mapOrphanTransactions.lower_bound(randomhash);
if (it == mapOrphanTransactions.end()) if (it == mapOrphanTransactions.end())
it = mapOrphanTransactions.begin(); it = mapOrphanTransactions.begin();
@ -1155,17 +1168,28 @@ bool CTransaction::ConnectInputs(MapPrevTx inputs,
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight); return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight);
// Check for negative or overflow input values
nValueIn += txPrev.vout[prevout.n].nValue;
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
return DoS(100, error("ConnectInputs() : txin values out of range"));
}
// The first loop above does all the inexpensive checks.
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
// Helps prevent CPU exhaustion attacks.
for (unsigned int i = 0; i < vin.size(); i++)
{
COutPoint prevout = vin[i].prevout;
assert(inputs.count(prevout.hash) > 0);
CTxIndex& txindex = inputs[prevout.hash].first;
CTransaction& txPrev = inputs[prevout.hash].second;
// Check for conflicts (double-spend) // Check for conflicts (double-spend)
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier // This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network. // for an attacker to attempt to split the network.
if (!txindex.vSpent[prevout.n].IsNull()) if (!txindex.vSpent[prevout.n].IsNull())
return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str()); return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str());
// Check for negative or overflow input values
nValueIn += txPrev.vout[prevout.n].nValue;
if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn))
return DoS(100, error("ConnectInputs() : txin values out of range"));
// Skip ECDSA signature verification when connecting blocks (fBlock=true) // Skip ECDSA signature verification when connecting blocks (fBlock=true)
// before the last blockchain checkpoint. This is safe because block merkle hashes are // before the last blockchain checkpoint. This is safe because block merkle hashes are
// still computed and checked, and any change will be caught at the next checkpoint. // still computed and checked, and any change will be caught at the next checkpoint.
@ -2460,7 +2484,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
// at a time so the setAddrKnowns of the chosen nodes prevent repeats // at a time so the setAddrKnowns of the chosen nodes prevent repeats
static uint256 hashSalt; static uint256 hashSalt;
if (hashSalt == 0) if (hashSalt == 0)
RAND_bytes((unsigned char*)&hashSalt, sizeof(hashSalt)); hashSalt = GetRandHash();
int64 hashAddr = addr.GetHash(); int64 hashAddr = addr.GetHash();
uint256 hashRand = hashSalt ^ (hashAddr<<32) ^ ((GetTime()+hashAddr)/(24*60*60)); uint256 hashRand = hashSalt ^ (hashAddr<<32) ^ ((GetTime()+hashAddr)/(24*60*60));
hashRand = Hash(BEGIN(hashRand), END(hashRand)); hashRand = Hash(BEGIN(hashRand), END(hashRand));
@ -2676,6 +2700,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
else if (strCommand == "tx") else if (strCommand == "tx")
{ {
vector<uint256> vWorkQueue; vector<uint256> vWorkQueue;
vector<uint256> vEraseQueue;
CDataStream vMsg(vRecv); CDataStream vMsg(vRecv);
CTxDB txdb("r"); CTxDB txdb("r");
CTransaction tx; CTransaction tx;
@ -2691,32 +2716,41 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
RelayMessage(inv, vMsg); RelayMessage(inv, vMsg);
mapAlreadyAskedFor.erase(inv); mapAlreadyAskedFor.erase(inv);
vWorkQueue.push_back(inv.hash); vWorkQueue.push_back(inv.hash);
vEraseQueue.push_back(inv.hash);
// Recursively process any orphan transactions that depended on this one // Recursively process any orphan transactions that depended on this one
for (unsigned int i = 0; i < vWorkQueue.size(); i++) for (unsigned int i = 0; i < vWorkQueue.size(); i++)
{ {
uint256 hashPrev = vWorkQueue[i]; uint256 hashPrev = vWorkQueue[i];
for (multimap<uint256, CDataStream*>::iterator mi = mapOrphanTransactionsByPrev.lower_bound(hashPrev); for (map<uint256, CDataStream*>::iterator mi = mapOrphanTransactionsByPrev[hashPrev].begin();
mi != mapOrphanTransactionsByPrev.upper_bound(hashPrev); mi != mapOrphanTransactionsByPrev[hashPrev].end();
++mi) ++mi)
{ {
const CDataStream& vMsg = *((*mi).second); const CDataStream& vMsg = *((*mi).second);
CTransaction tx; CTransaction tx;
CDataStream(vMsg) >> tx; CDataStream(vMsg) >> tx;
CInv inv(MSG_TX, tx.GetHash()); CInv inv(MSG_TX, tx.GetHash());
bool fMissingInputs2 = false;
if (tx.AcceptToMemoryPool(txdb, true)) if (tx.AcceptToMemoryPool(txdb, true, &fMissingInputs2))
{ {
printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str()); printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
SyncWithWallets(tx, NULL, true); SyncWithWallets(tx, NULL, true);
RelayMessage(inv, vMsg); RelayMessage(inv, vMsg);
mapAlreadyAskedFor.erase(inv); mapAlreadyAskedFor.erase(inv);
vWorkQueue.push_back(inv.hash); vWorkQueue.push_back(inv.hash);
vEraseQueue.push_back(inv.hash);
}
else if (!fMissingInputs2)
{
// invalid orphan
vEraseQueue.push_back(inv.hash);
printf(" removed invalid orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
} }
} }
} }
BOOST_FOREACH(uint256 hash, vWorkQueue) BOOST_FOREACH(uint256 hash, vEraseQueue)
EraseOrphanTx(hash); EraseOrphanTx(hash);
} }
else if (fMissingInputs) else if (fMissingInputs)
@ -3072,7 +3106,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
// 1/4 of tx invs blast to all immediately // 1/4 of tx invs blast to all immediately
static uint256 hashSalt; static uint256 hashSalt;
if (hashSalt == 0) if (hashSalt == 0)
RAND_bytes((unsigned char*)&hashSalt, sizeof(hashSalt)); hashSalt = GetRandHash();
uint256 hashRand = inv.hash ^ hashSalt; uint256 hashRand = inv.hash ^ hashSalt;
hashRand = Hash(BEGIN(hashRand), END(hashRand)); hashRand = Hash(BEGIN(hashRand), END(hashRand));
bool fTrickleWait = ((hashRand & 3) != 0); bool fTrickleWait = ((hashRand & 3) != 0);

135
src/test/DoS_tests.cpp

@ -1,7 +1,10 @@
// //
// Unit tests for denial-of-service detection/prevention code // Unit tests for denial-of-service detection/prevention code
// //
#include <algorithm>
#include <boost/assign/list_of.hpp> // for 'map_list_of()' #include <boost/assign/list_of.hpp> // for 'map_list_of()'
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
@ -13,10 +16,10 @@
#include <stdint.h> #include <stdint.h>
// Tests this internal-to-main.cpp method: // Tests this internal-to-main.cpp method:
extern void AddOrphanTx(const CDataStream& vMsg); extern bool AddOrphanTx(const CDataStream& vMsg);
extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans); extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans);
extern std::map<uint256, CDataStream*> mapOrphanTransactions; extern std::map<uint256, CDataStream*> mapOrphanTransactions;
extern std::multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; extern std::map<uint256, std::map<uint256, CDataStream*> > mapOrphanTransactionsByPrev;
CService ip(uint32_t i) CService ip(uint32_t i)
{ {
@ -57,7 +60,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore)
BOOST_CHECK(!CNode::IsBanned(addr1)); BOOST_CHECK(!CNode::IsBanned(addr1));
dummyNode1.Misbehaving(1); dummyNode1.Misbehaving(1);
BOOST_CHECK(CNode::IsBanned(addr1)); BOOST_CHECK(CNode::IsBanned(addr1));
mapArgs["-banscore"] = "100"; mapArgs.erase("-banscore");
} }
BOOST_AUTO_TEST_CASE(DoS_bantime) BOOST_AUTO_TEST_CASE(DoS_bantime)
@ -129,18 +132,10 @@ BOOST_AUTO_TEST_CASE(DoS_checknbits)
} }
static uint256 RandomHash()
{
std::vector<unsigned char> randbytes(32);
RAND_bytes(&randbytes[0], 32);
uint256 randomhash(randbytes);
return randomhash;
}
CTransaction RandomOrphan() CTransaction RandomOrphan()
{ {
std::map<uint256, CDataStream*>::iterator it; std::map<uint256, CDataStream*>::iterator it;
it = mapOrphanTransactions.lower_bound(RandomHash()); it = mapOrphanTransactions.lower_bound(GetRandHash());
if (it == mapOrphanTransactions.end()) if (it == mapOrphanTransactions.end())
it = mapOrphanTransactions.begin(); it = mapOrphanTransactions.begin();
const CDataStream* pvMsg = it->second; const CDataStream* pvMsg = it->second;
@ -162,7 +157,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
CTransaction tx; CTransaction tx;
tx.vin.resize(1); tx.vin.resize(1);
tx.vin[0].prevout.n = 0; tx.vin[0].prevout.n = 0;
tx.vin[0].prevout.hash = RandomHash(); tx.vin[0].prevout.hash = GetRandHash();
tx.vin[0].scriptSig << OP_1; tx.vin[0].scriptSig << OP_1;
tx.vout.resize(1); tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT; tx.vout[0].nValue = 1*CENT;
@ -192,6 +187,32 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
AddOrphanTx(ds); AddOrphanTx(ds);
} }
// This really-big orphan should be ignored:
for (int i = 0; i < 10; i++)
{
CTransaction txPrev = RandomOrphan();
CTransaction tx;
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
tx.vin.resize(500);
for (int j = 0; j < tx.vin.size(); j++)
{
tx.vin[j].prevout.n = j;
tx.vin[j].prevout.hash = txPrev.GetHash();
}
SignSignature(keystore, txPrev, tx, 0);
// Re-use same signature for other inputs
// (they don't have to be valid for this test)
for (int j = 1; j < tx.vin.size(); j++)
tx.vin[j].scriptSig = tx.vin[0].scriptSig;
CDataStream ds(SER_DISK, CLIENT_VERSION);
ds << tx;
BOOST_CHECK(!AddOrphanTx(ds));
}
// Test LimitOrphanTxSize() function: // Test LimitOrphanTxSize() function:
LimitOrphanTxSize(40); LimitOrphanTxSize(40);
BOOST_CHECK(mapOrphanTransactions.size() <= 40); BOOST_CHECK(mapOrphanTransactions.size() <= 40);
@ -202,4 +223,92 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
BOOST_CHECK(mapOrphanTransactionsByPrev.empty()); BOOST_CHECK(mapOrphanTransactionsByPrev.empty());
} }
BOOST_AUTO_TEST_CASE(DoS_checkSig)
{
// Test signature caching code (see key.cpp Verify() methods)
CKey key;
key.MakeNewKey(true);
CBasicKeyStore keystore;
keystore.AddKey(key);
// 100 orphan transactions:
static const int NPREV=100;
CTransaction orphans[NPREV];
for (int i = 0; i < NPREV; i++)
{
CTransaction& tx = orphans[i];
tx.vin.resize(1);
tx.vin[0].prevout.n = 0;
tx.vin[0].prevout.hash = GetRandHash();
tx.vin[0].scriptSig << OP_1;
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
CDataStream ds(SER_DISK, CLIENT_VERSION);
ds << tx;
AddOrphanTx(ds);
}
// Create a transaction that depends on orphans:
CTransaction tx;
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey.SetBitcoinAddress(key.GetPubKey());
tx.vin.resize(NPREV);
for (int j = 0; j < tx.vin.size(); j++)
{
tx.vin[j].prevout.n = 0;
tx.vin[j].prevout.hash = orphans[j].GetHash();
}
// Creating signatures primes the cache:
boost::posix_time::ptime mst1 = boost::posix_time::microsec_clock::local_time();
for (int j = 0; j < tx.vin.size(); j++)
BOOST_CHECK(SignSignature(keystore, orphans[j], tx, j));
boost::posix_time::ptime mst2 = boost::posix_time::microsec_clock::local_time();
boost::posix_time::time_duration msdiff = mst2 - mst1;
long nOneValidate = msdiff.total_milliseconds();
if (fDebug) printf("DoS_Checksig sign: %ld\n", nOneValidate);
// ... now validating repeatedly should be quick:
// 2.8GHz machine, -g build: Sign takes ~760ms,
// uncached Verify takes ~250ms, cached Verify takes ~50ms
// (for 100 single-signature inputs)
mst1 = boost::posix_time::microsec_clock::local_time();
for (int i = 0; i < 5; i++)
for (int j = 0; j < tx.vin.size(); j++)
BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, SIGHASH_ALL));
mst2 = boost::posix_time::microsec_clock::local_time();
msdiff = mst2 - mst1;
long nManyValidate = msdiff.total_milliseconds();
if (fDebug) printf("DoS_Checksig five: %ld\n", nManyValidate);
BOOST_CHECK_MESSAGE(nManyValidate < nOneValidate, "Signature cache timing failed");
// Empty a signature, validation should fail:
CScript save = tx.vin[0].scriptSig;
tx.vin[0].scriptSig = CScript();
BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, true, SIGHASH_ALL));
tx.vin[0].scriptSig = save;
// Swap signatures, validation should fail:
std::swap(tx.vin[0].scriptSig, tx.vin[1].scriptSig);
BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, true, SIGHASH_ALL));
BOOST_CHECK(!VerifySignature(orphans[1], tx, 1, true, SIGHASH_ALL));
std::swap(tx.vin[0].scriptSig, tx.vin[1].scriptSig);
// Exercise -maxsigcachesize code:
mapArgs["-maxsigcachesize"] = "10";
// Generate a new, different signature for vin[0] to trigger cache clear:
CScript oldSig = tx.vin[0].scriptSig;
BOOST_CHECK(SignSignature(keystore, orphans[0], tx, 0));
BOOST_CHECK(tx.vin[0].scriptSig != oldSig);
for (int j = 0; j < tx.vin.size(); j++)
BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, SIGHASH_ALL));
mapArgs.erase("-maxsigcachesize");
LimitOrphanTxSize(0);
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

6
src/util.cpp

@ -176,6 +176,12 @@ int GetRandInt(int nMax)
return GetRand(nMax); return GetRand(nMax);
} }
uint256 GetRandHash()
{
uint256 hash;
RAND_bytes((unsigned char*)&hash, sizeof(hash));
return hash;
}

1
src/util.h

@ -164,6 +164,7 @@ boost::filesystem::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
void ShrinkDebugFile(); void ShrinkDebugFile();
int GetRandInt(int nMax); int GetRandInt(int nMax);
uint64 GetRand(uint64 nMax); uint64 GetRand(uint64 nMax);
uint256 GetRandHash();
int64 GetTime(); int64 GetTime();
void SetMockTime(int64 nMockTimeIn); void SetMockTime(int64 nMockTimeIn);
int64 GetAdjustedTime(); int64 GetAdjustedTime();

Loading…
Cancel
Save