From cf5d16755d46cd4b46e7abe6c71b535393e3b70c Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 18 Jan 2016 20:35:04 -0800 Subject: [PATCH] Add CScript::IsWithdrawLock() (which is standard) --- src/policy/policy.cpp | 24 ++++--- src/script/interpreter.cpp | 3 +- src/script/script.cpp | 137 ++++++++++++++++++++++++++++++++++++- src/script/script.h | 18 +++++ 4 files changed, 169 insertions(+), 13 deletions(-) diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index ae42b2bd74..eef0b57d10 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -75,17 +75,6 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnes 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; @@ -129,6 +118,19 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) { const CTxOut& prev = mapInputs.GetOutputFor(tx.vin[i]); + if (prev.scriptPubKey.IsWithdrawLock()) + continue; + + // 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 (tx.vin[i].scriptSig.size() > 1650) + return false; + std::vector > vSolutions; txnouttype whichType; // get the scriptPubKey corresponding to this input: diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 836cf9ee35..f71f380b91 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -426,8 +426,9 @@ bool EvalScript(vector >& stack, const CScript& script, un break; } - case OP_NOP1: case OP_NOP4: case OP_NOP5: + case OP_NOP1: case OP_NOP5: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: + case OP_WITHDRAWPROOFVERIFY: { if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS); diff --git a/src/script/script.cpp b/src/script/script.cpp index ddf6775569..dc7ceaee9a 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -5,8 +5,14 @@ #include "script.h" +#include + +#include "hash.h" +#include "primitives/transaction.h" +#include "streams.h" #include "tinyformat.h" #include "utilstrencodings.h" +#include "version.h" using namespace std; @@ -133,7 +139,6 @@ const char* GetOpName(opcodetype opcode) case OP_NOP1 : return "OP_NOP1"; case OP_CHECKLOCKTIMEVERIFY : return "OP_CHECKLOCKTIMEVERIFY"; case OP_CHECKSEQUENCEVERIFY : return "OP_CHECKSEQUENCEVERIFY"; - case OP_NOP4 : return "OP_NOP4"; case OP_NOP5 : return "OP_NOP5"; case OP_NOP6 : return "OP_NOP6"; case OP_NOP7 : return "OP_NOP7"; @@ -141,6 +146,9 @@ const char* GetOpName(opcodetype opcode) case OP_NOP9 : return "OP_NOP9"; case OP_NOP10 : return "OP_NOP10"; + // sidechains/withdraw-proofs + case OP_WITHDRAWPROOFVERIFY : return "OP_WITHDRAWPROOFVERIFY"; + case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; // Note: @@ -201,6 +209,133 @@ unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const return subscript.GetSigOpCount(true); } +bool CScript::IsWithdrawProof() const +{ + // Format is xN xM + // for at least 6 total pushes + // Here we simply check that the script is push-only and has at least 6 pushes. + // The output must be P2SH to the appropriate script + const_iterator pc = begin(); + opcodetype opcode; + uint32_t push_count = 0; + while (pc < end()) + { + if (!GetOp(pc, opcode)) + return false; + if (opcode > OP_16 || opcode == OP_RESERVED) + return false; + push_count++; + } + return push_count >= 6; +} + +bool CScript::IsWithdrawLock() const +{ + // Locks look like [ OP_DROP] OP_WITHDRAWPROOFVERIFY + // This function must return true for an OP_WITHDRAWPROOFVERIFY opcode to execute. + // We require all pushes be in their minimal form, to make inspection of + // withdraw locks a purely byte-matching affair. + const_iterator pc = begin(); + vector data; + opcodetype opcode; + + if (!GetOp(pc, opcode, data)) + return false; + if (opcode == 24 && data.size() == 24) { // 4 byte type + 20 byte destination is suggested + if (!GetOp(pc, opcode, data) || opcode != OP_DROP || data.size() != 0) + return false; + + if (!GetOp(pc, opcode, data)) + return false; + } + + if (opcode != 32 || data.size() != 32) + return false; + + if (!GetOp(pc, opcode, data) || opcode != OP_WITHDRAWPROOFVERIFY || data.size() != 0) + return false; + + if (GetOp(pc, opcode)) + return false; + + return true; +} + +uint256 CScript::GetWithdrawLockGenesisHash() const +{ + assert(IsWithdrawLock()); + + const_iterator pc = begin(); + opcodetype opcode; + vector vchgenesishash; + + assert(GetOp(pc, opcode, vchgenesishash)); + if (vchgenesishash.size() != 32) { + assert(GetOp(pc, opcode) && opcode == OP_DROP); + assert(GetOp(pc, opcode, vchgenesishash)); + } + assert(vchgenesishash.size() == 32); + return uint256(vchgenesishash); +} + +static bool PopWithdrawPush(vector >& pushes, vector *read=NULL) { + if (pushes.empty()) + return false; + int pushCount = CScriptNum(pushes.back(), false).getint(); + pushes.pop_back(); + if (pushCount < 0 || pushCount > 2000 || pushes.size() < size_t(pushCount)) + return false; + for (int i = pushCount; i > 0; i--) { + if (i != 1 && pushes[pushes.size() - i].size() != 520) + return false; + if (read != NULL) { + const vector &push = pushes[pushes.size() - i]; + read->insert(read->end(), push.begin(), push.end()); + } + } + for (int i = 0; i < pushCount; i++) + pushes.pop_back(); + return true; +} + +COutPoint CScript::GetWithdrawSpent() const +{ + assert(IsWithdrawProof()); + + try { + const_iterator pc = begin(); + opcodetype opcode; + + // We have to read the script from back-to-front, so we stack-ize it + vector > pushes; + pushes.reserve(6); + while (pc < end()) { + pushes.push_back(vector()); + assert(GetOp(pc, opcode, pushes.back())); + if (opcode <= OP_16 && opcode >= OP_1) + pushes.back().push_back(opcode - OP_1 + 1); + else if (opcode == OP_1NEGATE) + pushes.back().push_back(0x81); + } + + int ntxOut = CScriptNum(pushes.back(), false).getint(); + pushes.pop_back(); + + vector vTx; + if (!PopWithdrawPush(pushes, &vTx)) + return COutPoint(); + CTransaction tx; + CDataStream(vTx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_BITCOIN_BLOCK_OR_TX) >> tx; + + if (ntxOut < 0 || (unsigned int)ntxOut >= tx.vout.size()) + return COutPoint(); + + return COutPoint(tx.GetHash(), ntxOut); + } catch (std::exception& e) { + return COutPoint(); + } +} + bool CScript::IsPayToScriptHash() const { // Extra-fast test for pay-to-script-hash CScripts: diff --git a/src/script/script.h b/src/script/script.h index 278774d32e..de20b32867 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -29,6 +29,8 @@ static const int MAX_PUBKEYS_PER_MULTISIG = 20; // Maximum script length in bytes static const int MAX_SCRIPT_SIZE = 10000; +class uint256; +class COutPoint; // Threshold for nLockTime: below this value it is interpreted as block number, // otherwise as UNIX timestamp. @@ -170,6 +172,7 @@ enum opcodetype OP_CHECKSEQUENCEVERIFY = 0xb2, OP_NOP3 = OP_CHECKSEQUENCEVERIFY, OP_NOP4 = 0xb3, + OP_WITHDRAWPROOFVERIFY = OP_NOP4, OP_NOP5 = 0xb4, OP_NOP6 = 0xb5, OP_NOP7 = 0xb6, @@ -623,6 +626,21 @@ public: bool IsPayToScriptHash() const; bool IsPayToWitnessScriptHash() const; bool IsWitnessProgram(int& version, std::vector& program) const; + /** + * Returns true if this is a withdraw-lock scriptPubKey. + * Note that a withdraw-lock could be a plan [re-]lock output *or* a + * x-chain transfer lock. + */ + bool IsWithdrawLock() const; + + /** Returns true if this is a proof-of-withdraw, spending an IsWithdrawLock */ + bool IsWithdrawProof() const; + + /** Get the withdraw output spent, asserting IsWithdrawProof first */ + COutPoint GetWithdrawSpent() const; + + /** Get the genesis hash locked to, asserting IsWithdrawLock first */ + uint256 GetWithdrawLockGenesisHash() const; /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ bool IsPushOnly(const_iterator pc) const;