Skip to content

Commit

Permalink
Merge bitcoin#8524: Precompute sighashes
Browse files Browse the repository at this point in the history
35fe039 Rename to PrecomputedTransactionData (Pieter Wuille)
ab48c5e Unit test for sighash caching (Nicolas DORIER)
d2c5d04 Precompute sighashes (Pieter Wuille)
  • Loading branch information
sipa authored and PastaPastaPasta committed Jun 10, 2019
1 parent a83b631 commit 7696cc9
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 20 deletions.
35 changes: 33 additions & 2 deletions src/script/interpreter.cpp
Expand Up @@ -1117,9 +1117,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)
PrecomputedTransactionData::PrecomputedTransactionData(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 PrecomputedTransactionData* cache)
{
static const uint256 one(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
if (nIn >= txTo.vin.size()) {
Expand Down Expand Up @@ -1162,7 +1193,7 @@ bool TransactionSignatureChecker::CheckSig(const std::vector<unsigned char>& vch
int nHashType = vchSig.back();
vchSig.pop_back();

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

if (!VerifySignature(vchSig, pubkey, sighash))
return false;
Expand Down
13 changes: 11 additions & 2 deletions src/script/interpreter.h
Expand Up @@ -94,7 +94,14 @@ enum

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

uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType);
struct PrecomputedTransactionData
{
uint256 hashPrevouts, hashSequence, hashOutputs;

PrecomputedTransactionData(const CTransaction& tx);
};

uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const PrecomputedTransactionData* cache = NULL);

class BaseSignatureChecker
{
Expand Down Expand Up @@ -122,12 +129,14 @@ class TransactionSignatureChecker : public BaseSignatureChecker
private:
const CTransaction* txTo;
unsigned int nIn;
const PrecomputedTransactionData* txdata;

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) : txTo(txToIn), nIn(nInIn) {}
TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn) : txTo(txToIn), nIn(nInIn), txdata(NULL) {}
TransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), txdata(&txdataIn) {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckSequence(const CScriptNum& nSequence) const override;
Expand Down
2 changes: 1 addition & 1 deletion src/script/sigcache.h
Expand Up @@ -46,7 +46,7 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker
bool store;

public:
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, bool storeIn=true) : TransactionSignatureChecker(txToIn, nInIn), store(storeIn) {}
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, PrecomputedTransactionData& txdataIn, bool storeIn=true) : TransactionSignatureChecker(txToIn, nInIn, txdataIn), store(storeIn) {}

bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
};
Expand Down
6 changes: 4 additions & 2 deletions src/test/script_P2SH_tests.cpp
Expand Up @@ -104,19 +104,21 @@ 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 i = 0; i < 8; i++) {
PrecomputedTransactionData txdata(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;
const CTxOut& output = txFrom.vout[txTo[i].vin[0].prevout.n];
bool sigOK = CScriptCheck(output.scriptPubKey, output.nValue, txTo[i], 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false)();
bool sigOK = CScriptCheck(output.scriptPubKey, output.nValue, txTo[i], 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false, &txdata)();
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
7 changes: 5 additions & 2 deletions src/test/transaction_tests.cpp
Expand Up @@ -7,6 +7,7 @@
#include "test/test_dash.h"

#include "clientversion.h"
#include "checkqueue.h"
#include "consensus/validation.h"
#include "core_io.h"
#include "key.h"
Expand Down Expand Up @@ -139,6 +140,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state), strTest);
BOOST_CHECK(state.IsValid());

PrecomputedTransactionData txdata(tx);
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
Expand All @@ -149,7 +151,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)

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), &err),
verify_flags, TransactionSignatureChecker(&tx, i, txdata), &err),
strTest);
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err));
}
Expand Down Expand Up @@ -214,6 +216,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
CValidationState state;
fValid = CheckTransaction(tx, state) && state.IsValid();

PrecomputedTransactionData txdata(tx);
for (unsigned int i = 0; i < tx.vin.size() && fValid; i++)
{
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
Expand All @@ -224,7 +227,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)

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), &err);
verify_flags, TransactionSignatureChecker(&tx, i, txdata), &err);
}
BOOST_CHECK_MESSAGE(!fValid, strTest);
BOOST_CHECK_MESSAGE(err != SCRIPT_ERR_OK, ScriptErrorString(err));
Expand Down
20 changes: 13 additions & 7 deletions src/validation.cpp
Expand Up @@ -832,7 +832,8 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C

// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
PrecomputedTransactionData txdata(tx);
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true, txdata))
return false; // state filled in by CheckInputs

// Check again against just the consensus-critical mandatory script
Expand All @@ -844,7 +845,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
// There is a similar check in CreateNewBlock() to prevent creating
// invalid blocks, however allowing such transactions into the mempool
// can be exploited as a DoS attack.
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true))
if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata))
{
return error("%s: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s, %s",
__func__, hash.ToString(), FormatStateMessage(state));
Expand Down Expand Up @@ -1331,7 +1332,8 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight)

bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, cacheStore), &error)) {
PrecomputedTransactionData txdata(*ptxTo);
if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, txdata, cacheStore), &error)) {
return false;
}
return true;
Expand Down Expand Up @@ -1390,7 +1392,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, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks)
{
if (!tx.IsCoinBase())
{
Expand Down Expand Up @@ -1424,7 +1426,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
const CAmount amount = coin.out.nValue;

// Verify signature
CScriptCheck check(scriptPubKey, amount, tx, i, flags, cacheStore);
CScriptCheck check(scriptPubKey, amount, tx, i, flags, cacheStore, &txdata);
if (pvChecks) {
pvChecks->push_back(CScriptCheck());
check.swap(pvChecks->back());
Expand All @@ -1437,7 +1439,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
// avoid splitting the network between upgraded and
// non-upgraded nodes.
CScriptCheck check2(scriptPubKey, amount, tx, i,
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore);
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata);
if (check2())
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
}
Expand Down Expand Up @@ -2015,6 +2017,9 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > addressUnspentIndex;
std::vector<std::pair<CSpentIndexKey, CSpentIndexValue> > spentIndex;

std::vector<PrecomputedTransactionData> txdata;
txdata.reserve(block.vtx.size()); // Required so that pointers to individual PrecomputedTransactionData don't get invalidated

bool fDIP0001Active_context = pindex->nHeight >= Params().GetConsensus().DIP0001Height;

for (unsigned int i = 0; i < block.vtx.size(); i++)
Expand All @@ -2028,6 +2033,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
return state.DoS(100, error("ConnectBlock(): too many sigops"),
REJECT_INVALID, "bad-blk-sigops");

txdata.emplace_back(tx);
if (!tx.IsCoinBase())
{
if (!view.HaveInputs(tx))
Expand Down Expand Up @@ -2102,7 +2108,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd

std::vector<CScriptCheck> vChecks;
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, txdata[i], nScriptCheckThreads ? &vChecks : NULL))
return error("ConnectBlock(): CheckInputs on %s failed with %s",
tx.GetHash().ToString(), FormatStateMessage(state));
control.Add(vChecks);
Expand Down
11 changes: 7 additions & 4 deletions src/validation.h
Expand Up @@ -46,6 +46,7 @@ class CBlockPolicyEstimator;
class CTxMemPool;
class CValidationInterface;
class CValidationState;
class PrecomputedTransactionData;
struct ChainTxData;

struct LockPoints;
Expand Down Expand Up @@ -233,7 +234,7 @@ static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 945 * 1024 * 1024;
*
* Note that we guarantee that either the proof-of-work is valid on pblock, or
* (and possibly also) BlockChecked will have been called.
*
*
* Call without cs_main held.
*
* @param[in] pblock The block we want to process.
Expand Down Expand Up @@ -377,7 +378,7 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& ma
* 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);
unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, 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 @@ -450,12 +451,13 @@ class CScriptCheck
unsigned int nFlags;
bool cacheStore;
ScriptError error;
PrecomputedTransactionData *txdata;

public:
CScriptCheck(): ptxTo(0), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR) {}
CScriptCheck(const CScript& scriptPubKeyIn, const CAmount amountIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn) :
CScriptCheck(const CScript& scriptPubKeyIn, const CAmount amountIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn) :
scriptPubKey(scriptPubKeyIn),
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), txdata(txdataIn) { }

bool operator()();

Expand All @@ -466,6 +468,7 @@ class CScriptCheck
std::swap(nFlags, check.nFlags);
std::swap(cacheStore, check.cacheStore);
std::swap(error, check.error);
std::swap(txdata, check.txdata);
}

ScriptError GetScriptError() const { return error; }
Expand Down

0 comments on commit 7696cc9

Please sign in to comment.