Skip to content

Commit

Permalink
Precompute sighashes
Browse files Browse the repository at this point in the history
Coming from btc@d2c5d044d00ec805957ab246a7863d83ca075805
  • Loading branch information
furszy committed Aug 2, 2020
1 parent dfd24eb commit 446d340
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 42 deletions.
33 changes: 23 additions & 10 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,9 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa
int flags = STANDARD_SCRIPT_VERIFY_FLAGS;
if (fCLTVIsActivated)
flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
if (!CheckInputs(tx, state, view, true, flags, true)) {

CachedHashes cachedHashes(tx);
if (!CheckInputs(tx, state, view, true, flags, true, cachedHashes)) {
return false;
}

Expand All @@ -1074,9 +1076,9 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa
flags = MANDATORY_SCRIPT_VERIFY_FLAGS;
if (fCLTVIsActivated)
flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
if (!CheckInputs(tx, state, view, true, flags, true)) {
if (!CheckInputs(tx, state, view, true, flags, true, cachedHashes)) {
return error("%s: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s, %s",
__func__, hash.ToString(), FormatStateMessage(state));
__func__, hash.ToString(), FormatStateMessage(state));
}

// Store transaction in memory
Expand Down Expand Up @@ -1283,7 +1285,9 @@ bool AcceptableInputs(CTxMemPool& pool, CValidationState& state, const CTransact
int flags = STANDARD_SCRIPT_VERIFY_FLAGS;
if (fCLTVIsActivated)
flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
if (!CheckInputs(tx, state, view, false, flags, true)) {

CachedHashes cachedHashes(tx);
if (!CheckInputs(tx, state, view, false, flags, true, cachedHashes)) {
return error("AcceptableInputs: : ConnectInputs failed %s", hash.ToString());
}

Expand All @@ -1297,7 +1301,7 @@ bool AcceptableInputs(CTxMemPool& pool, CValidationState& state, const CTransact
// invalid blocks, however allowing such transactions into the mempool
// can be exploited as a DoS attack.
// for any real tx this will be checked on AcceptToMemoryPool anyway
// if (!CheckInputs(tx, state, view, false, MANDATORY_SCRIPT_VERIFY_FLAGS, true))
// if (!CheckInputs(tx, state, view, false, MANDATORY_SCRIPT_VERIFY_FLAGS, true, cachedHashes))
// {
// return error("AcceptableInputs: : BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString());
// }
Expand Down Expand Up @@ -1735,7 +1739,7 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache &inputs, int nHeight)
bool CScriptCheck::operator()()
{
const CScript& scriptSig = ptxTo->vin[nIn].scriptSig;
return VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore), &error);
return VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore, *cachedHashes), &error);
}

std::map<COutPoint, COutPoint> mapInvalidOutPoints;
Expand Down Expand Up @@ -1858,7 +1862,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
}
}// namespace Consensus

bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks)
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, CachedHashes& cachedHashes, std::vector<CScriptCheck> *pvChecks)
{
if (!tx.IsCoinBase() && !tx.HasZerocoinSpendInputs()) {

Expand All @@ -1882,7 +1886,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
assert(coins);

// Verify signature
CScriptCheck check(*coins, tx, i, flags, cacheStore);
CScriptCheck check(*coins, tx, i, flags, cacheStore, &cachedHashes);
if (pvChecks) {
pvChecks->push_back(CScriptCheck());
check.swap(pvChecks->back());
Expand All @@ -1895,7 +1899,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
// avoid splitting the network between upgraded and
// non-upgraded nodes.
CScriptCheck check(*coins, tx, i,
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore);
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &cachedHashes);
if (check())
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
}
Expand Down Expand Up @@ -2238,6 +2242,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
unsigned int nMaxBlockSigOps = MAX_BLOCK_SIGOPS_CURRENT;
std::vector<uint256> vSpendsInBlock;
uint256 hashBlock = block.GetHash();

std::vector<CachedHashes> cachedHashes;
cachedHashes.reserve(block.vtx.size()); // Required so that pointers to individual CachedHashes don't get invalidated
for (unsigned int i = 0; i < block.vtx.size(); i++) {
const CTransaction& tx = block.vtx[i];

Expand Down Expand Up @@ -2344,6 +2351,12 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
if (nSigOps > nMaxBlockSigOps)
return state.DoS(100, error("ConnectBlock() : too many sigops"), REJECT_INVALID, "bad-blk-sigops");

}

// Cache the sig ser hashes
cachedHashes.emplace_back(tx);

if (!tx.IsCoinBase()) {
if (!tx.IsCoinStake())
nFees += view.GetValueIn(tx) - tx.GetValueOut();
nValueIn += view.GetValueIn(tx);
Expand All @@ -2354,7 +2367,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;

bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, nScriptCheckThreads ? &vChecks : NULL))
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, cachedHashes[i], nScriptCheckThreads ? &vChecks : NULL))
return error("%s: Check inputs on %s failed with %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
control.Add(vChecks);
}
Expand Down
10 changes: 7 additions & 3 deletions src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class CScriptCheck;
class CValidationInterface;
class CValidationState;

struct CachedHashes;
struct CBlockTemplate;
struct CNodeStateStats;

Expand Down Expand Up @@ -281,7 +282,7 @@ CAmount GetMinRelayFee(const CTransaction& tx, const CTxMemPool& pool, unsigned
* This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it
* instead of being performed inline.
*/
bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& view, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector<CScriptCheck>* pvChecks = NULL);
bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& view, bool fScriptChecks, unsigned int flags, bool cacheStore, CachedHashes& cachedHashes, std::vector<CScriptCheck>* pvChecks = NULL);

/** Apply the effects of this transaction on the UTXO set represented by view */
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);
Expand Down Expand Up @@ -314,12 +315,14 @@ class CScriptCheck
unsigned int nFlags;
bool cacheStore;
ScriptError error;
CachedHashes *cachedHashes;

public:
CScriptCheck() : amount(0), ptxTo(0), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR) {}
CScriptCheck(const CCoins& txFromIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn) : scriptPubKey(txFromIn.vout[txToIn.vin[nInIn].prevout.n].scriptPubKey),
CScriptCheck(const CCoins& txFromIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, CachedHashes* cachedHashesIn) : scriptPubKey(txFromIn.vout[txToIn.vin[nInIn].prevout.n].scriptPubKey),
amount(txFromIn.vout[txToIn.vin[nInIn].prevout.n].nValue),
ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR) {}
ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR),
cachedHashes(cachedHashesIn) {}

bool operator()();

Expand All @@ -332,6 +335,7 @@ class CScriptCheck
std::swap(nFlags, check.nFlags);
std::swap(cacheStore, check.cacheStore);
std::swap(error, check.error);
std::swap(cachedHashes, check.cachedHashes);
}

ScriptError GetScriptError() const { return error; }
Expand Down
3 changes: 2 additions & 1 deletion src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,8 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet,
// create only contains transactions that are valid in new blocks.

CValidationState state;
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true))
CachedHashes cachedHashes(tx);
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, cachedHashes))
continue;

UpdateCoins(tx, view, nHeight);
Expand Down
53 changes: 36 additions & 17 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1085,9 +1085,40 @@ class CTransactionSignatureSerializer {
}
};

uint256 GetPrevoutHash(const CTransaction& txTo) {
CHashWriter ss(SER_GETHASH, 0);
for (unsigned int n = 0; n < txTo.vin.size(); n++) {
ss << txTo.vin[n].prevout;
}
return ss.GetHash();
}

uint256 GetSequenceHash(const CTransaction& txTo) {
CHashWriter ss(SER_GETHASH, 0);
for (unsigned int n = 0; n < txTo.vin.size(); n++) {
ss << txTo.vin[n].nSequence;
}
return ss.GetHash();
}

