mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-16 13:01:19 +02:00
Add CScript::IsWithdrawLock() (which is standard)
This commit is contained in:
parent
c22932429e
commit
8c121ccd5c
4 changed files with 169 additions and 13 deletions
|
|
@ -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<std::vector<unsigned char> > vSolutions;
|
||||
txnouttype whichType;
|
||||
// get the scriptPubKey corresponding to this input:
|
||||
|
|
|
|||
|
|
@ -426,8 +426,9 @@ bool EvalScript(vector<vector<unsigned char> >& 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);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,14 @@
|
|||
|
||||
#include "script.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
#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 <contract> <merkleBlock>xN <N> <locking tx>xM <M> <output index>
|
||||
// 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 [<chaindest> OP_DROP] <genesishash> 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<unsigned char> 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<unsigned char> 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<vector<unsigned char> >& pushes, vector<unsigned char> *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<unsigned char> &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<vector<unsigned char> > pushes;
|
||||
pushes.reserve(6);
|
||||
while (pc < end()) {
|
||||
pushes.push_back(vector<unsigned char>());
|
||||
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<unsigned char> vTx;
|
||||
if (!PopWithdrawPush(pushes, &vTx))
|
||||
return COutPoint();
|
||||
CTransactionRef 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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -622,6 +625,21 @@ public:
|
|||
bool IsPayToScriptHash() const;
|
||||
bool IsPayToWitnessScriptHash() const;
|
||||
bool IsWitnessProgram(int& version, std::vector<unsigned char>& 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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue