From 905fe35e5002bfd665fe88849aca050fb66d45fb Mon Sep 17 00:00:00 2001 From: miketout Date: Wed, 3 Oct 2018 16:26:41 -0700 Subject: [PATCH] More work on CoinbaseGuard and validation --- src/cc/CCinclude.h | 3 +- src/cc/CCutils.cpp | 4 +- src/cc/CoinbaseGuard.cpp | 138 ++++++++++++++++++++++++++++++++------- src/cc/CoinbaseGuard.h | 14 ++-- src/script/script.h | 1 + src/script/standard.cpp | 88 +++++++++++++++++++++++++ src/script/standard.h | 27 ++------ src/wallet/wallet.cpp | 2 +- 8 files changed, 224 insertions(+), 53 deletions(-) diff --git a/src/cc/CCinclude.h b/src/cc/CCinclude.h index 119325240..fa4ea7960 100644 --- a/src/cc/CCinclude.h +++ b/src/cc/CCinclude.h @@ -112,7 +112,8 @@ int32_t iguana_rwbignum(int32_t rwflag,uint8_t *serialized,int32_t len,uint8_t * CScript GetScriptForMultisig(int nRequired, const std::vector& keys); int64_t CCaddress_balance(char *coinaddr); CPubKey CCtxidaddr(char *txidaddr,uint256 txid); -bool GetCCParams(Eval* eval, const CTransaction &tx, uint32_t nIn, std::vector> &preConditions, std::vector> ¶ms); +bool GetCCParams(Eval* eval, const CTransaction &tx, uint32_t nIn, + CTransaction &txOut, std::vector> &preConditions, std::vector> ¶ms); int64_t OraclePrice(int32_t height,uint256 reforacletxid,char *markeraddr,char *format); uint8_t DecodeOraclesCreateOpRet(const CScript &scriptPubKey,std::string &name,std::string &description,std::string &format); diff --git a/src/cc/CCutils.cpp b/src/cc/CCutils.cpp index 4b465e44e..64a832c3a 100644 --- a/src/cc/CCutils.cpp +++ b/src/cc/CCutils.cpp @@ -197,9 +197,9 @@ bool Getscriptaddress(char *destaddr,const CScript &scriptPubKey) return(false); } -bool GetCCParams(Eval* eval, const CTransaction &tx, uint32_t nIn, std::vector> &preConditions, std::vector> ¶ms) +bool GetCCParams(Eval* eval, const CTransaction &tx, uint32_t nIn, + CTransaction &txOut, std::vector> &preConditions, std::vector> ¶ms) { - CTransaction txOut; uint256 blockHash; bool isValid = false; diff --git a/src/cc/CoinbaseGuard.cpp b/src/cc/CoinbaseGuard.cpp index dc243ba3a..84841adbb 100644 --- a/src/cc/CoinbaseGuard.cpp +++ b/src/cc/CoinbaseGuard.cpp @@ -12,6 +12,9 @@ #include "CoinbaseGuard.h" #include "script/script.h" #include "main.h" +#include "hash.h" + +#include "streams.h" extern int32_t VERUS_MIN_STAKEAGE; @@ -97,6 +100,22 @@ CStakeParams::CStakeParams(std::vector> vData) } } +bool GetStakeParams(const CTransaction &stakeTx, CStakeParams &stakeParams) +{ + std::vector> vData = std::vector>(); + + if (stakeTx.vin.size() == 1 && + stakeTx.vout.size() == 2 && + stakeTx.vout[0].nValue > 0 && + stakeTx.vout[1].scriptPubKey.IsOpReturn() && + UnpackStakeOpRet(stakeTx, vData)) + { + stakeParams = CStakeParams(vData); + return true; + } + return false; +} + // this validates everything, except the PoS eligibility and the actual stake spend. the only time it matters // is to validate a properly formed stake transaction for either pre-check before PoS validity check, or to // validate the stake transaction on a fork that will be used to spend a winning stake that cheated by being posted @@ -108,7 +127,7 @@ bool ValidateStakeTransaction(const CTransaction &stakeTx, CStakeParams &stakePa // a valid stake transaction has one input and two outputs, one output is the monetary value and one is an op_ret with CStakeParams // stake output #1 must be P2PK or P2PKH, unless a delegate for the coinbase is specified - bool isValid = false;; + bool isValid = false; if (stakeTx.vin.size() == 1 && stakeTx.vout.size() == 2 && stakeTx.vout[0].nValue > 0 && @@ -171,36 +190,100 @@ bool MakeGuardedOutput(CAmount value, CPubKey &dest, CTransaction &stakeTx, CTxO // destination address or a properly signed stake transaction of the same utxo on a fork vout = MakeCC1of2vout(EVAL_COINBASEGUARD, value, dest, ccAddress); - // add parameters to scriptPubKey - COptCCParams p = COptCCParams(COptCCParams::VERSION, EVAL_COINBASEGUARD, 1, 2); + std::vector vPubKeys = std::vector(); + vPubKeys.push_back(dest); + vPubKeys.push_back(ccAddress); + + std::vector> vData = std::vector>(); - std::vector a1, a2; - CKeyID id1 = dest.GetID(); - CKeyID id2 = ccAddress.GetID(); - a1 = std::vector(id1.begin(), id1.end()); - a2 = std::vector(id2.begin(), id2.end()); + CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); - // version - // utxo source hash - // utxo source output - // destination's pubkey - CKeyID key = dest.GetID(); - vout.scriptPubKey << p.AsVector() << OP_DROP - << a1 << OP_DROP << a2 << OP_DROP - << std::vector(stakeTx.vin[0].prevout.hash.begin(), stakeTx.vin[0].prevout.hash.end()) << OP_DROP - << stakeTx.vin[0].prevout.n << OP_DROP; + hw << stakeTx.vin[0].prevout.hash; + hw << stakeTx.vin[0].prevout.n; + uint256 utxo = hw.GetHash(); + vData.push_back(std::vector(utxo.begin(), utxo.end())); + + CStakeParams p; + if (GetStakeParams(stakeTx, p)) + { + // prev block hash and height is here to make validation easy + vData.push_back(std::vector(p.prevHash.begin(), p.prevHash.end())); + std::vector height = std::vector(4); + for (int i = 0; i < 4; i++) + { + height[i] = (p.blkHeight >> (8 * i)) & 0xff; + } + + COptCCParams ccp = COptCCParams(COptCCParams::VERSION, EVAL_COINBASEGUARD, 1, 2, vPubKeys, vData); + + vout.scriptPubKey << ccp.AsVector() << OP_DROP; + return true; + } return false; } -// this creates a spend using a stake transaction -bool MakeGuardedSpend(CTxIn &vin, CPubKey &dest, CTransaction &pCheater) +// validates if a stake transaction is both valid and cheating, defined by: +// the same exact utxo source, a target block height of later than that of this tx that is also targeting a fork +// of the chain. we know the transaction is a coinbase +bool ValidateCheatingStake(const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx) +{ + if (ccTx.IsCoinBase()) + { + CStakeParams p; + if (ValidateStakeTransaction(cheatTx, p)) + { + std::vector> vParams = std::vector>(); + CScript dummy; + + if (ccTx.vout[voutNum].scriptPubKey.IsPayToCryptoCondition(&dummy, vParams) && vParams.size() > 0) + { + COptCCParams ccp = COptCCParams(vParams[0]); + if (ccp.IsValid() & ccp.vData.size() >= 3 && ccp.vData[2].size() <= 4) + { + CVerusHashWriter hw = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); + + hw << cheatTx.vin[0].prevout.hash; + hw << cheatTx.vin[0].prevout.n; + uint256 utxo = hw.GetHash(); + uint256 hash = uint256(ccp.vData[1]); + + uint32_t height = 0; + for (int i = 0; i < ccp.vData[2].size(); i++) + { + height = height << 8 + ccp.vData[2][i]; + } + + if (hash == uint256(ccp.vData[0]) && p.prevHash != hash && p.blkHeight >= height) + { + return true; + } + } + } + } + } + return false; +} + +// this attaches an opret to a mutable transaction that provides the necessary evidence of a signed, cheating stake transaction +bool MakeCheatEvidence(CMutableTransaction &mtx, const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx) { CCcontract_info *cp,C; + std::vector vch; + CDataStream s = CDataStream(SER_DISK, CLIENT_VERSION); - cp = CCinit(&C,EVAL_COINBASEGUARD); - CC cc; - vin.scriptSig = CCPubKey(MakeCCcond1of2(EVAL_COINBASEGUARD, dest, CPubKey(ParseHex(cp->CChexstr)))); + if (ValidateCheatingStake(ccTx, voutNum, cheatTx)) + { + CTxOut vOut = CTxOut(); + + CScript vData = CScript(); + cheatTx.Serialize(s); + vch = std::vector(s.begin(), s.end()); + vData << OPRETTYPE_STAKECHEAT << vch; + vOut.scriptPubKey << OP_RETURN << std::vector(vData.begin(), vData.end()); + vOut.nValue = 0; + mtx.vout.push_back(vOut); + } } bool CoinbaseGuardValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn) @@ -209,7 +292,7 @@ bool CoinbaseGuardValidate(struct CCcontract_info *cp, Eval* eval, const CTransa // validate this spend of a transaction with it being past any applicable time lock and one of the following statements being true: // 1. the spend is signed by the original output destination's private key and normal payment requirements, spends as normal // 2. the spend is signed by the private key of the CoinbaseGuard contract and pushes a signed stake transaction - // with the same exact utxo source, a target block height of later than that of this tx that is also targeting a fork + // with the same exact utxo source, a target block height of later than or equal to this tx that is targeting a fork // of the chain // first, check to see if the spending contract is signed by the default destination address @@ -218,12 +301,19 @@ bool CoinbaseGuardValidate(struct CCcontract_info *cp, Eval* eval, const CTransa // get preConditions and parameters std::vector> preConditions = std::vector>(); std::vector> params = std::vector>(); + CTransaction txOut; - if (GetCCParams(eval, tx, nIn, preConditions, params)) + if (GetCCParams(eval, tx, nIn, txOut, preConditions, params)) { CC *cc = GetCryptoCondition(tx.vin[nIn].scriptSig); printf("CryptoCondition code %x\n", *cc->code); + + if (preConditions.size() > 0 && params.size() > 0) + { + COptCCParams ccp = COptCCParams(preConditions[1]); + } + // check any applicable time lock // determine who signed // if from receiver's priv key, success diff --git a/src/cc/CoinbaseGuard.h b/src/cc/CoinbaseGuard.h index f2959e4d8..9a8af0cd9 100644 --- a/src/cc/CoinbaseGuard.h +++ b/src/cc/CoinbaseGuard.h @@ -41,9 +41,11 @@ class CStakeParams std::vector AsVector() { std::vector ret; - CScript scr = CScript() << OPRETTYPE_STAKEPARAMS - << srcHeight << blkHeight - << std::vector(prevHash.begin(), prevHash.end()); + CScript scr = CScript(); + scr << OPRETTYPE_STAKEPARAMS; + scr << srcHeight; + scr << blkHeight; + scr << std::vector(prevHash.begin(), prevHash.end()); if (pk.IsValid()) { @@ -59,11 +61,15 @@ class CStakeParams bool UnpackStakeOpRet(const CTransaction &stakeTx, std::vector> &vData); +bool GetStakeParams(const CTransaction &stakeTx, CStakeParams &stakeParams); + bool ValidateStakeTransaction(const CTransaction &stakeTx, CStakeParams &stakeParams); +bool ValidateCheatingStake(const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx); + bool MakeGuardedOutput(CAmount value, CPubKey &dest, CTransaction &stakeTx, CTxOut &vout); -bool MakeGuardedSpend(CTxIn &vin, CPubKey &dest, CTransaction &pCheater); +bool MakeCheatEvidence(CMutableTransaction &mtx, const CTransaction &ccTx, uint32_t voutNum, const CTransaction &cheatTx); bool CoinbaseGuardValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx, uint32_t nIn); diff --git a/src/script/script.h b/src/script/script.h index e15d58bb4..fee9dce83 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -20,6 +20,7 @@ #define OPRETTYPE_TIMELOCK 1 #define OPRETTYPE_STAKEPARAMS 2 +#define OPRETTYPE_STAKECHEAT 3 static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 1db594953..d1245c6f2 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -19,6 +19,94 @@ typedef vector valtype; unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY; +COptCCParams::COptCCParams(std::vector &vch) +{ + CScript inScr = CScript(vch.begin(), vch.end()); + if (inScr.size() > 1) + { + CScript::const_iterator pc = inScr.begin(); + opcodetype opcode; + std::vector> data; + std::vector param; + bool valid = true; + + while (pc < inScr.end()) + { + param.clear(); + if (inScr.GetOp(pc, opcode, param)) + { + if (opcode > 0 && opcode <= OP_PUSHDATA4 && param.size() > 0) + { + data.push_back(param); + } + else + { + valid = false; + break; + } + } + } + + if (valid && pc == inScr.end() && data.size() > 0) + { + version = 0; + param = data[0]; + if (param.size() == 4) + { + version = param[0]; + evalCode = param[1]; + n = param[2]; + m = param[3]; + if (version != VERSION || m != 1 || (n != 1 && n != 2) || data.size() <= n) + { + // we only support one version, and 1 of 1 or 1 of 2 now, so set invalid + version = 0; + } + else + { + // load keys and data + vKeys.clear(); + vData.clear(); + int i; + for (i = 1; i <= n; i++) + { + vKeys.push_back(CPubKey(data[i])); + if (!vKeys[vKeys.size() - 1].IsValid()) + { + version = 0; + break; + } + } + if (version != 0) + { + // get the rest of the data + for ( ; i < data.size(); i++) + { + vData.push_back(data[i]); + } + } + } + } + } + } +} + +std::vector COptCCParams::AsVector() +{ + CScript cData = CScript(); + + cData << std::vector({version, evalCode, n, m}); + for (auto k : vKeys) + { + cData << std::vector(k.begin(), k.end()); + } + for (auto d : vData) + { + cData << std::vector(d); + } + return std::vector(cData.begin(), cData.end()); +} + CScriptID::CScriptID(const CScript& in) : uint160(Hash160(in.begin(), in.end())) {} const char* GetTxnOutputType(txnouttype t) diff --git a/src/script/standard.h b/src/script/standard.h index 64f7a26ab..561f09cd8 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -91,34 +91,19 @@ class COptCCParams uint8_t version; uint8_t evalCode; uint8_t m, n; // for m of n sigs required, n pub keys for sigs will follow + std::vector vKeys; // n public keys to aid in signing + std::vector> vData; // extra parameters COptCCParams() : version(0), evalCode(0), n(0), m(0) {} - COptCCParams(uint8_t ver, uint8_t code, uint8_t _n, uint8_t _m) : version(ver), evalCode(code), n(_n), m(_m) {} + COptCCParams(uint8_t ver, uint8_t code, uint8_t _n, uint8_t _m, std::vector &vkeys, std::vector> &vdata) : + version(ver), evalCode(code), n(_n), m(_m), vKeys(vkeys), vData(vdata) {} - COptCCParams(std::vector &vch) - { - version = 0; - if (vch.size() == 4) - { - version = vch[0]; - evalCode = vch[1]; - n = vch[2]; - m = vch[3]; - if (version != VERSION && m == 1 && (n == 1 || n == 2)) - { - // we only support one version, and 1 of 1 or 1 of 2 now, so set invalid - version = 0; - } - } - } + COptCCParams(std::vector &vch); bool IsValid() { return version != 0; } - std::vector AsVector() - { - std::vector vch = std::vector({version, evalCode, n, m}); - } + std::vector AsVector(); }; /** Check whether a CTxDestination is a CNoDestination. */ diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 46b5fe2fc..af9ba05d7 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1306,7 +1306,7 @@ int32_t CWallet::VerusStakeTransaction(CBlock *pBlock, CMutableTransaction &txNe // if we are staking with the extended format, add the opreturn data required // TODO: uncomment the line below to save a little space after testing, remove this one - // if (extendedStake) + if (extendedStake) { uint256 srcBlock = uint256(); CBlockIndex *pSrcIndex;