Rewrite time locks, coinbase validation, and script functions in C++ for easier integration
This commit is contained in:
@@ -379,6 +379,7 @@ libbitcoin_common_a_SOURCES = \
|
||||
scheduler.cpp \
|
||||
script/interpreter.cpp \
|
||||
script/script.cpp \
|
||||
script/script_ext.cpp \
|
||||
script/script_error.cpp \
|
||||
script/sign.cpp \
|
||||
script/standard.cpp \
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "pubkey.h"
|
||||
#include "script/script.h"
|
||||
#include "script/standard.h"
|
||||
#include "script/script_ext.h"
|
||||
#include "sync.h"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "zcash/NoteEncryption.hpp"
|
||||
|
||||
@@ -57,12 +57,15 @@ uint32_t ASSETCHAINS_MAGIC = 2387029918;
|
||||
uint64_t ASSETCHAINS_SUPPLY = 10;
|
||||
uint64_t ASSETCHAINS_COMMISSION;
|
||||
|
||||
// consensus variables for coinbase timelock control and timelock transaction support
|
||||
// time locks are specified enough to enable their use initially to lock specific coinbase transactions for emission control
|
||||
// to be verifiable, timelocks require additional data that enables them to be validated and their ownership and
|
||||
// release time determined from the blockchain. to do this, every time locked output according to this
|
||||
// spec will use an op_return with CLTV at front and anything after |OP_RETURN|PUSH of rest|OPRETTYPE_TIMELOCK|script|
|
||||
#define _ASSETCHAINS_TIMELOCKOFF 0x7fffffffffffffffLL
|
||||
int64_t ASSETCHAINS_TIMELOCKGTE = _ASSETCHAINS_TIMELOCKOFF;
|
||||
uint64_t ASSETCHAINS_TIMEUNLOCKFROM = 0;
|
||||
uint64_t ASSETCHAINS_TIMEUNLOCKTO = 0;
|
||||
#define OPRETTYPE_COINBASETIMELOCK = 1;
|
||||
#define OPRETTYPE_REDEEMSCRIPT = 2;
|
||||
|
||||
uint32_t ASSETCHAINS_ERAS = 1;
|
||||
uint64_t ASSETCHAINS_ENDSUBSIDY[ASSETCHAINS_MAX_ERAS],ASSETCHAINS_REWARD[ASSETCHAINS_MAX_ERAS],ASSETCHAINS_HALVING[ASSETCHAINS_MAX_ERAS],ASSETCHAINS_DECAY[ASSETCHAINS_MAX_ERAS];
|
||||
|
||||
@@ -1011,47 +1011,22 @@ int32_t komodo_scriptitemlen(int32_t *opretlenp,uint8_t *script)
|
||||
return(len);
|
||||
}
|
||||
|
||||
// we need to replace this with an include file (like script.h) that defines all opcodes, but for now,
|
||||
// we'll keep these localized near where they're used in the two functions below. script.h is not
|
||||
// required with komodo_utils.h
|
||||
#define SCRIPT_OP_DUP 0x76
|
||||
#define SCRIPT_OP_HASH160 0xa9
|
||||
#define SCRIPT_OP_EQUALVERIFY 0x88
|
||||
#define SCRIPT_OP_CHECKSIG 0xac
|
||||
#define SCRIPT_OP_CHECKLOCKTIMEVERIFY 0xb1
|
||||
#define SCRIPT_OP_DROP 0x75
|
||||
#define SCRIPT_OP_EQUAL 0x87
|
||||
#define SCRIPT_OP_RETURN 0x6a
|
||||
#define SCRIPT_OP_PUSH1 0x4c
|
||||
#define SCRIPT_OP_PUSH2 0x4d
|
||||
|
||||
// standard spend script
|
||||
int32_t komodo_standardspend(uint8_t *script, int32_t n, const uint8_t rmd160[20])
|
||||
{
|
||||
script[n++] = SCRIPT_OP_DUP;
|
||||
script[n++] = SCRIPT_OP_HASH160;
|
||||
script[n++] = 0x14; memcpy(&script[n],rmd160,0x14); n += 0x14;
|
||||
script[n++] = SCRIPT_OP_EQUALVERIFY;
|
||||
script[n++] = SCRIPT_OP_CHECKSIG;
|
||||
return(n);
|
||||
}
|
||||
|
||||
int32_t komodo_opreturnscript(uint8_t *script,uint8_t type,uint8_t *opret,int32_t opretlen)
|
||||
{
|
||||
int32_t offset = 0;
|
||||
script[offset++] = 0x6a;
|
||||
opretlen++;
|
||||
if ( opretlen >= SCRIPT_OP_PUSH1 )
|
||||
if ( opretlen >= 0x4c )
|
||||
{
|
||||
if ( opretlen > 0xff )
|
||||
{
|
||||
script[offset++] = SCRIPT_OP_PUSH2;
|
||||
script[offset++] = 0x4d;
|
||||
script[offset++] = opretlen & 0xff;
|
||||
script[offset++] = (opretlen >> 8) & 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
script[offset++] = SCRIPT_OP_PUSH1;
|
||||
script[offset++] = 0x4c;
|
||||
script[offset++] = opretlen;
|
||||
}
|
||||
} else script[offset++] = opretlen;
|
||||
@@ -1060,60 +1035,6 @@ int32_t komodo_opreturnscript(uint8_t *script,uint8_t type,uint8_t *opret,int32_
|
||||
return(offset + opretlen - 1);
|
||||
}
|
||||
|
||||
// pay to script hash script
|
||||
int32_t komodo_p2sh(uint8_t *script, int32_t n, const uint8_t scriptHash[20])
|
||||
{
|
||||
script[n++] = SCRIPT_OP_HASH160;
|
||||
script[n++] = 0x14; memcpy(&(script[n]),scriptHash,0x14); n += 0x14;
|
||||
script[n++] = SCRIPT_OP_EQUAL;
|
||||
return(n);
|
||||
}
|
||||
|
||||
// check lock time verify script to ensure that the UTXO cannot be spent
|
||||
// until the specified lock time
|
||||
int32_t komodo_checklocktimeverify(uint8_t *script, int32_t n, uint64_t unlocktime)
|
||||
{
|
||||
int numBytes = unlocktime <= 0x7f ? 1 : unlocktime <= 0x7fff ? 2 : unlocktime <= 0x7fffff ? 3 : unlocktime <= 0x7fffffff ? 4 : 5;
|
||||
script[n++] = numBytes;
|
||||
|
||||
for ( int i = 0; i < numBytes; i++ )
|
||||
script[n++] = unlocktime & 0xff, unlocktime >>= 8;
|
||||
|
||||
script[n++] = SCRIPT_OP_CHECKLOCKTIMEVERIFY;
|
||||
script[n++] = SCRIPT_OP_DROP;
|
||||
return(n);
|
||||
}
|
||||
|
||||
// combined CLTV script and standard spend
|
||||
int32_t komodo_timelockspend(uint8_t *script, int32_t n, const uint8_t rmd160[20], uint64_t unlocktime)
|
||||
{
|
||||
n = komodo_checklocktimeverify(script,n,unlocktime);
|
||||
n = komodo_standardspend(script,n,rmd160);
|
||||
return(n);
|
||||
}
|
||||
|
||||
// return the unlock time from a CLTV script and ensure that it is, in fact, a CLTV script
|
||||
// if not a CLTV script, this returns 0
|
||||
uint64_t komodo_getscriptunlocktime(uint8_t *script, uint32_t scriptLen)
|
||||
{
|
||||
uint32_t nBytes;
|
||||
uint64_t unlockTime = 0;
|
||||
|
||||
nBytes = *script++;
|
||||
if ((nBytes > 0 && nBytes <= 5) && nBytes < scriptLen - 2)
|
||||
{
|
||||
if (*(script += nBytes) == SCRIPT_OP_CHECKLOCKTIMEVERIFY)
|
||||
{
|
||||
for ( ; nBytes > 0; nBytes--)
|
||||
{
|
||||
unlockTime <<= 8;
|
||||
unlockTime |= *--script;
|
||||
}
|
||||
}
|
||||
}
|
||||
return(unlockTime);
|
||||
}
|
||||
|
||||
// get a pseudo random number that is the same for each block individually at all times and different
|
||||
// from all other blocks. the sequence is extremely likely, but not guaranteed to be unique for each block chain
|
||||
uint64_t blockPRG(uint32_t nHeight)
|
||||
@@ -1141,7 +1062,7 @@ uint64_t blockPRG(uint32_t nHeight)
|
||||
// given a block height, this returns the unlock time for that block height, derived from
|
||||
// the ASSETCHAINS_MAGIC number as well as the block height, providing different random numbers
|
||||
// for corresponding blocks across chains, but the same sequence in each chain
|
||||
uint64_t komodo_block_unlocktime(uint32_t nHeight)
|
||||
int64_t komodo_block_unlocktime(uint32_t nHeight)
|
||||
{
|
||||
uint64_t fromTime, toTime, unlocktime;
|
||||
|
||||
@@ -1155,21 +1076,7 @@ uint64_t komodo_block_unlocktime(uint32_t nHeight)
|
||||
if (unlocktime > ASSETCHAINS_TIMEUNLOCKTO)
|
||||
unlocktime--;
|
||||
}
|
||||
return (unlocktime);
|
||||
}
|
||||
|
||||
// create a CLTV output script, returning the script size, script, and its P2SH address
|
||||
// funds will be locked a pseudo random time between specified from and to time, with entropy taken from the parameters used
|
||||
// to setup the chain and the specified block height. this can be used to create, validate, or spend a time locked coin base transaction
|
||||
// returns unlocktime
|
||||
uint32_t komodo_coinbase_timelock(uint8_t *script, uint8_t *p2sh160, const uint8_t taddrrmd160[20], uint32_t nHeight, int64_t nSubsidy)
|
||||
{
|
||||
uint32_t n = 0;
|
||||
uint64_t unlocktime = nSubsidy >= ASSETCHAINS_TIMELOCKGTE ? komodo_block_unlocktime(nHeight) : 0;
|
||||
|
||||
n = komodo_timelockspend(script, n, taddrrmd160, unlocktime);
|
||||
calc_rmd160_sha256(p2sh160, script, n);
|
||||
return(n);
|
||||
return ((int64_t)unlocktime);
|
||||
}
|
||||
|
||||
long _stripwhite(char *buf,int accept)
|
||||
|
||||
71
src/main.cpp
71
src/main.cpp
@@ -900,63 +900,40 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
|
||||
*/
|
||||
bool ContextualCheckCoinbaseTransaction(const CTransaction& tx, const int nHeight)
|
||||
{
|
||||
int64_t i, total = 0;
|
||||
uint8_t script[1024], scriptHash[20];
|
||||
|
||||
for (i = 0; total += tx.vout[i].IsNull() ? 0 : tx.vout[i].nValue, i < tx.vout.size(); i++);
|
||||
i = 0;
|
||||
|
||||
// if time locks are on, ensure that this coin base is time locked exactly as it should be
|
||||
if (total >= ASSETCHAINS_TIMELOCKGTE)
|
||||
if (tx.GetValueOut() >= ASSETCHAINS_TIMELOCKGTE)
|
||||
{
|
||||
CScriptID scriptHash;
|
||||
|
||||
// to be valid, it must be a P2SH transaction and have an op_return in vout[1] that
|
||||
// holds either:
|
||||
// 1) the receiver's public key hash, which we can use to recreate the output script to
|
||||
// check against the hash, or
|
||||
// 2) the full output script, which may include multisig, etc., that starts with
|
||||
// the time lock verify of the correct time lock for this block height
|
||||
if (tx.vout.size() != 2 ||
|
||||
tx.vout[1].scriptPubKey.size() < 4 || // minimum for any possible future to prevent out of bounds
|
||||
tx.vout[1].scriptPubKey.data()[0] != SCRIPT_OP_RETURN ||
|
||||
tx.vout[0].scriptPubKey.size() != 22 ||
|
||||
*(uint8_t *)(tx.vout[0].scriptPubKey.data()) != 20 ||
|
||||
((uint8_t *)(tx.vout[0].scriptPubKey.data()))[21] != SCRIPT_OP_EQUAL)
|
||||
i = 0;
|
||||
else
|
||||
// holds the full output script, which may include multisig, etc., but starts with
|
||||
// the time lock verify of the correct time lock for this block height
|
||||
if (tx.vout.size() == 2 &&
|
||||
CScriptExt(tx.vout[0].scriptPubKey).IsPayToScriptHash(&scriptHash) &&
|
||||
tx.vout[1].scriptPubKey.size() >= 7 && // minimum for any possible future to prevent out of bounds
|
||||
tx.vout[1].scriptPubKey.data()[0] == OP_RETURN)
|
||||
{
|
||||
i = tx.vout[1].scriptPubKey.data()[1];
|
||||
i = i < SCRIPT_OP_PUSH1 ? i :
|
||||
i == SCRIPT_OP_PUSH1 ? tx.vout[1].scriptPubKey.data()[2] :
|
||||
i == SCRIPT_OP_PUSH2 ? tx.vout[1].scriptPubKey.data()[3] << 8 + tx.vout[1].scriptPubKey.data()[2] : 0;
|
||||
if (i != 0)
|
||||
opcodetype op;
|
||||
std::vector<uint8_t> opretData = std::vector<uint8_t>();
|
||||
CScript::const_iterator it = tx.vout[1].scriptPubKey.begin() + 1;
|
||||
if (tx.vout[1].scriptPubKey.GetOp2(it, op, &opretData))
|
||||
{
|
||||
if (tx.vout[1].scriptPubKey.data()[2] == OPRETTYPE_COINBASETIMELOCK && i >= 21)
|
||||
if (opretData.size() > 0 && opretData.data()[0] == OPRETTYPE_TIMELOCK)
|
||||
{
|
||||
// recreate the time lock script and its hash
|
||||
i = komodo_coinbase_timelock(script, scriptHash, &tx.vout[1].scriptPubKey.data()[3], nHeight, total);
|
||||
}
|
||||
else if (tx.vout[1].scriptPubKey.data()[2] == OPRETTYPE_REDEEMSCRIPT && i >= 23 && i < sizeof(script))
|
||||
{
|
||||
memcpy(script, ((uint8_t *)tx.vout[1].scriptPubKey.data())[i < SCRIPT_OP_PUSH1 ? 3 : i > SCRIPT_OP_PUSH1 ? 5 : 4]), i - 1);
|
||||
// decrement after the prior call in case of optimization side effects
|
||||
i--;
|
||||
calc_rmd160_sha256(scriptHash, script, i);
|
||||
}
|
||||
else
|
||||
i = 0;
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
// get the lock time from the script, regardless of if we recognize the rest or not,
|
||||
// we will return true if it is a proper time lock for the right time and the script matches the hash
|
||||
if (komodo_block_unlocktime(nHeight) != komodo_getscriptunlocktime(script, i) ||
|
||||
memcmp(((uint8_t *)tx.vout[0].scriptPubKey.data())+1, scriptHash, sizeof(scriptHash)))
|
||||
i = 0;
|
||||
int64_t unlocktime;
|
||||
CScriptExt opretScript = CScriptExt(opretData.begin() + 1, opretData.end());
|
||||
|
||||
if (CScriptID(opretScript) == scriptHash &&
|
||||
opretScript.IsCheckLockTimeVerify(&unlocktime) &&
|
||||
komodo_block_unlocktime(nHeight) == unlocktime)
|
||||
{
|
||||
return(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return(i != 0);
|
||||
return(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "script/script.h"
|
||||
#include "script/sigcache.h"
|
||||
#include "script/standard.h"
|
||||
#include "script/script_ext.h"
|
||||
#include "sync.h"
|
||||
#include "tinyformat.h"
|
||||
#include "txmempool.h"
|
||||
|
||||
@@ -108,7 +108,6 @@ void UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams,
|
||||
extern int32_t ASSETCHAINS_SEED,IS_KOMODO_NOTARY,USE_EXTERNAL_PUBKEY,KOMODO_CHOSEN_ONE,ASSETCHAIN_INIT,KOMODO_INITDONE,KOMODO_ON_DEMAND,KOMODO_INITDONE,KOMODO_PASSPORT_INITDONE;
|
||||
extern uint64_t ASSETCHAINS_COMMISSION;
|
||||
extern uint64_t ASSETCHAINS_REWARD[ASSETCHAINS_MAX_ERAS], ASSETCHAINS_TIMELOCKGTE;
|
||||
extern uint8_t OPRETTYPE_COINBASETIMELOCK;
|
||||
extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN];
|
||||
extern std::string NOTARY_PUBKEY;
|
||||
extern uint8_t NOTARY_PUBKEY33[33],ASSETCHAINS_OVERRIDE_PUBKEY33[33];
|
||||
@@ -121,10 +120,7 @@ int32_t komodo_is_issuer();
|
||||
int32_t komodo_gateway_deposits(CMutableTransaction *txNew,char *symbol,int32_t tokomodo);
|
||||
int32_t komodo_isrealtime(int32_t *kmdheightp);
|
||||
int32_t komodo_validate_interest(const CTransaction &tx,int32_t txheight,uint32_t nTime,int32_t dispflag);
|
||||
int32_t komodo_coinbase_outputscript(uint8_t *script, uint8_t *p2sh160, uint8_t *taddrrmd160, int64_t nSubsidy, uint32_t nHeight);
|
||||
int32_t komodo_coinbase_timelock(uint8_t * script, uint8_t *p2sh160, const uint8_t taddrrmd160[20], uint32_t nHeight, int64_t nSubsidy);
|
||||
int32_t komodo_p2sh(uint8_t *script, int32_t n, const uint8_t scriptHash[20]);
|
||||
int32_t komodo_opreturnscript(uint8_t *script, uint8_t type, uint8_t *opret, int32_t opretlen);
|
||||
int64_t komodo_block_unlocktime(uint32_t nHeight);
|
||||
|
||||
CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
|
||||
{
|
||||
@@ -401,25 +397,23 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
|
||||
txNew.vout[0].nValue = GetBlockSubsidy(nHeight,chainparams.GetConsensus()) + nFees;
|
||||
txNew.nExpiryHeight = 0;
|
||||
|
||||
// check if coinbase transactions must be time locked at current subsidy and make the time lock if so
|
||||
// check if coinbase transactions must be time locked at current subsidy and prepend the time lock
|
||||
// to transaction if so
|
||||
if (txNew.vout[0].nValue >= ASSETCHAINS_TIMELOCKGTE)
|
||||
{
|
||||
int32_t opretlen, p2shlen, scriptlen;
|
||||
uint8_t opret[256], p2shscript[22], redeemscript[256], taddr[20], p2sh[20];
|
||||
CScriptExt opretScript = CScriptExt();
|
||||
|
||||
txNew.vout.resize(2);
|
||||
|
||||
memcpy(taddr, ((uint8_t *)scriptPubKeyIn.data()) + 2, sizeof(taddr));
|
||||
// prepend time lock to original script unless original script is P2SH, in which case, we will leave the coins
|
||||
// protected only by the time lock rather than 100% inaccessible
|
||||
opretScript.AddCheckLockTimeVerify(komodo_block_unlocktime(nHeight));
|
||||
if (!scriptPubKeyIn.IsPayToScriptHash())
|
||||
opretScript += scriptPubKeyIn;
|
||||
|
||||
scriptlen = komodo_coinbase_timelock(redeemscript, p2sh, taddr, nHeight, txNew.vout[0].nValue);
|
||||
p2shlen = komodo_p2sh(p2shscript, 0, p2sh);
|
||||
|
||||
txNew.vout[0].scriptPubKey.resize(p2shlen);
|
||||
memcpy((uint8_t *)(txNew.vout[0].scriptPubKey.data()), p2shscript, p2shlen);
|
||||
|
||||
opretlen = komodo_opreturnscript(opret, OPRETTYPE_COINBASETIMELOCK, taddr, sizeof(taddr));
|
||||
txNew.vout[1].scriptPubKey.resize(opretlen);
|
||||
memcpy((uint8_t *)(txNew.vout[1].scriptPubKey.data()), opret, opretlen);
|
||||
txNew.vout[0].scriptPubKey = CScriptExt().PayToScriptHash(CScriptID(opretScript));
|
||||
txNew.vout[1].scriptPubKey = CScriptExt().OpReturnScript(opretScript, OPRETTYPE_TIMELOCK);
|
||||
txNew.vout[1].nValue = 0;
|
||||
}
|
||||
|
||||
|
||||
105
src/script/script_ext.cpp
Normal file
105
src/script/script_ext.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Copyright (c) 2018 The Verus developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "script_ext.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool CScriptExt::IsPayToScriptHash(CScriptID *scriptID) const
|
||||
{
|
||||
if (((CScript *)this)->IsPayToScriptHash())
|
||||
{
|
||||
*scriptID = CScriptID(uint160(std::vector<unsigned char>(this->begin() + 2, this->end() - 1)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// P2PKH script, adds to whatever is already in the script (for example CLTV)
|
||||
const CScriptExt &CScriptExt::AddPayToPubKeyHash(const CKeyID &key) const
|
||||
{
|
||||
*((CScript *)this) << OP_DUP;
|
||||
*((CScript *)this) << OP_HASH160;
|
||||
*((CScript *)this) << ToByteVector(key);
|
||||
*((CScript *)this) << OP_EQUALVERIFY;
|
||||
*((CScript *)this) << OP_CHECKSIG;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// push data into an op_return script with an opret type indicator, fails if the op_return is too large
|
||||
const CScriptExt &CScriptExt::OpReturnScript(const vector<unsigned char> &data, unsigned char opretType) const
|
||||
{
|
||||
((CScript *)this)->clear();
|
||||
if (data.size() < MAX_SCRIPT_ELEMENT_SIZE)
|
||||
{
|
||||
vector<unsigned char> scratch = vector<unsigned char>(data);
|
||||
scratch.insert(data.begin(), opretType);
|
||||
*((CScript *)this) << OP_RETURN;
|
||||
*((CScript *)this) << scratch;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// P2SH script, adds to whatever is already in the script (for example CLTV)
|
||||
const CScriptExt &CScriptExt::PayToScriptHash(const CScriptID &scriptID) const
|
||||
{
|
||||
((CScript *)this)->clear();
|
||||
*((CScript *)this) << OP_HASH160;
|
||||
*((CScript *)this) << ToByteVector(scriptID);
|
||||
*((CScript *)this) << OP_EQUAL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// P2SH script, adds to whatever is already in the script (for example CLTV)
|
||||
const CScriptExt &CScriptExt::AddCheckLockTimeVerify(int64_t unlocktime) const
|
||||
{
|
||||
unlocktime = unlocktime < 0 ? 0 : unlocktime;
|
||||
*((CScript *)this) << CScriptNum::serialize(unlocktime);
|
||||
*((CScript *)this) << OP_CHECKLOCKTIMEVERIFY;
|
||||
*((CScript *)this) << OP_DROP;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// combined CLTV script and P2PKH
|
||||
const CScriptExt &CScriptExt::TimeLockSpend(const CKeyID &key, int64_t unlocktime) const
|
||||
{
|
||||
((CScript *)this)->clear();
|
||||
this->AddCheckLockTimeVerify(unlocktime);
|
||||
this->AddPayToPubKeyHash(key);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// if the front of the script has check lock time verify. this is a fairly simple check.
|
||||
// accepts NULL as parameter if unlockTime is not needed.
|
||||
bool CScriptExt::IsCheckLockTimeVerify(int64_t *unlockTime) const
|
||||
{
|
||||
opcodetype op;
|
||||
std::vector<unsigned char> unlockTimeParam = std::vector<unsigned char>();
|
||||
|
||||
CScript::const_iterator it = this->begin();
|
||||
if (this->GetOp2(it, op, &unlockTimeParam))
|
||||
{
|
||||
if (unlockTimeParam.size() > 1 && unlockTimeParam.size() < 6 &&
|
||||
*(this->data() + unlockTimeParam.size() + 1) == OP_CHECKLOCKTIMEVERIFY)
|
||||
{
|
||||
int i = unlockTimeParam.size() - 1;
|
||||
for (*unlockTime = 0; i >= 0; i--)
|
||||
{
|
||||
*unlockTime <<= 8;
|
||||
*unlockTime |= *((unsigned char *)unlockTimeParam.data() + i);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CScriptExt::IsCheckLockTimeVerify() const
|
||||
{
|
||||
int64_t ult;
|
||||
return this->IsCheckLockTimeVerify(&ult);
|
||||
}
|
||||
|
||||
50
src/script/script_ext.h
Normal file
50
src/script/script_ext.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Copyright (c) 2018 The Verus developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_SCRIPT_SCRIPT_EXT_H
|
||||
#define BITCOIN_SCRIPT_SCRIPT_EXT_H
|
||||
|
||||
#include "script.h"
|
||||
#include "standard.h"
|
||||
#include "pubkey.h"
|
||||
|
||||
#define OPRETTYPE_TIMELOCK 1
|
||||
|
||||
class CScriptExt : public CScript
|
||||
{
|
||||
public:
|
||||
CScriptExt() { }
|
||||
CScriptExt(const CScript& b) : CScript(b.begin(), b.end()) { }
|
||||
CScriptExt(const_iterator pbegin, const_iterator pend) : CScript(pbegin, pend) { }
|
||||
CScriptExt(const unsigned char* pbegin, const unsigned char* pend) : CScript(pbegin, pend) { }
|
||||
|
||||
// overload to return the hash of the referenced script
|
||||
bool IsPayToScriptHash(CScriptID *scriptID) const;
|
||||
|
||||
// P2PKH script, adds to whatever is already in the script (for example CLTV)
|
||||
const CScriptExt &AddPayToPubKeyHash(const CKeyID &key) const;
|
||||
|
||||
// push data into an op_return script with an opret type indicator, fails if the op_return is too large
|
||||
const CScriptExt &OpReturnScript(const std::vector<unsigned char> &data, unsigned char opretType) const;
|
||||
|
||||
// P2SH script
|
||||
const CScriptExt &PayToScriptHash(const CScriptID &scriptID) const;
|
||||
|
||||
// P2SH script, adds to whatever is already in the script (for example CLTV)
|
||||
const CScriptExt &AddCheckLockTimeVerify(int64_t unlocktime) const;
|
||||
|
||||
// combined CLTV script and P2PKH
|
||||
const CScriptExt &TimeLockSpend(const CKeyID &key, int64_t unlocktime) const;
|
||||
|
||||
// if the front of the script has check lock time verify. this is a fairly simple check.
|
||||
// accepts NULL as parameter if unlockTime is not needed.
|
||||
bool IsCheckLockTimeVerify(int64_t *unlockTime) const;
|
||||
|
||||
bool IsCheckLockTimeVerify() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1466,6 +1466,140 @@ bool CWallet::IsMine(const CTransaction& tx) const
|
||||
return false;
|
||||
}
|
||||
|
||||
// special case handling for CLTV scripts, this does not error check to ensure the script is CLTV and is
|
||||
// only internal to the wallet for that reason. if it is the first time we see this script, we add it to the wallet.
|
||||
isminetype CWallet::IsCLTVMine(CScriptExt &script, CScriptID &scriptID, int64_t locktime)
|
||||
{
|
||||
uint8_t pushOp = script.data()[0];
|
||||
uint32_t scriptStart = pushOp + 2;
|
||||
|
||||
// check post CLTV script
|
||||
CScriptExt postfix = CScriptExt(script.size() > scriptStart ? script.begin() + scriptStart : script.end(), script.end());
|
||||
|
||||
// check again with postfix subscript
|
||||
isminetype ret = ::IsMine(*this, postfix);
|
||||
if (ret != ISMINE_NO)
|
||||
{
|
||||
// once we get here, we should have this script in our
|
||||
// wallet, either as watch only if still time locked, or spendable
|
||||
if (!chainActive.Tip()->nHeight >= locktime)
|
||||
{
|
||||
ret = ISMINE_WATCH_ONLY;
|
||||
if (!this->HaveWatchOnly(script))
|
||||
{
|
||||
this->AddWatchOnly(script);
|
||||
}
|
||||
} else
|
||||
{
|
||||
if (this->HaveWatchOnly(script))
|
||||
this->RemoveWatchOnly(script);
|
||||
if (!this->HaveCScript(scriptID))
|
||||
this->AddCScript(script);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef vector<unsigned char> valtype;
|
||||
unsigned int HaveKeys(const vector<valtype>& pubkeys, const CKeyStore& keystore);
|
||||
|
||||
// special case handling for non-standard/Verus OP_RETURN script outputs, which need the transaction
|
||||
// to determine ownership
|
||||
isminetype CWallet::IsMine(const CTransaction& tx, uint32_t voutNum)
|
||||
{
|
||||
vector<valtype> vSolutions;
|
||||
txnouttype whichType;
|
||||
const CScriptExt scriptPubKey = CScriptExt(tx.vout[voutNum].scriptPubKey);
|
||||
|
||||
if (!Solver(scriptPubKey, whichType, vSolutions)) {
|
||||
if (this->HaveWatchOnly(scriptPubKey))
|
||||
return ISMINE_WATCH_ONLY;
|
||||
return ISMINE_NO;
|
||||
}
|
||||
|
||||
CKeyID keyID;
|
||||
CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
|
||||
CScriptExt subscript;
|
||||
int voutNext = voutNum + 1;
|
||||
|
||||
switch (whichType)
|
||||
{
|
||||
case TX_NONSTANDARD:
|
||||
case TX_NULL_DATA:
|
||||
break;
|
||||
|
||||
case TX_PUBKEY:
|
||||
keyID = CPubKey(vSolutions[0]).GetID();
|
||||
if (this->HaveKey(keyID))
|
||||
return ISMINE_SPENDABLE;
|
||||
break;
|
||||
|
||||
case TX_PUBKEYHASH:
|
||||
keyID = CKeyID(uint160(vSolutions[0]));
|
||||
if (this->HaveKey(keyID))
|
||||
return ISMINE_SPENDABLE;
|
||||
break;
|
||||
|
||||
case TX_SCRIPTHASH:
|
||||
if (this->GetCScript(scriptID, subscript))
|
||||
{
|
||||
// if this is a CLTV, handle it differently
|
||||
int64_t lockTime;
|
||||
if (subscript.IsCheckLockTimeVerify(&lockTime))
|
||||
{
|
||||
return this->IsCLTVMine(subscript, scriptID, lockTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
isminetype ret = ::IsMine(*this, subscript);
|
||||
if (ret == ISMINE_SPENDABLE)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else if (tx.vout.size() > (voutNext = voutNum + 1) &&
|
||||
tx.vout[voutNext].scriptPubKey.size() > 7 &&
|
||||
tx.vout[voutNext].scriptPubKey.data()[0] == OP_RETURN)
|
||||
{
|
||||
// get the opret script from next vout, verify that the front is CLTV and hash matches
|
||||
// if so, remove it and use the solver
|
||||
opcodetype op;
|
||||
std::vector<uint8_t> opretData;
|
||||
CScript::const_iterator it = tx.vout[voutNext].scriptPubKey.begin() + 1;
|
||||
if (tx.vout[voutNext].scriptPubKey.GetOp2(it, op, &opretData))
|
||||
{
|
||||
if (opretData.size() > 0 && opretData.data()[0] == OPRETTYPE_TIMELOCK)
|
||||
{
|
||||
int64_t unlocktime;
|
||||
CScriptExt opretScript = CScriptExt(opretData.begin() + 1, opretData.end());
|
||||
|
||||
if (CScriptID(opretScript) == scriptID &&
|
||||
opretScript.IsCheckLockTimeVerify(&unlocktime))
|
||||
{
|
||||
return this->IsCLTVMine(opretScript, scriptID, unlocktime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TX_MULTISIG:
|
||||
// Only consider transactions "mine" if we own ALL the
|
||||
// keys involved. Multi-signature transactions that are
|
||||
// partially owned (somebody else has a key that can spend
|
||||
// them) enable spend-out-from-under-you attacks, especially
|
||||
// in shared-wallet situations.
|
||||
vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1);
|
||||
if (HaveKeys(keys, *this) == keys.size())
|
||||
return ISMINE_SPENDABLE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->HaveWatchOnly(scriptPubKey))
|
||||
return ISMINE_WATCH_ONLY;
|
||||
|
||||
return ISMINE_NO;
|
||||
}
|
||||
|
||||
bool CWallet::IsFromMe(const CTransaction& tx) const
|
||||
{
|
||||
if (GetDebit(tx, ISMINE_ALL) > 0) {
|
||||
|
||||
@@ -762,6 +762,7 @@ protected:
|
||||
private:
|
||||
template <class T>
|
||||
void SyncMetaData(std::pair<typename TxSpendMap<T>::iterator, typename TxSpendMap<T>::iterator>);
|
||||
isminetype IsCLTVMine(CScriptExt &script, CScriptID &scriptID, int64_t locktime);
|
||||
|
||||
protected:
|
||||
bool UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx);
|
||||
@@ -1041,6 +1042,7 @@ public:
|
||||
isminetype IsMine(const CTxIn& txin) const;
|
||||
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
|
||||
isminetype IsMine(const CTxOut& txout) const;
|
||||
isminetype IsMine(const CTransaction& tx, uint32_t voutNum);
|
||||
CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const;
|
||||
bool IsChange(const CTxOut& txout) const;
|
||||
CAmount GetChange(const CTxOut& txout) const;
|
||||
|
||||
Reference in New Issue
Block a user