From dd0c036538d1ecee358801b591b3623738c2f318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Tim=C3=B3n?= Date: Wed, 24 Jun 2015 07:25:30 +0200 Subject: [PATCH 1/9] Policy: MOVEONLY: Create policy/policy.h with some constants Zcash: Adjusted to be move-only after our changes to the constants. --- src/Makefile.am | 1 + src/bitcoin-tx.cpp | 1 + src/consensus/consensus.h | 2 ++ src/init.cpp | 1 + src/main.cpp | 2 ++ src/main.h | 14 ----------- src/miner.cpp | 1 + src/policy/policy.h | 44 ++++++++++++++++++++++++++++++++++ src/rpc/rawtransaction.cpp | 1 + src/script/sign.cpp | 3 ++- src/script/standard.h | 18 -------------- src/test/script_P2SH_tests.cpp | 1 + src/wallet/wallet.cpp | 1 + src/zcbenchmarks.cpp | 1 + 14 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 src/policy/policy.h diff --git a/src/Makefile.am b/src/Makefile.am index 86c3724fa..ef9c0ccde 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -157,6 +157,7 @@ BITCOIN_CORE_H = \ netbase.h \ noui.h \ policy/fees.h \ + policy/policy.h \ pow.h \ prevector.h \ primitives/block.h \ diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 77f9f24d2..1bb304596 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -9,6 +9,7 @@ #include "core_io.h" #include "key_io.h" #include "keystore.h" +#include "policy/policy.h" #include "primitives/transaction.h" #include "script/script.h" #include "script/sign.h" diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index 865b280e7..847e5d53a 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -6,6 +6,8 @@ #ifndef BITCOIN_CONSENSUS_CONSENSUS_H #define BITCOIN_CONSENSUS_CONSENSUS_H +#include + /** The minimum allowed block version (network rule) */ static const int32_t MIN_BLOCK_VERSION = 4; /** The minimum allowed transaction version (network rule) */ diff --git a/src/init.cpp b/src/init.cpp index b0426bd49..c26cd681a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -26,6 +26,7 @@ #include "metrics.h" #include "miner.h" #include "net.h" +#include "policy/policy.h" #include "rpc/server.h" #include "rpc/register.h" #include "script/standard.h" diff --git a/src/main.cpp b/src/main.cpp index 6c312f953..200743075 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,6 +13,7 @@ #include "chainparams.h" #include "checkpoints.h" #include "checkqueue.h" +#include "consensus/consensus.h" #include "consensus/upgrades.h" #include "consensus/validation.h" #include "deprecation.h" @@ -20,6 +21,7 @@ #include "merkleblock.h" #include "metrics.h" #include "net.h" +#include "policy/policy.h" #include "pow.h" #include "txmempool.h" #include "ui_interface.h" diff --git a/src/main.h b/src/main.h index 1e9f352c1..db60609ff 100644 --- a/src/main.h +++ b/src/main.h @@ -14,7 +14,6 @@ #include "chain.h" #include "chainparams.h" #include "coins.h" -#include "consensus/consensus.h" #include "consensus/upgrades.h" #include "net.h" #include "primitives/block.h" @@ -54,21 +53,12 @@ class PrecomputedTransactionData; struct CNodeStateStats; -/** Default for -blockmaxsize and -blockminsize, which control the range of sizes the mining code will create **/ -static const unsigned int DEFAULT_BLOCK_MAX_SIZE = MAX_BLOCK_SIZE; -static const unsigned int DEFAULT_BLOCK_MIN_SIZE = 0; -/** Default for -blockprioritysize, maximum space for zero/low-fee transactions **/ -static const unsigned int DEFAULT_BLOCK_PRIORITY_SIZE = DEFAULT_BLOCK_MAX_SIZE / 2; /** Default for accepting alerts from the P2P network. */ static const bool DEFAULT_ALERTS = true; /** Minimum alert priority for enabling safe mode. */ static const int ALERT_PRIORITY_SAFE_MODE = 4000; /** Maximum reorg length we will accept before we shut down and alert the user. */ static const unsigned int MAX_REORG_LENGTH = COINBASE_MATURITY - 1; -/** Maximum number of signature check operations in an IsStandard() P2SH script */ -static const unsigned int MAX_P2SH_SIGOPS = 15; -/** The maximum number of sigops we're willing to relay/mine in a single tx */ -static const unsigned int MAX_STANDARD_TX_SIGOPS = MAX_BLOCK_SIGOPS/5; /** Default for -minrelaytxfee, minimum relay fee for transactions */ static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 100; /** Default for -maxorphantx, maximum number of orphan transactions kept in memory */ @@ -119,10 +109,6 @@ static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100; static const bool DEFAULT_TESTSAFEMODE = false; -// Sanity check the magic numbers when we change them -BOOST_STATIC_ASSERT(DEFAULT_BLOCK_MAX_SIZE <= MAX_BLOCK_SIZE); -BOOST_STATIC_ASSERT(DEFAULT_BLOCK_PRIORITY_SIZE <= DEFAULT_BLOCK_MAX_SIZE); - #define equihash_parameters_acceptable(N, K) \ ((CBlockHeader::HEADER_SIZE + equihash_solution_size(N, K))*MAX_HEADERS_RESULTS < \ MAX_PROTOCOL_MESSAGE_LENGTH-1000) diff --git a/src/miner.cpp b/src/miner.cpp index fe25ebebe..505195ab3 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -21,6 +21,7 @@ #include "main.h" #include "metrics.h" #include "net.h" +#include "policy/policy.h" #include "pow.h" #include "primitives/transaction.h" #include "random.h" diff --git a/src/policy/policy.h b/src/policy/policy.h new file mode 100644 index 000000000..5641bdf2d --- /dev/null +++ b/src/policy/policy.h @@ -0,0 +1,44 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2014 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_POLICY_H +#define BITCOIN_POLICY_H + +#include "consensus/consensus.h" +#include "script/interpreter.h" +#include "script/standard.h" + +/** Default for -blockmaxsize and -blockminsize, which control the range of sizes the mining code will create **/ +static const unsigned int DEFAULT_BLOCK_MAX_SIZE = MAX_BLOCK_SIZE; +static const unsigned int DEFAULT_BLOCK_MIN_SIZE = 0; +/** Default for -blockprioritysize, maximum space for zero/low-fee transactions **/ +static const unsigned int DEFAULT_BLOCK_PRIORITY_SIZE = DEFAULT_BLOCK_MAX_SIZE / 2; +/** Maximum number of signature check operations in an IsStandard() P2SH script */ +static const unsigned int MAX_P2SH_SIGOPS = 15; +/** The maximum number of sigops we're willing to relay/mine in a single tx */ +static const unsigned int MAX_STANDARD_TX_SIGOPS = MAX_BLOCK_SIGOPS/5; +/** + * Standard script verification flags that standard transactions will comply + * with. However scripts violating these flags may still be present in valid + * blocks and we must accept those blocks. + */ +static const unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS | + // SCRIPT_VERIFY_DERSIG is always enforced + SCRIPT_VERIFY_STRICTENC | + SCRIPT_VERIFY_MINIMALDATA | + SCRIPT_VERIFY_NULLDUMMY | + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | + SCRIPT_VERIFY_CLEANSTACK | + SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | + SCRIPT_VERIFY_LOW_S; + +/** For convenience, standard but not mandatory verify flags. */ +static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; + +// Sanity check the magic numbers when we change them +BOOST_STATIC_ASSERT(DEFAULT_BLOCK_MAX_SIZE <= MAX_BLOCK_SIZE); +BOOST_STATIC_ASSERT(DEFAULT_BLOCK_PRIORITY_SIZE <= DEFAULT_BLOCK_MAX_SIZE); + +#endif // BITCOIN_POLICY_H diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 4b6140f4c..b3898bd71 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -13,6 +13,7 @@ #include "main.h" #include "merkleblock.h" #include "net.h" +#include "policy/policy.h" #include "primitives/transaction.h" #include "rpc/server.h" #include "script/script.h" diff --git a/src/script/sign.cpp b/src/script/sign.cpp index a6434e80c..08c896f31 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -5,9 +5,10 @@ #include "script/sign.h" -#include "primitives/transaction.h" #include "key.h" #include "keystore.h" +#include "policy/policy.h" +#include "primitives/transaction.h" #include "script/standard.h" #include "uint256.h" diff --git a/src/script/standard.h b/src/script/standard.h index 0bd2d9ca0..87930e65c 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -41,24 +41,6 @@ extern unsigned nMaxDatacarrierBytes; */ static const unsigned int MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH; -/** - * Standard script verification flags that standard transactions will comply - * with. However scripts violating these flags may still be present in valid - * blocks and we must accept those blocks. - */ -static const unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS | - // SCRIPT_VERIFY_DERSIG is always enforced - SCRIPT_VERIFY_STRICTENC | - SCRIPT_VERIFY_MINIMALDATA | - SCRIPT_VERIFY_NULLDUMMY | - SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | - SCRIPT_VERIFY_CLEANSTACK | - SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | - SCRIPT_VERIFY_LOW_S; - -/** For convenience, standard but not mandatory verify flags. */ -static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; - enum txnouttype { TX_NONSTANDARD, diff --git a/src/test/script_P2SH_tests.cpp b/src/test/script_P2SH_tests.cpp index a01e3e8e4..c3e09444b 100644 --- a/src/test/script_P2SH_tests.cpp +++ b/src/test/script_P2SH_tests.cpp @@ -7,6 +7,7 @@ #include "key.h" #include "keystore.h" #include "main.h" +#include "policy/policy.h" #include "script/script.h" #include "script/script_error.h" #include "script/sign.h" diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 37512d26c..3f14a41a7 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -16,6 +16,7 @@ #include "key_io.h" #include "main.h" #include "net.h" +#include "policy/policy.h" #include "rpc/protocol.h" #include "rpc/server.h" #include "script/script.h" diff --git a/src/zcbenchmarks.cpp b/src/zcbenchmarks.cpp index 40d8a75c9..e064dd586 100644 --- a/src/zcbenchmarks.cpp +++ b/src/zcbenchmarks.cpp @@ -17,6 +17,7 @@ #include "consensus/validation.h" #include "main.h" #include "miner.h" +#include "policy/policy.h" #include "pow.h" #include "rpc/server.h" #include "script/sign.h" From 4884e75c8c378a84dd2a0f83c1d0dbd61bd59085 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sat, 11 Oct 2014 22:41:05 +0000 Subject: [PATCH 2/9] Policy: MOVEONLY: 3 functions to policy.o: - [script/standard.o] IsStandard - [main.o] IsStandardTx - [main.o] AreInputsStandard Also, don't use namespace std in policy.cpp --- src/Makefile.am | 1 + src/main.cpp | 141 ------------------------- src/main.h | 24 ----- src/policy/policy.cpp | 186 +++++++++++++++++++++++++++++++++ src/policy/policy.h | 18 ++++ src/script/standard.cpp | 20 ---- src/script/standard.h | 1 - src/test/multisig_tests.cpp | 2 +- src/test/transaction_tests.cpp | 1 + 9 files changed, 207 insertions(+), 187 deletions(-) create mode 100644 src/policy/policy.cpp diff --git a/src/Makefile.am b/src/Makefile.am index ef9c0ccde..3fb8890b5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -256,6 +256,7 @@ libbitcoin_server_a_SOURCES = \ net.cpp \ noui.cpp \ policy/fees.cpp \ + policy/policy.cpp \ pow.cpp \ rest.cpp \ rpc/blockchain.cpp \ diff --git a/src/main.cpp b/src/main.cpp index 200743075..c42f728ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -673,79 +673,6 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) EXCLUSIVE_LOCKS_REQUIRE return nEvicted; } - -bool IsStandardTx(const CTransaction& tx, string& reason, const CChainParams& chainparams, const int nHeight) -{ - bool overwinterActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_OVERWINTER); - bool saplingActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_SAPLING); - - if (saplingActive) { - // Sapling standard rules apply - if (tx.nVersion > CTransaction::SAPLING_MAX_CURRENT_VERSION || tx.nVersion < CTransaction::SAPLING_MIN_CURRENT_VERSION) { - reason = "sapling-version"; - return false; - } - } else if (overwinterActive) { - // Overwinter standard rules apply - if (tx.nVersion > CTransaction::OVERWINTER_MAX_CURRENT_VERSION || tx.nVersion < CTransaction::OVERWINTER_MIN_CURRENT_VERSION) { - reason = "overwinter-version"; - return false; - } - } else { - // Sprout standard rules apply - if (tx.nVersion > CTransaction::SPROUT_MAX_CURRENT_VERSION || tx.nVersion < CTransaction::SPROUT_MIN_CURRENT_VERSION) { - reason = "version"; - return false; - } - } - - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { - // Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed - // keys. (remember the 520 byte limit on redeemScript size) That works - // out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627 - // bytes of scriptSig, which we round off to 1650 bytes for some minor - // future-proofing. That's also enough to spend a 20-of-20 - // CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not - // considered standard) - if (txin.scriptSig.size() > 1650) { - reason = "scriptsig-size"; - return false; - } - if (!txin.scriptSig.IsPushOnly()) { - reason = "scriptsig-not-pushonly"; - return false; - } - } - - unsigned int nDataOut = 0; - txnouttype whichType; - BOOST_FOREACH(const CTxOut& txout, tx.vout) { - if (!::IsStandard(txout.scriptPubKey, whichType)) { - reason = "scriptpubkey"; - return false; - } - - if (whichType == TX_NULL_DATA) - nDataOut++; - else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { - reason = "bare-multisig"; - return false; - } else if (txout.IsDust(::minRelayTxFee)) { - reason = "dust"; - return false; - } - } - - // only one OP_RETURN txout is permitted - if (nDataOut > 1) { - reason = "multi-op-return"; - return false; - } - - return true; -} - bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) { if (tx.nLockTime == 0) @@ -802,74 +729,6 @@ bool CheckFinalTx(const CTransaction &tx, int flags) return IsFinalTx(tx, nBlockHeight, nBlockTime); } -/** - * Check transaction inputs to mitigate two - * potential denial-of-service attacks: - * - * 1. scriptSigs with extra data stuffed into them, - * not consumed by scriptPubKey (or P2SH script) - * 2. P2SH scripts with a crazy number of expensive - * CHECKSIG/CHECKMULTISIG operations - */ -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, uint32_t consensusBranchId) -{ - if (tx.IsCoinBase()) - return true; // Coinbases don't use vin normally - - for (unsigned int i = 0; i < tx.vin.size(); i++) - { - const CTxOut& prev = mapInputs.GetOutputFor(tx.vin[i]); - - vector > vSolutions; - txnouttype whichType; - // get the scriptPubKey corresponding to this input: - const CScript& prevScript = prev.scriptPubKey; - if (!Solver(prevScript, whichType, vSolutions)) - return false; - int nArgsExpected = ScriptSigArgsExpected(whichType, vSolutions); - if (nArgsExpected < 0) - return false; - - // Transactions with extra stuff in their scriptSigs are - // non-standard. Note that this EvalScript() call will - // be quick, because if there are any operations - // beside "push data" in the scriptSig - // IsStandardTx() will have already returned false - // and this method isn't called. - vector > stack; - if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), consensusBranchId)) - return false; - - if (whichType == TX_SCRIPTHASH) - { - if (stack.empty()) - return false; - CScript subscript(stack.back().begin(), stack.back().end()); - vector > vSolutions2; - txnouttype whichType2; - if (Solver(subscript, whichType2, vSolutions2)) - { - int tmpExpected = ScriptSigArgsExpected(whichType2, vSolutions2); - if (tmpExpected < 0) - return false; - nArgsExpected += tmpExpected; - } - else - { - // Any other Script with less than 15 sigops OK: - unsigned int sigops = subscript.GetSigOpCount(true); - // ... extra data left on the stack after execution is OK, too: - return (sigops <= MAX_P2SH_SIGOPS); - } - } - - if (stack.size() != (unsigned int)nArgsExpected) - return false; - } - - return true; -} - unsigned int GetLegacySigOpCount(const CTransaction& tx) { unsigned int nSigOps = 0; diff --git a/src/main.h b/src/main.h index db60609ff..64055bcac 100644 --- a/src/main.h +++ b/src/main.h @@ -299,25 +299,6 @@ struct CNodeStateStats { CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree); -/** - * Check transaction inputs, and make sure any - * pay-to-script-hash transactions are evaluating IsStandard scripts - * - * Why bother? To avoid denial-of-service attacks; an attacker - * can submit a standard HASH... OP_EQUAL transaction, - * which will get accepted into blocks. The redemption - * script can be anything; an attacker could use a very - * expensive-to-check-upon-redemption script like: - * DUP CHECKSIG DROP ... repeated 100 times... OP_1 - */ - -/** - * Check for standard transaction types - * @param[in] mapInputs Map of previous transactions that have outputs we're spending - * @return True if all inputs (scriptSigs) use only standard transaction forms - */ -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, uint32_t consensusBranchId); - /** * Count ECDSA signature operations the old-fashioned (pre-0.6) way * @return number of sigops this transaction's outputs will produce when spent @@ -359,11 +340,6 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight); bool CheckTransaction(const CTransaction& tx, CValidationState& state, libzcash::ProofVerifier& verifier); bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidationState &state); -/** Check for standard transaction types - * @return True if all outputs (scriptPubKeys) use only standard transaction forms - */ -bool IsStandardTx(const CTransaction& tx, std::string& reason, const CChainParams& chainparams, int nHeight = 0); - namespace Consensus { /** diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp new file mode 100644 index 000000000..ea88d3aaa --- /dev/null +++ b/src/policy/policy.cpp @@ -0,0 +1,186 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2014 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// NOTE: This file is intended to be customised by the end user, and includes only local node policy logic + +#include "policy/policy.h" + +#include "main.h" +#include "tinyformat.h" +#include "util.h" +#include "utilstrencodings.h" + +#include + + /** + * Check transaction inputs to mitigate two + * potential denial-of-service attacks: + * + * 1. scriptSigs with extra data stuffed into them, + * not consumed by scriptPubKey (or P2SH script) + * 2. P2SH scripts with a crazy number of expensive + * CHECKSIG/CHECKMULTISIG operations + * + * Check transaction inputs, and make sure any + * pay-to-script-hash transactions are evaluating IsStandard scripts + * + * Why bother? To avoid denial-of-service attacks; an attacker + * can submit a standard HASH... OP_EQUAL transaction, + * which will get accepted into blocks. The redemption + * script can be anything; an attacker could use a very + * expensive-to-check-upon-redemption script like: + * DUP CHECKSIG DROP ... repeated 100 times... OP_1 + */ + +bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType) +{ + std::vector > vSolutions; + if (!Solver(scriptPubKey, whichType, vSolutions)) + return false; + + if (whichType == TX_MULTISIG) + { + unsigned char m = vSolutions.front()[0]; + unsigned char n = vSolutions.back()[0]; + // Support up to x-of-3 multisig txns as standard + if (n < 1 || n > 3) + return false; + if (m < 1 || m > n) + return false; + } + + return whichType != TX_NONSTANDARD; +} + +bool IsStandardTx(const CTransaction& tx, std::string& reason, const CChainParams& chainparams, const int nHeight) +{ + bool overwinterActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_OVERWINTER); + bool saplingActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_SAPLING); + + if (saplingActive) { + // Sapling standard rules apply + if (tx.nVersion > CTransaction::SAPLING_MAX_CURRENT_VERSION || tx.nVersion < CTransaction::SAPLING_MIN_CURRENT_VERSION) { + reason = "sapling-version"; + return false; + } + } else if (overwinterActive) { + // Overwinter standard rules apply + if (tx.nVersion > CTransaction::OVERWINTER_MAX_CURRENT_VERSION || tx.nVersion < CTransaction::OVERWINTER_MIN_CURRENT_VERSION) { + reason = "overwinter-version"; + return false; + } + } else { + // Sprout standard rules apply + if (tx.nVersion > CTransaction::SPROUT_MAX_CURRENT_VERSION || tx.nVersion < CTransaction::SPROUT_MIN_CURRENT_VERSION) { + reason = "version"; + return false; + } + } + + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + // Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed + // keys. (remember the 520 byte limit on redeemScript size) That works + // out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627 + // bytes of scriptSig, which we round off to 1650 bytes for some minor + // future-proofing. That's also enough to spend a 20-of-20 + // CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not + // considered standard) + if (txin.scriptSig.size() > 1650) { + reason = "scriptsig-size"; + return false; + } + if (!txin.scriptSig.IsPushOnly()) { + reason = "scriptsig-not-pushonly"; + return false; + } + } + + unsigned int nDataOut = 0; + txnouttype whichType; + BOOST_FOREACH(const CTxOut& txout, tx.vout) { + if (!::IsStandard(txout.scriptPubKey, whichType)) { + reason = "scriptpubkey"; + return false; + } + + if (whichType == TX_NULL_DATA) + nDataOut++; + else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { + reason = "bare-multisig"; + return false; + } else if (txout.IsDust(::minRelayTxFee)) { + reason = "dust"; + return false; + } + } + + // only one OP_RETURN txout is permitted + if (nDataOut > 1) { + reason = "multi-op-return"; + return false; + } + + return true; +} + +bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, uint32_t consensusBranchId) +{ + if (tx.IsCoinBase()) + return true; // Coinbases don't use vin normally + + for (unsigned int i = 0; i < tx.vin.size(); i++) + { + const CTxOut& prev = mapInputs.GetOutputFor(tx.vin[i]); + + std::vector > vSolutions; + txnouttype whichType; + // get the scriptPubKey corresponding to this input: + const CScript& prevScript = prev.scriptPubKey; + if (!Solver(prevScript, whichType, vSolutions)) + return false; + int nArgsExpected = ScriptSigArgsExpected(whichType, vSolutions); + if (nArgsExpected < 0) + return false; + + // Transactions with extra stuff in their scriptSigs are + // non-standard. Note that this EvalScript() call will + // be quick, because if there are any operations + // beside "push data" in the scriptSig + // IsStandardTx() will have already returned false + // and this method isn't called. + std::vector > stack; + if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), consensusBranchId)) + return false; + + if (whichType == TX_SCRIPTHASH) + { + if (stack.empty()) + return false; + CScript subscript(stack.back().begin(), stack.back().end()); + std::vector > vSolutions2; + txnouttype whichType2; + if (Solver(subscript, whichType2, vSolutions2)) + { + int tmpExpected = ScriptSigArgsExpected(whichType2, vSolutions2); + if (tmpExpected < 0) + return false; + nArgsExpected += tmpExpected; + } + else + { + // Any other Script with less than 15 sigops OK: + unsigned int sigops = subscript.GetSigOpCount(true); + // ... extra data left on the stack after execution is OK, too: + return (sigops <= MAX_P2SH_SIGOPS); + } + } + + if (stack.size() != (unsigned int)nArgsExpected) + return false; + } + + return true; +} diff --git a/src/policy/policy.h b/src/policy/policy.h index 5641bdf2d..6f6bf092a 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -10,6 +10,11 @@ #include "script/interpreter.h" #include "script/standard.h" +#include + +class CChainParams; +class CCoinsViewCache; + /** Default for -blockmaxsize and -blockminsize, which control the range of sizes the mining code will create **/ static const unsigned int DEFAULT_BLOCK_MAX_SIZE = MAX_BLOCK_SIZE; static const unsigned int DEFAULT_BLOCK_MIN_SIZE = 0; @@ -41,4 +46,17 @@ static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_ BOOST_STATIC_ASSERT(DEFAULT_BLOCK_MAX_SIZE <= MAX_BLOCK_SIZE); BOOST_STATIC_ASSERT(DEFAULT_BLOCK_PRIORITY_SIZE <= DEFAULT_BLOCK_MAX_SIZE); +bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType); + /** + * Check for standard transaction types + * @return True if all outputs (scriptPubKeys) use only standard transaction forms + */ +bool IsStandardTx(const CTransaction& tx, std::string& reason, const CChainParams& chainparams, int nHeight = 0); + /** + * Check for standard transaction types + * @param[in] mapInputs Map of previous transactions that have outputs we're spending + * @return True if all inputs (scriptSigs) use only standard transaction forms + */ +bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, uint32_t consensusBranchId); + #endif // BITCOIN_POLICY_H diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 08bcaf4fa..116f4f9d4 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -181,26 +181,6 @@ int ScriptSigArgsExpected(txnouttype t, const std::vector vSolutions; - if (!Solver(scriptPubKey, whichType, vSolutions)) - return false; - - if (whichType == TX_MULTISIG) - { - unsigned char m = vSolutions.front()[0]; - unsigned char n = vSolutions.back()[0]; - // Support up to x-of-3 multisig txns as standard - if (n < 1 || n > 3) - return false; - if (m < 1 || m > n) - return false; - } - - return whichType != TX_NONSTANDARD; -} - bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) { vector vSolutions; diff --git a/src/script/standard.h b/src/script/standard.h index 87930e65c..8c804f1b1 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -74,7 +74,6 @@ const char* GetTxnOutputType(txnouttype t); bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector >& vSolutionsRet); int ScriptSigArgsExpected(txnouttype t, const std::vector >& vSolutions); -bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType); bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet); bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 506575f1a..f61d54734 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -5,7 +5,7 @@ #include "consensus/upgrades.h" #include "key.h" #include "keystore.h" -#include "main.h" +#include "policy/policy.h" #include "script/script.h" #include "script/script_error.h" #include "script/interpreter.h" diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 085b3d039..529d469a7 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -15,6 +15,7 @@ #include "key.h" #include "keystore.h" #include "main.h" +#include "policy/policy.h" #include "script/script.h" #include "script/script_error.h" #include "script/sign.h" From cf6cf56a685f6416193dc1dd4e9c9894a90a8092 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Mon, 13 Oct 2014 10:14:58 -0400 Subject: [PATCH 3/9] Make TX_SCRIPTHASH clear vSolutionsRet first Previously unlike other transaction types the TX_SCRIPTHASH would not clear vSolutionsRet, which means that unlike other transaction types if it was called twice in a row you would get the result of the previous invocation as well. --- src/script/standard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 116f4f9d4..9bd37ac40 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -59,6 +59,8 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector Date: Tue, 4 Nov 2014 12:38:56 -0500 Subject: [PATCH 4/9] Add IsPushOnly(const_iterator pc) Allows IsPushOnly() to be applied to just part of the script for OP_RETURN outputs. --- src/script/script.cpp | 8 ++++++-- src/script/script.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/script/script.cpp b/src/script/script.cpp index e2b2be3a5..51839a08b 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -223,9 +223,8 @@ bool CScript::IsPayToScriptHash() const (*this)[22] == OP_EQUAL); } -bool CScript::IsPushOnly() const +bool CScript::IsPushOnly(const_iterator pc) const { - const_iterator pc = begin(); while (pc < end()) { opcodetype opcode; @@ -241,6 +240,11 @@ bool CScript::IsPushOnly() const return true; } +bool CScript::IsPushOnly() const +{ + return this->IsPushOnly(begin()); +} + // insightexplorer CScript::ScriptType CScript::GetType() const { diff --git a/src/script/script.h b/src/script/script.h index 3b886e581..16787e72e 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -579,6 +579,7 @@ public: uint160 AddressHash() const; /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ + bool IsPushOnly(const_iterator pc) const; bool IsPushOnly() const; /** From 602960e5b59cee7755dc389cfd6379eea7b56605 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Mon, 13 Oct 2014 10:18:05 -0400 Subject: [PATCH 5/9] Accept any sequence of PUSHDATAs in OP_RETURN outputs Previously only one PUSHDATA was allowed, needlessly limiting applications such as matching OP_RETURN contents with bloom filters that operate on a per-PUSHDATA level. Now any combination that passes IsPushOnly() is allowed, so long as the total size of the scriptPubKey is less than 42 bytes. (unchanged modulo non-minimal PUSHDATA encodings) Also, this fixes the odd bug where previously the PUSHDATA could be replaced by any single opcode, even sigops consuming opcodes such as CHECKMULTISIG. (20 sigops!) --- src/policy/policy.cpp | 4 +++- src/script/script.cpp | 2 +- src/script/script.h | 1 - src/script/standard.cpp | 21 ++++++++++----------- src/script/standard.h | 2 +- src/test/transaction_tests.cpp | 21 +++++++++++++++++++-- 6 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index ea88d3aaa..1d10f5d7c 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -49,7 +49,9 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType) return false; if (m < 1 || m > n) return false; - } + } else if (whichType == TX_NULL_DATA && + (!GetBoolArg("-datacarrier", true) || scriptPubKey.size() > nMaxDatacarrierBytes)) + return false; return whichType != TX_NONSTANDARD; } diff --git a/src/script/script.cpp b/src/script/script.cpp index 51839a08b..6cbe17175 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -144,7 +144,7 @@ const char* GetOpName(opcodetype opcode) case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; // Note: - // The template matching params OP_SMALLDATA/etc are defined in opcodetype enum + // The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum // as kind of implementation hack, they are *NOT* real opcodes. If found in real // Script, just let the default: case deal with them. diff --git a/src/script/script.h b/src/script/script.h index 16787e72e..2109076af 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -173,7 +173,6 @@ enum opcodetype // template matching params - OP_SMALLDATA = 0xf9, OP_SMALLINTEGER = 0xfa, OP_PUBKEYS = 0xfb, OP_PUBKEYHASH = 0xfd, diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 9bd37ac40..f7bf9e1b7 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -52,11 +52,6 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector= 1 && scriptPubKey[0] == OP_RETURN && scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) { + typeRet = TX_NULL_DATA; + return true; + } + // Scan templates const CScript& script1 = scriptPubKey; BOOST_FOREACH(const PAIRTYPE(txnouttype, CScript)& tplate, mTemplates) @@ -143,12 +148,6 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector nMaxDatacarrierBytes) - break; - } else if (opcode1 != opcode2 || vch1 != vch2) { // Others must match exactly diff --git a/src/script/standard.h b/src/script/standard.h index 8c804f1b1..1022075c4 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -27,7 +27,7 @@ public: CScriptID(const uint160& in) : uint160(in) {} }; -static const unsigned int MAX_OP_RETURN_RELAY = 80; //!< bytes +static const unsigned int MAX_OP_RETURN_RELAY = 83; //!< bytes (+1 for OP_RETURN, +2 for the pushdata opcodes) extern bool fAcceptDatacarrier; extern unsigned nMaxDatacarrierBytes; diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 529d469a7..591a6409b 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -779,12 +779,29 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout[0].scriptPubKey = CScript() << OP_1; BOOST_CHECK(!IsStandardTx(t, reason, chainparams)); - // 80-byte TX_NULL_DATA (standard) + // MAX_OP_RETURN_RELAY-byte TX_NULL_DATA (standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); + BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY, t.vout[0].scriptPubKey.size()); BOOST_CHECK(IsStandardTx(t, reason, chainparams)); - // 81-byte TX_NULL_DATA (non-standard) + // MAX_OP_RETURN_RELAY+1-byte TX_NULL_DATA (non-standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800"); + BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY + 1, t.vout[0].scriptPubKey.size()); + BOOST_CHECK(!IsStandardTx(t, reason, chainparams)); + + // Data payload can be encoded in any way... + t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex(""); + BOOST_CHECK(IsStandardTx(t, reason, chainparams)); + t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("00") << ParseHex("01"); + BOOST_CHECK(IsStandardTx(t, reason, chainparams)); + // OP_RESERVED *is* considered to be a PUSHDATA type opcode by IsPushOnly()! + t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RESERVED << -1 << 0 << ParseHex("01") << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 15 << 16; + BOOST_CHECK(IsStandardTx(t, reason, chainparams)); + t.vout[0].scriptPubKey = CScript() << OP_RETURN << 0 << ParseHex("01") << 2 << ParseHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + BOOST_CHECK(IsStandardTx(t, reason, chainparams)); + + // ...so long as it only contains PUSHDATA's + t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RETURN; BOOST_CHECK(!IsStandardTx(t, reason, chainparams)); // TX_NULL_DATA w/o PUSHDATA From 3c8c8009db282e4d86719a2ea681ed1c2fb5cfab Mon Sep 17 00:00:00 2001 From: Jim Posen Date: Tue, 15 Aug 2017 17:55:26 -0700 Subject: [PATCH 6/9] Comments: More comments on functions/globals in standard.h. Zcash: Excludes comments for variables and functions we don't have: - GetScriptForRawPubKey (bitcoin/bitcoin#6415) - GetScriptForWitness (bitcoin/bitcoin#8149) --- src/script/standard.cpp | 5 +--- src/script/standard.h | 52 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index f7bf9e1b7..a21ff58ac 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -35,10 +35,7 @@ const char* GetTxnOutputType(txnouttype t) return NULL; } -/** - * Return public keys or hashes from scriptPubKey, for 'standard' transaction types. - */ -bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector >& vSolutionsRet) +bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector >& vSolutionsRet) { // Templates static multimap mTemplates; diff --git a/src/script/standard.h b/src/script/standard.h index 1022075c4..7c1832f0b 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -27,8 +27,19 @@ public: CScriptID(const uint160& in) : uint160(in) {} }; -static const unsigned int MAX_OP_RETURN_RELAY = 83; //!< bytes (+1 for OP_RETURN, +2 for the pushdata opcodes) +/** + * Default setting for nMaxDatacarrierBytes. 80 bytes of data, +1 for OP_RETURN, + * +2 for the pushdata opcodes. + */ +static const unsigned int MAX_OP_RETURN_RELAY = 83; + +/** + * A data carrying output is an unspendable output containing data. The script + * type is designated as TX_NULL_DATA. + */ extern bool fAcceptDatacarrier; + +/** Maximum size of TX_NULL_DATA scripts that this node considers standard. */ extern unsigned nMaxDatacarrierBytes; /** @@ -49,7 +60,7 @@ enum txnouttype TX_PUBKEYHASH, TX_SCRIPTHASH, TX_MULTISIG, - TX_NULL_DATA, + TX_NULL_DATA, //!< unspendable OP_RETURN script that carries data }; class CNoDestination { @@ -58,7 +69,7 @@ public: friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; } }; -/** +/** * A txout script template with a specific destination. It is either: * * CNoDestination: no destination set * * CKeyID: TX_PUBKEYHASH destination @@ -70,15 +81,50 @@ typedef boost::variant CTxDestination; /** Check whether a CTxDestination is a CNoDestination. */ bool IsValidDestination(const CTxDestination& dest); +/** Get the name of a txnouttype as a C string, or nullptr if unknown. */ const char* GetTxnOutputType(txnouttype t); +/** + * Parse a scriptPubKey and identify script type for standard scripts. If + * successful, returns script type and parsed pubkeys or hashes, depending on + * the type. For example, for a P2SH script, vSolutionsRet will contain the + * script hash, for P2PKH it will contain the key hash, etc. + * + * @param[in] scriptPubKey Script to parse + * @param[out] typeRet The script type + * @param[out] vSolutionsRet Vector of parsed pubkeys and hashes + * @return True if script matches standard template + */ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector >& vSolutionsRet); int ScriptSigArgsExpected(txnouttype t, const std::vector >& vSolutions); + +/** + * Parse a standard scriptPubKey for the destination address. Assigns result to + * the addressRet parameter and returns true if successful. For multisig + * scripts, instead use ExtractDestinations. Currently only works for P2PK, + * P2PKH, and P2SH scripts. + */ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet); + +/** + * Parse a standard scriptPubKey with one or more destination addresses. For + * multisig scripts, this populates the addressRet vector with the pubkey IDs + * and nRequiredRet with the n required to spend. For other destinations, + * addressRet is populated with a single value and nRequiredRet is set to 1. + * Returns true if successful. Currently does not extract address from + * pay-to-witness scripts. + */ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); +/** + * Generate a Bitcoin scriptPubKey for the given CTxDestination. Returns a P2PKH + * script for a CKeyID destination, a P2SH script for a CScriptID, and an empty + * script for CNoDestination. + */ CScript GetScriptForDestination(const CTxDestination& dest); CScript GetScriptForRawPubKey(const CPubKey& pubkey); + +/** Generate a multisig script. */ CScript GetScriptForMultisig(int nRequired, const std::vector& keys); // insightexplorer From 82beb189011c6ee5d9f597ca23ab0568081fd741 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Fri, 16 Feb 2018 20:28:03 +0000 Subject: [PATCH 7/9] Assert CPubKey::ValidLength to the pubkey's header-relevent size Previously this was an inline test where the specificity was probably judged overly specific. As a class method it makes sense to maintain consistency. And replace some magic values with their constant equivalents. Zcash: Excludes changes to the following functions we don't have: - ExtractPubKey (bitcoin/bitcoin#6415) - IsCompressedPubKey (bitcoin/bitcoin#8499) --- src/pubkey.h | 5 +++++ src/script/interpreter.cpp | 6 +++--- src/script/standard.cpp | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pubkey.h b/src/pubkey.h index 871f51179..c32a30d1e 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -70,6 +70,11 @@ private: } public: + + bool static ValidSize(const std::vector &vch) { + return vch.size() > 0 && GetLen(vch[0]) == vch.size(); + } + //! Construct an invalid public key. CPubKey() { diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index b3cad00a8..7a8376eea 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -65,17 +65,17 @@ static inline void popstack(vector& stack) } bool static IsCompressedOrUncompressedPubKey(const valtype &vchPubKey) { - if (vchPubKey.size() < 33) { + if (vchPubKey.size() < CPubKey::COMPRESSED_PUBLIC_KEY_SIZE) { // Non-canonical public key: too short return false; } if (vchPubKey[0] == 0x04) { - if (vchPubKey.size() != 65) { + if (vchPubKey.size() != CPubKey::PUBLIC_KEY_SIZE) { // Non-canonical public key: invalid length for uncompressed key return false; } } else if (vchPubKey[0] == 0x02 || vchPubKey[0] == 0x03) { - if (vchPubKey.size() != 33) { + if (vchPubKey.size() != CPubKey::COMPRESSED_PUBLIC_KEY_SIZE) { // Non-canonical public key: invalid length for compressed key return false; } diff --git a/src/script/standard.cpp b/src/script/standard.cpp index a21ff58ac..b4fa0994a 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -110,7 +110,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector= 33 && vch1.size() <= 65) + while (CPubKey::ValidSize(vch1)) { vSolutionsRet.push_back(vch1); if (!script1.GetOp(pc1, opcode1, vch1)) @@ -124,7 +124,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector 65) + if (!CPubKey::ValidSize(vch1)) break; vSolutionsRet.push_back(vch1); } From 2cb14d79b15447ab180ad1ab0d79f3f57535e95e Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 8 May 2018 20:24:06 -0700 Subject: [PATCH 8/9] Remove template matching and pseudo opcodes The current code contains a rather complex script template matching engine, which is only used for 3 particular script types (P2PK, P2PKH, multisig). The first two of these are trivial to match for otherwise, and a specialized matcher for multisig is both more compact and more efficient than a generic one. The goal is being more flexible, so that for example larger standard multisigs inside SegWit outputs are more easy to implement. As a side-effect, it also gets rid of the pseudo opcodes hack. --- src/pubkey.h | 8 +-- src/script/script.cpp | 5 -- src/script/script.h | 7 -- src/script/standard.cpp | 155 +++++++++++++++++----------------------- 4 files changed, 69 insertions(+), 106 deletions(-) diff --git a/src/pubkey.h b/src/pubkey.h index c32a30d1e..2f5bd6aaa 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -33,10 +33,10 @@ public: /** * secp256k1: */ - static const unsigned int PUBLIC_KEY_SIZE = 65; - static const unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33; - static const unsigned int SIGNATURE_SIZE = 72; - static const unsigned int COMPACT_SIGNATURE_SIZE = 65; + static constexpr unsigned int PUBLIC_KEY_SIZE = 65; + static constexpr unsigned int COMPRESSED_PUBLIC_KEY_SIZE = 33; + static constexpr unsigned int SIGNATURE_SIZE = 72; + static constexpr unsigned int COMPACT_SIGNATURE_SIZE = 65; /** * see www.keylength.com * script supports up to 75 for single byte push diff --git a/src/script/script.cpp b/src/script/script.cpp index 6cbe17175..bc904fd19 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -143,11 +143,6 @@ const char* GetOpName(opcodetype opcode) case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; - // Note: - // The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum - // as kind of implementation hack, they are *NOT* real opcodes. If found in real - // Script, just let the default: case deal with them. - default: return "OP_UNKNOWN"; } diff --git a/src/script/script.h b/src/script/script.h index 2109076af..008efae54 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -171,13 +171,6 @@ enum opcodetype OP_NOP9 = 0xb8, OP_NOP10 = 0xb9, - - // template matching params - OP_SMALLINTEGER = 0xfa, - OP_PUBKEYS = 0xfb, - OP_PUBKEYHASH = 0xfd, - OP_PUBKEY = 0xfe, - OP_INVALIDOPCODE = 0xff, }; diff --git a/src/script/standard.cpp b/src/script/standard.cpp index b4fa0994a..f84406075 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -35,22 +35,54 @@ const char* GetTxnOutputType(txnouttype t) return NULL; } +static bool MatchPayToPubkey(const CScript& script, valtype& pubkey) +{ + if (script.size() == CPubKey::PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) { + pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::PUBLIC_KEY_SIZE + 1); + return CPubKey::ValidSize(pubkey); + } + if (script.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 2 && script[0] == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE && script.back() == OP_CHECKSIG) { + pubkey = valtype(script.begin() + 1, script.begin() + CPubKey::COMPRESSED_PUBLIC_KEY_SIZE + 1); + return CPubKey::ValidSize(pubkey); + } + return false; +} + +static bool MatchPayToPubkeyHash(const CScript& script, valtype& pubkeyhash) +{ + if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160 && script[2] == 20 && script[23] == OP_EQUALVERIFY && script[24] == OP_CHECKSIG) { + pubkeyhash = valtype(script.begin () + 3, script.begin() + 23); + return true; + } + return false; +} + +/** Test for "small positive integer" script opcodes - OP_1 through OP_16. */ +static constexpr bool IsSmallInteger(opcodetype opcode) +{ + return opcode >= OP_1 && opcode <= OP_16; +} + +static bool MatchMultisig(const CScript& script, unsigned int& required, std::vector& pubkeys) +{ + opcodetype opcode; + valtype data; + CScript::const_iterator it = script.begin(); + if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false; + + if (!script.GetOp(it, opcode, data) || !IsSmallInteger(opcode)) return false; + required = CScript::DecodeOP_N(opcode); + while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) { + pubkeys.emplace_back(std::move(data)); + } + if (!IsSmallInteger(opcode)) return false; + unsigned int keys = CScript::DecodeOP_N(opcode); + if (pubkeys.size() != keys || keys < required) return false; + return (it + 1 == script.end()); +} + bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector >& vSolutionsRet) { - // Templates - static multimap mTemplates; - if (mTemplates.empty()) - { - // Standard tx, sender provides pubkey, receiver adds signature - mTemplates.insert(make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG)); - - // Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey - mTemplates.insert(make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG)); - - // Sender provides N pubkeys, receivers provides M signatures - mTemplates.insert(make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG)); - } - vSolutionsRet.clear(); // Shortcut for pay-to-script-hash, which are more constrained than the other types: @@ -73,84 +105,27 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector data; + if (MatchPayToPubkey(scriptPubKey, data)) { + typeRet = TX_PUBKEY; + vSolutionsRet.push_back(std::move(data)); + return true; + } - opcodetype opcode1, opcode2; - vector vch1, vch2; + if (MatchPayToPubkeyHash(scriptPubKey, data)) { + typeRet = TX_PUBKEYHASH; + vSolutionsRet.push_back(std::move(data)); + return true; + } - // Compare - CScript::const_iterator pc1 = script1.begin(); - CScript::const_iterator pc2 = script2.begin(); - while (true) - { - if (pc1 == script1.end() && pc2 == script2.end()) - { - // Found a match - typeRet = tplate.first; - if (typeRet == TX_MULTISIG) - { - // Additional checks for TX_MULTISIG: - unsigned char m = vSolutionsRet.front()[0]; - unsigned char n = vSolutionsRet.back()[0]; - if (m < 1 || n < 1 || m > n || vSolutionsRet.size()-2 != n) - return false; - } - return true; - } - if (!script1.GetOp(pc1, opcode1, vch1)) - break; - if (!script2.GetOp(pc2, opcode2, vch2)) - break; - - // Template matching opcodes: - if (opcode2 == OP_PUBKEYS) - { - while (CPubKey::ValidSize(vch1)) - { - vSolutionsRet.push_back(vch1); - if (!script1.GetOp(pc1, opcode1, vch1)) - break; - } - if (!script2.GetOp(pc2, opcode2, vch2)) - break; - // Normal situation is to fall through - // to other if/else statements - } - - if (opcode2 == OP_PUBKEY) - { - if (!CPubKey::ValidSize(vch1)) - break; - vSolutionsRet.push_back(vch1); - } - else if (opcode2 == OP_PUBKEYHASH) - { - if (vch1.size() != sizeof(uint160)) - break; - vSolutionsRet.push_back(vch1); - } - else if (opcode2 == OP_SMALLINTEGER) - { // Single-byte small integer pushed onto vSolutions - if (opcode1 == OP_0 || - (opcode1 >= OP_1 && opcode1 <= OP_16)) - { - char n = (char)CScript::DecodeOP_N(opcode1); - vSolutionsRet.push_back(valtype(1, n)); - } - else - break; - } - else if (opcode1 != opcode2 || vch1 != vch2) - { - // Others must match exactly - break; - } - } + unsigned int required; + std::vector> keys; + if (MatchMultisig(scriptPubKey, required, keys)) { + typeRet = TX_MULTISIG; + vSolutionsRet.push_back({static_cast(required)}); // safe as required is in range 1..16 + vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end()); + vSolutionsRet.push_back({static_cast(keys.size())}); // safe as size is in range 1..16 + return true; } vSolutionsRet.clear(); From 31e1ea63e1fdd6095e370117b91ed64faee0e35b Mon Sep 17 00:00:00 2001 From: str4d Date: Tue, 17 Dec 2019 11:05:28 -0600 Subject: [PATCH 9/9] Apply suggestions from code review Co-Authored-By: Daira Hopwood --- src/policy/policy.cpp | 2 +- src/script/standard.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 1d10f5d7c..a5306e98b 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -20,7 +20,7 @@ * * 1. scriptSigs with extra data stuffed into them, * not consumed by scriptPubKey (or P2SH script) - * 2. P2SH scripts with a crazy number of expensive + * 2. P2SH scripts with very many expensive * CHECKSIG/CHECKMULTISIG operations * * Check transaction inputs, and make sure any diff --git a/src/script/standard.h b/src/script/standard.h index 7c1832f0b..3558f6674 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -111,8 +111,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) * multisig scripts, this populates the addressRet vector with the pubkey IDs * and nRequiredRet with the n required to spend. For other destinations, * addressRet is populated with a single value and nRequiredRet is set to 1. - * Returns true if successful. Currently does not extract address from - * pay-to-witness scripts. + * Returns true if successful. */ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet);