diff --git a/src/Makefile.am b/src/Makefile.am index 8028a6d5e..04fcdb415 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/keystore.h b/src/keystore.h index b1ad32a42..980ac4403 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -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" diff --git a/src/komodo_globals.h b/src/komodo_globals.h index f77ee71e3..782fc7a60 100644 --- a/src/komodo_globals.h +++ b/src/komodo_globals.h @@ -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]; diff --git a/src/komodo_utils.h b/src/komodo_utils.h index 5be5b1b4b..81938899a 100644 --- a/src/komodo_utils.h +++ b/src/komodo_utils.h @@ -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) diff --git a/src/main.cpp b/src/main.cpp index 3ae4c65ea..b21f4a768 100644 --- a/src/main.cpp +++ b/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 opretData = std::vector(); + 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); } /** diff --git a/src/main.h b/src/main.h index 14513b730..66c43655c 100644 --- a/src/main.h +++ b/src/main.h @@ -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" diff --git a/src/miner.cpp b/src/miner.cpp index cc2c8e24e..bcf5fe16b 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -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; } diff --git a/src/script/script_ext.cpp b/src/script/script_ext.cpp new file mode 100644 index 000000000..560c4e0cf --- /dev/null +++ b/src/script/script_ext.cpp @@ -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(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 &data, unsigned char opretType) const +{ + ((CScript *)this)->clear(); + if (data.size() < MAX_SCRIPT_ELEMENT_SIZE) + { + vector scratch = vector(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 unlockTimeParam = std::vector(); + + 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); +} + diff --git a/src/script/script_ext.h b/src/script/script_ext.h new file mode 100644 index 000000000..ece0cf594 --- /dev/null +++ b/src/script/script_ext.h @@ -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 &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 + diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 8172c00e3..13d6f9045 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -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 valtype; +unsigned int HaveKeys(const vector& 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 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 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 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) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0f9e6c5da..205a974b5 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -762,6 +762,7 @@ protected: private: template void SyncMetaData(std::pair::iterator, typename TxSpendMap::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;