diff --git a/src/komodo_globals.h b/src/komodo_globals.h index c651a14de..041c64fc6 100644 --- a/src/komodo_globals.h +++ b/src/komodo_globals.h @@ -61,6 +61,8 @@ uint64_t ASSETCHAINS_COMMISSION; int64_t ASSETCHAINS_TIMELOCKGTE = _ASSETCHAINS_TIMELOCKOFF; uint64_t ASSETCHAINS_TIMEUNLOCKFROM = 0; uint64_t ASSETCHAINS_TIMEUNLOCKTO = 0; +uint8_t OPRETTYPE_COINBASETIMELOCK = 1; +uint8_t 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 67fc9a99d..5be5b1b4b 100644 --- a/src/komodo_utils.h +++ b/src/komodo_utils.h @@ -1011,22 +1011,47 @@ 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 >= 0x4c ) + if ( opretlen >= SCRIPT_OP_PUSH1 ) { if ( opretlen > 0xff ) { - script[offset++] = 0x4d; + script[offset++] = SCRIPT_OP_PUSH2; script[offset++] = opretlen & 0xff; script[offset++] = (opretlen >> 8) & 0xff; } else { - script[offset++] = 0x4c; + script[offset++] = SCRIPT_OP_PUSH1; script[offset++] = opretlen; } } else script[offset++] = opretlen; @@ -1035,23 +1060,12 @@ int32_t komodo_opreturnscript(uint8_t *script,uint8_t type,uint8_t *opret,int32_ return(offset + opretlen - 1); } -// 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 -#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 - -// standard spend script -int32_t komodo_standardspend(uint8_t *script, int32_t n, uint8_t rmd160[20]) +// pay to script hash script +int32_t komodo_p2sh(uint8_t *script, int32_t n, const uint8_t scriptHash[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; + script[n++] = 0x14; memcpy(&(script[n]),scriptHash,0x14); n += 0x14; + script[n++] = SCRIPT_OP_EQUAL; return(n); } @@ -1071,13 +1085,35 @@ int32_t komodo_checklocktimeverify(uint8_t *script, int32_t n, uint64_t unlockti } // combined CLTV script and standard spend -int32_t komodo_timelockspend(uint8_t *script, int32_t n, uint8_t rmd160[20], uint64_t unlocktime) +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) @@ -1102,53 +1138,38 @@ uint64_t blockPRG(uint32_t nHeight) return(result); } -uint64_t komodo_pr_unlocktime(uint32_t nHeight, uint64_t fromTime, uint64_t toTime) +// 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) { - uint64_t unlocktime; - uint32_t i, n = 0; + uint64_t fromTime, toTime, unlocktime; - if ( toTime < fromTime ) - return(0); - else if ( toTime == fromTime ) - unlocktime = toTime; + if ( ASSETCHAINS_TIMEUNLOCKFROM == ASSETCHAINS_TIMEUNLOCKTO ) + unlocktime = ASSETCHAINS_TIMEUNLOCKTO; else { - unlocktime = blockPRG(nHeight) / (0xffffffffffffffff / ((toTime - fromTime) + 1)); - // boundary and power of 2 can make it exceed toTime by 1 - unlocktime = i = (unlocktime + fromTime) <= toTime ? i : i - 1; + unlocktime = blockPRG(nHeight) / (0xffffffffffffffff / ((ASSETCHAINS_TIMEUNLOCKTO - ASSETCHAINS_TIMEUNLOCKFROM) + 1)); + // boundary and power of 2 can make it exceed to time by 1 + unlocktime = unlocktime + ASSETCHAINS_TIMEUNLOCKFROM; + if (unlocktime > ASSETCHAINS_TIMEUNLOCKTO) + unlocktime--; } return (unlocktime); } -// create a CLTV output script and return the script and its P2SH address +// 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 -int64_t komodo_block_timelockscript(uint8_t *script, uint8_t *p2sh160, uint8_t *taddrrmd160, uint32_t nHeight, uint64_t fromTime, uint64_t toTime) +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, unlocktime = komodo_pr_unlocktime(nHeight, fromTime, toTime); + 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(unlocktime); -} - -// create an otherwise normal output script to a single address, based on consensus rules, -// including a pseudo random time lock based on block height of this chain and coinbase subsidy -int64_t komodo_coinbase_ouputscript(uint8_t *script, uint8_t *p2sh160, uint8_t *taddrrmd160, int64_t nSubsidy, uint32_t nHeight) -{ - int n = 0; - - // if it should be locked, lock it, otherwise use standard spend script - if (nSubsidy >= ASSETCHAINS_TIMELOCKGTE) - return komodo_block_timelockscript(script, p2sh160, taddrrmd160, nHeight, ASSETCHAINS_TIMEUNLOCKFROM, ASSETCHAINS_TIMEUNLOCKTO); - else - { - n = komodo_standardspend(script, n, taddrrmd160); - calc_rmd160_sha256(p2sh160, script, n); - return (0); - } + return(n); } long _stripwhite(char *buf,int accept) diff --git a/src/main.cpp b/src/main.cpp index b478bc851..72b3f20f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -895,26 +895,67 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in } /** - * Check that a P2SH coinbase transaction follows consensus rules valid at a given block height. - * - * Notes: - * 1. AcceptToMemoryPool calls CheckTransaction and this function. - * 2. ProcessNewBlock calls AcceptBlock, which calls CheckBlock (which calls CheckTransaction) - * and ContextualCheckBlock (which calls this function). + * Ensure that a coinbase transaction is structured according to the consensus rules of the + * chain */ -int32_t ContextualCheckCoinbaseTx(const CTransaction &tx, uint32_t nHeight) +bool ContextualCheckCoinbaseTransaction(const CTransaction& tx, const int nHeight) { - int i; - uint64_t total = 0; - uint64_t timelock = komodo_pr_unlocktime(nHeight, ASSETCHAINS_TIMEUNLOCKFROM, ASSETCHAINS_TIMEUNLOCKTO); + 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; - for (int i = 0; i < tx.vout.size(); i++) + // if time locks are on, ensure that this coin base is time locked exactly as it should be + if (total >= ASSETCHAINS_TIMELOCKGTE) { - const CScript *script = &(tx.vout[i].scriptPubKey); - // if there should be a timelock, get the time lock from the script and return it + // 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 + { + 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) + { + if (tx.vout[1].scriptPubKey.data()[2] == OPRETTYPE_COINBASETIMELOCK && i >= 21) + { + // 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)) + { + i -= 1; + memcpy(script, (uint8_t *)tx.vout[1].scriptPubKey.data()+3, 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; + } + } + } } + return(i != 0); } /** @@ -989,21 +1030,9 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, if (tx.IsCoinBase()) { - int i; - int64_t total = 0; - uint8_t script[256], scriptHash160[20]; - - for (i = 0; total += tx.vout[i].IsNull() ? 0 : tx.vout[i].nValue, i < tx.vout.size(); i++); - - // if time locks are on, ensure that this coin base is time locked exactly as it should be - if (total >= ASSETCHAINS_TIMELOCKGTE) - { - for (i = 0; i < tx.vout.size(); i++) - { - // validate that the outputs are locked for the proper time - // uint64_t i = komodo_block_timelockscript(script, scriptHash160, tx.addr, nHeight, ASSETCHAINS_TIMEUNLOCKFROM, ASSETCHAINS_TIMEUNLOCKTO) - } - } + if (!ContextualCheckCoinbaseTransaction(tx, nHeight)) + return state.DoS(100, error("CheckTransaction(): invalid script data for coinbase time lock"), + REJECT_INVALID, "bad-txns-invalid-script-data-for-coinbase-time-lock"); } return true; } diff --git a/src/miner.cpp b/src/miner.cpp index c69bfeeb9..cc2c8e24e 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -107,7 +107,8 @@ 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]; +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]; @@ -120,6 +121,10 @@ 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); CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) { @@ -389,13 +394,34 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) CMutableTransaction txNew = CreateNewContextualCMutableTransaction(chainparams.GetConsensus(), nHeight); txNew.vin.resize(1); txNew.vin[0].prevout.SetNull(); + txNew.vin[0].scriptSig = CScript() << nHeight << OP_0; + txNew.vout.resize(1); txNew.vout[0].scriptPubKey = scriptPubKeyIn; - txNew.vout[0].nValue = GetBlockSubsidy(nHeight,chainparams.GetConsensus()); + txNew.vout[0].nValue = GetBlockSubsidy(nHeight,chainparams.GetConsensus()) + nFees; txNew.nExpiryHeight = 0; - // Add fees - txNew.vout[0].nValue += nFees; - txNew.vin[0].scriptSig = CScript() << nHeight << OP_0; + + // check if coinbase transactions must be time locked at current subsidy and make the time lock 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]; + + txNew.vout.resize(2); + + memcpy(taddr, ((uint8_t *)scriptPubKeyIn.data()) + 2, sizeof(taddr)); + + 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[1].nValue = 0; + } /*if ( ASSETCHAINS_SYMBOL[0] == 0 ) {