uint256 GetOutputsHash(const CTransaction& txTo) {
CHashWriter ss(SER_GETHASH, 0);
for (unsigned int n = 0; n < txTo.vout.size(); n++) {
ss << txTo.vout[n];
}
return ss.GetHash();
}

} // anon namespace

uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion)
CachedHashes::CachedHashes(const CTransaction& txTo)
{
hashPrevouts = GetPrevoutHash(txTo);
hashSequence = GetSequenceHash(txTo);
hashOutputs = GetOutputsHash(txTo);
}

uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const CachedHashes* cache)
{
if (nIn >= txTo.vin.size()) {
// nIn out of range
Expand All @@ -1101,27 +1132,15 @@ uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsig
uint256 hashOutputs;

if (!(nHashType & SIGHASH_ANYONECANPAY)) {
CHashWriter ss(SER_GETHASH, 0);
for (unsigned int n = 0; n < txTo.vin.size(); n++) {
ss << txTo.vin[n].prevout;
}
hashPrevouts = ss.GetHash(); // TODO: cache this value for all signatures in a transaction
hashPrevouts = cache ? cache->hashPrevouts : GetPrevoutHash(txTo);
}

if (!(nHashType & SIGHASH_ANYONECANPAY) && (nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) {
CHashWriter ss(SER_GETHASH, 0);
for (unsigned int n = 0; n < txTo.vin.size(); n++) {
ss << txTo.vin[n].nSequence;
}
hashSequence = ss.GetHash(); // TODO: cache this value for all signatures in a transaction
hashSequence = cache ? cache->hashSequence : GetSequenceHash(txTo);
}

if ((nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) {
CHashWriter ss(SER_GETHASH, 0);
for (unsigned int n = 0; n < txTo.vout.size(); n++) {
ss << txTo.vout[n];
}
hashOutputs = ss.GetHash(); // TODO: cache this value for all signatures in a transaction
hashOutputs = cache ? cache->hashOutputs : GetOutputsHash(txTo);
} else if ((nHashType & 0x1f) == SIGHASH_SINGLE && nIn < txTo.vout.size()) {
CHashWriter ss(SER_GETHASH, 0);
ss << txTo.vout[nIn];
Expand Down Expand Up @@ -1186,7 +1205,7 @@ bool TransactionSignatureChecker::CheckSig(const std::vector<unsigned char>& vch
int nHashType = vchSig.back();
vchSig.pop_back();

uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);
uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->cachedHashes);

if (!VerifySignature(vchSig, pubkey, sighash))
return false;
Expand Down
14 changes: 12 additions & 2 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,20 @@ enum

bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned int flags, ScriptError* serror);

struct CachedHashes
{
uint256 hashPrevouts, hashSequence, hashOutputs;

CachedHashes(const CTransaction& tx);
};

enum SigVersion
{
SIGVERSION_BASE = 0,
SIGVERSION_WITNESS_V0 = 1,
};

uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion);
uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const CachedHashes* cache = nullptr);

class BaseSignatureChecker
{
Expand Down Expand Up @@ -121,12 +128,15 @@ class TransactionSignatureChecker : public BaseSignatureChecker
const CTransaction* txTo;
unsigned int nIn;
const CAmount amount;
const CachedHashes* cachedHashes;

protected:
virtual bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;

public:
TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn) {}
TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), cachedHashes(nullptr) {}
TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const CachedHashes& cachedHashesIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), cachedHashes(&cachedHashesIn) {}

bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override ;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckColdStake(const CScript& script) const override {
Expand Down
2 changes: 1 addition & 1 deletion src/script/sigcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker
bool store;

public:
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amount, bool storeIn=true) : TransactionSignatureChecker(txToIn, nInIn, amount), store(storeIn) {}
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amount, bool storeIn, CachedHashes& cachedHashesIn) : TransactionSignatureChecker(txToIn, nInIn, amount, cachedHashesIn), store(storeIn) {}

bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
};
Expand Down
10 changes: 6 additions & 4 deletions src/test/script_P2SH_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,20 @@ BOOST_AUTO_TEST_CASE(sign)
}
// All of the above should be OK, and the txTos have valid signatures
// Check to make sure signature verification fails if we use the wrong ScriptSig:
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
{
for (int i = 0; i < 8; i++) {
CachedHashes cachedHashes(txTo[i]);
for (int j = 0; j < 8; j++) {
CScript sigSave = txTo[i].vin[0].scriptSig;
txTo[i].vin[0].scriptSig = txTo[j].vin[0].scriptSig;
bool sigOK = CScriptCheck(CCoins(txFrom, 0), txTo[i], 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false)();
bool sigOK = CScriptCheck(CCoins(txFrom, 0), txTo[i], 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC,
false, &cachedHashes)();
if (i == j)
BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j));
else
BOOST_CHECK_MESSAGE(!sigOK, strprintf("VerifySignature %d %d", i, j));
txTo[i].vin[0].scriptSig = sigSave;
}
}
}

BOOST_AUTO_TEST_CASE(norecurse)
Expand Down
6 changes: 4 additions & 2 deletions src/test/transaction_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
BOOST_CHECK_MESSAGE(CheckTransaction(tx, false, false, state), strTest);
BOOST_CHECK(state.IsValid());

CachedHashes cachedHashes(tx);
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
Expand All @@ -151,7 +152,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
CAmount amount = 0;
unsigned int verify_flags = ParseScriptFlags(test[2].get_str());
BOOST_CHECK_MESSAGE(VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout],
verify_flags, TransactionSignatureChecker(&tx, i, amount), &err),
verify_flags, TransactionSignatureChecker(&tx, i, amount, cachedHashes), &err),
strTest);
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));
}
Expand Down Expand Up @@ -215,6 +216,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
CValidationState state;
fValid = CheckTransaction(tx, false, false, state) && state.IsValid();

CachedHashes cachedHashes(tx);
for (unsigned int i = 0; i < tx.vin.size() && fValid; i++)
{
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
Expand All @@ -226,7 +228,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
CAmount amount = 0;
unsigned int verify_flags = ParseScriptFlags(test[2].get_str());
fValid = VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout],
verify_flags, TransactionSignatureChecker(&tx, i, amount), &err);
verify_flags, TransactionSignatureChecker(&tx, i, amount, cachedHashes), &err);
}
BOOST_CHECK_MESSAGE(!fValid, strTest);
BOOST_CHECK_MESSAGE(err != SCRIPT_ERR_OK, ScriptErrorString(err));
Expand Down
6 changes: 4 additions & 2 deletions src/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,8 @@ void CTxMemPool::check(const CCoinsViewCache* pcoins) const
waitingOnDependants.push_back(&(*it));
else {
CValidationState state;
assert(CheckInputs(tx, state, mempoolDuplicate, false, 0, false, NULL));
CachedHashes cachedHashes(tx);
assert(CheckInputs(tx, state, mempoolDuplicate, false, 0, false, cachedHashes, NULL));
UpdateCoins(tx, mempoolDuplicate, 1000000);
}
}
Expand All @@ -677,7 +678,8 @@ void CTxMemPool::check(const CCoinsViewCache* pcoins) const
stepsSinceLastRemove++;
assert(stepsSinceLastRemove < waitingOnDependants.size());
} else {
assert(CheckInputs(entry->GetTx(), state, mempoolDuplicate, false, 0, false, NULL));
CachedHashes cachedHashes(entry->GetTx());
assert(CheckInputs(entry->GetTx(), state, mempoolDuplicate, false, 0, false, cachedHashes, NULL));
UpdateCoins(entry->GetTx(), mempoolDuplicate, 1000000);
stepsSinceLastRemove = 0;
}
Expand Down

0 comments on commit 446d340

Please sign in to comment.