diff --git a/src/Makefile.am b/src/Makefile.am index 59aff516bb..8280714e6e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -163,17 +163,14 @@ libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) libbitcoin_server_a_SOURCES = \ addrman.cpp \ alert.cpp \ - bloom.cpp \ chain.cpp \ checkpoints.cpp \ init.cpp \ leveldbwrapper.cpp \ main.cpp \ - merkleblock.cpp \ miner.cpp \ net.cpp \ noui.cpp \ - pow.cpp \ rest.cpp \ rpcblockchain.cpp \ rpcmining.cpp \ @@ -234,6 +231,7 @@ libbitcoin_common_a_SOURCES = \ allocators.cpp \ amount.cpp \ base58.cpp \ + bloom.cpp \ chainparams.cpp \ coins.cpp \ compressor.cpp \ @@ -246,7 +244,9 @@ libbitcoin_common_a_SOURCES = \ hash.cpp \ key.cpp \ keystore.cpp \ + merkleblock.cpp \ netbase.cpp \ + pow.cpp \ protocol.cpp \ pubkey.cpp \ script/interpreter.cpp \ diff --git a/src/script/bitcoinconsensus.h b/src/script/bitcoinconsensus.h index 6f47ba5d41..31a5afe7fb 100644 --- a/src/script/bitcoinconsensus.h +++ b/src/script/bitcoinconsensus.h @@ -49,6 +49,7 @@ enum bitcoinconsensus_SCRIPT_FLAGS_VERIFY_NONE = 0, bitcoinconsensus_SCRIPT_FLAGS_VERIFY_P2SH = (1U << 0), // evaluate P2SH (BIP16) subscripts bitcoinconsensus_SCRIPT_FLAGS_VERIFY_DERSIG = (1U << 2), // enforce strict DER (BIP66) compliance + bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITHDRAWS = (1U << 8), // evaluate withdrawproof opcodes }; /// Returns 1 if the input nIn of the serialized transaction pointed to by diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 0b48c7c9d7..ea97e89744 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -5,21 +5,43 @@ #include "interpreter.h" +#include + +#define FEDERATED_PEG_SIDECHAIN_ONLY + #include "primitives/transaction.h" #include "crypto/ripemd160.h" #include "crypto/sha1.h" #include "crypto/sha256.h" +#include "crypto/hmac_sha256.h" #include "eccryptoverify.h" +#include "merkleblock.h" +#include "pow.h" #include "pubkey.h" #include "script/script.h" +#include "script/standard.h" +#include "streams.h" #include "uint256.h" +#include "utilstrencodings.h" using namespace std; typedef vector valtype; +//! anonymous namespace namespace { +class CSecp256k1Init { +public: + CSecp256k1Init() { + secp256k1_start(SECP256K1_START_VERIFY); + } + ~CSecp256k1Init() { + secp256k1_stop(); + } +}; +static CSecp256k1Init instance_of_csecp256k1; + inline bool set_success(ScriptError* ret) { if (ret) @@ -235,6 +257,26 @@ bool static CheckMinimalPush(const valtype& data, opcodetype opcode) { return true; } +bool static WithdrawProofReadStackItem(const vector& stack, const bool fRequireMinimal, int *stackOffset, valtype& read) +{ + if (stack.size() < size_t(-(*stackOffset))) + return false; + int pushCount = CScriptNum(stacktop(*stackOffset), fRequireMinimal).getint(); + if (pushCount < 0 || pushCount > 2000 || stack.size() < size_t(-(*stackOffset) + pushCount)) + return false; + (*stackOffset)--; + + read.reserve(pushCount > 1 ? pushCount * 520 : 0); + for (int i = pushCount - 1; i >= 0; i--) { + if (i != 0 && stacktop((*stackOffset) - i).size() != 520) + return false; + const valtype& stackElem = stacktop((*stackOffset) - i); + read.insert(read.end(), stackElem.begin(), stackElem.end()); + } + (*stackOffset) -= pushCount; + return true; +} + bool EvalScript(vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror) { static const CScriptNum bnZero(0); @@ -421,7 +463,6 @@ bool EvalScript(vector >& stack, const CScript& script, un case OP_NOP1: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: - case OP_WITHDRAWPROOFVERIFY: case OP_REORGPROOFVERIFY: { if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS); @@ -997,6 +1038,411 @@ bool EvalScript(vector >& stack, const CScript& script, un } break; + //TODO: Need strict size limits on txn so that you cant be overly-large and break ds. reorg proofs + case OP_WITHDRAWPROOFVERIFY: + { + // In the make-withdraw case, reads the following from the stack: + // 1. HASH160(<...>) script which is used to extend checks + // 2. genesis block hash of the chain the withdraw is coming from +#ifndef FEDERATED_PEG_SIDECHAIN_ONLY + // TODO: 3. The compressed SPV proof +#endif + // 4. the coinbase tx within the locking block + // 5. the index within the locking tx's outputs we are claiming + // 6. the locking tx itself + // 7. the merkle block structure which contains the block in which + // the locking transaction is present +#ifdef FEDERATED_PEG_SIDECHAIN_ONLY + // 8. The contract which we are expected to send coins to +#endif + // 8. The scriptSig used to satisfy the <...> script + // 9. <...> script which is used to extend checks + // + // In the combine-outputs case, reads the following from the stack: + // 1. HASH160(<...>) script which is used to extend checks + // 2. genesis block hash of the chain the withdraw is coming from + + if (flags & SCRIPT_VERIFY_WITHDRAW) { + if (stack.size() < 10 && stack.size() != 2) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + const valtype &vsecondScriptPubKeyHash = stacktop(-1); + if (vsecondScriptPubKeyHash.size() != 20) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + const valtype &vgenesisHash = stacktop(-2); + if (vgenesisHash.size() != 32) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + assert(checker.GetValueIn() != -1); // Not using a NoWithdrawSignatureChecker + + if (stack.size() == 2) { // increasing value of locked coins + CAmount minValue = checker.GetValueIn(); + CTxOut newOutput = checker.GetOutputOffsetFromCurrent(0); + if (newOutput.IsNull()) { + newOutput = checker.GetOutputOffsetFromCurrent(-1); + minValue += checker.GetValueInPrevIn(); + } + if (newOutput.scriptPubKey != script || newOutput.nValue < minValue) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + } else { // stack.size() == 10...ie regular withdraw + int stackReadPos = -3; +#ifndef FEDERATED_PEG_SIDECHAIN_ONLY + valtype vspvProof; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, vspvProof)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); +#endif + + valtype vlockCoinbaseTx; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, vlockCoinbaseTx)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + if (stack.size() < size_t(-stackReadPos)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + const valtype &vlockTxOutIndex = stacktop(stackReadPos--); + + valtype vlockTx; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, vlockTx)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype vmerkleBlock; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, vmerkleBlock)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); +#ifdef FEDERATED_PEG_SIDECHAIN_ONLY + if (stack.size() < size_t(-stackReadPos)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + valtype vcontract = std::vector(stacktop(stackReadPos--)); +#endif + if (stack.size() < size_t(-stackReadPos)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + const valtype &vsecondScriptSig = stacktop(stackReadPos--); + if (stack.size() < size_t(-stackReadPos)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + const valtype &vsecondScriptPubKey = stacktop(stackReadPos--); + + uint256 genesishash(vgenesisHash); + + try { + CMerkleBlock merkleBlock; + CDataStream(vmerkleBlock, SER_NETWORK, PROTOCOL_VERSION) >> merkleBlock; + if (!CheckProofOfWork(merkleBlock.header.GetHash(), merkleBlock.header.nBits)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + vector txHashes; + if (merkleBlock.txn.ExtractMatches(txHashes) != merkleBlock.header.hashMerkleRoot || txHashes.size() != 2) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + // We disallow returns from the genesis block, allowing sidechains to + // make genesis outputs spendable with a 21m initially-locked-to-btc + // distributing transaction. + if (merkleBlock.header.GetHash() == genesishash) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + +#ifndef FEDERATED_PEG_SIDECHAIN_ONLY + //TODO: Check the SPV proof here (must point to genesishash, contain merkleBlock.header.GetHash()) +#endif + + CTransaction locktx; + CDataStream(vlockTx, SER_NETWORK, PROTOCOL_VERSION) >> locktx; + + int nlocktxOut = CScriptNum(vlockTxOutIndex, fRequireMinimal).getint(); + if (nlocktxOut < 0 || (unsigned int)nlocktxOut >= locktx.vout.size()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + if (locktx.GetHash() != txHashes[1]) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + CTransaction coinbasetx; + CDataStream(vlockCoinbaseTx, SER_NETWORK, PROTOCOL_VERSION) >> coinbasetx; + + if (coinbasetx.GetHash() != txHashes[0] || !coinbasetx.IsCoinBase()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + valtype vcoinbaseHeight; + CScript::const_iterator coinbasepc = coinbasetx.vin[0].scriptSig.begin(); + opcodetype opcodeTmp; + if (!coinbasetx.vin[0].scriptSig.GetOp(coinbasepc, opcodeTmp, vcoinbaseHeight) || vcoinbaseHeight.size() < 1) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + int nLockHeight = CScriptNum(vcoinbaseHeight, fRequireMinimal).getint(); + +#ifdef FEDERATED_PEG_SIDECHAIN_ONLY + if (vcontract.size() != 40) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + CScript scriptDestination(CScript() << OP_1 << ParseHex("03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd") << OP_1 << OP_CHECKMULTISIG); + { + CScript::iterator sdpc = scriptDestination.begin(); + vector vch; + while (scriptDestination.GetOp(sdpc, opcodeTmp, vch)) + { + assert((vch.size() == 33 && opcodeTmp < OP_PUSHDATA4) || + (opcodeTmp <= OP_16 && opcodeTmp >= OP_1) || opcodeTmp == OP_CHECKMULTISIG); + if (vch.size() == 33) + { + unsigned char tweak[32]; + unsigned char *pub_start = &(*(sdpc - 33)); + CHMAC_SHA256(pub_start, 33).Write(&vcontract[0], 40).Finalize(tweak); + if (secp256k1_ec_pubkey_tweak_add(pub_start, 33, tweak) == 0) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + } + } + } + + CScriptID expectedP2SH(scriptDestination); + if (locktx.vout[nlocktxOut].scriptPubKey != GetScriptForDestination(expectedP2SH)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + vcontract.erase(vcontract.begin() + 4, vcontract.begin() + 20); // Remove the nonce from the contract before further processing +#else + const CScript &lockingScriptPubKey = locktx.vout[nlocktxOut].scriptPubKey; + //TODO: Make script checker expose GenesisBlockHash + if (!lockingScriptPubKey.IsWithdrawLock(checker.GenesisBlockHash(), true, true)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + valtype vcontract; + CScript::const_iterator locktxpc = lockingScriptPubKey.begin(); + assert(lockingScriptPubKey.GetOp(locktxpc, opcodeTmp, vcontract)); + if (vcontract.size() != 24) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); +#endif + assert(vcontract.size() == 24); + if (vcontract[0] != 'P' || vcontract[1] != '2' || vcontract[2] != 'S' || vcontract[3] != 'H') + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + CAmount withdrawVal = locktx.vout[nlocktxOut].nValue; + const CTxOut newLockOutput = checker.GetOutputOffsetFromCurrent(1); + if (newLockOutput.scriptPubKey != script || newLockOutput.nValue < checker.GetValueIn() - withdrawVal) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + const CTxOut withdrawOutput = checker.GetOutputOffsetFromCurrent(0); + if (withdrawOutput.nValue < withdrawVal) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + uint256 locktxHash = locktx.GetHash(); + std::vector vlocktxHash(locktxHash.begin(), locktxHash.end()); + CScript expectedWithdrawScriptPubKeyStart = CScript() << OP_IF << nLockHeight + << std::vector(vlocktxHash.rbegin(), vlocktxHash.rend()) << nlocktxOut +#ifndef FEDERATED_PEG_SIDECHAIN_ONLY + << 42 // (TODO: Measure of work from genesis to proof tip) +#endif + << CScriptNum(withdrawOutput.nValue - withdrawVal) // Fraud bounty + << vsecondScriptPubKeyHash << vgenesisHash << OP_REORGPROOFVERIFY << OP_ELSE; + // << lockTime << OP_CHECKSEQUENCEVERIFY << OP_DROP << OP_HASH160 + // << std::vector(vcontract.begin() + 4, vcontract.begin() + 24) << OP_EQUAL << OP_ENDIF; + if (withdrawOutput.scriptPubKey.size() < expectedWithdrawScriptPubKeyStart.size() || + memcmp(&withdrawOutput.scriptPubKey[0], &expectedWithdrawScriptPubKeyStart[0], expectedWithdrawScriptPubKeyStart.size()) != 0) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + CScript::const_iterator withdrawOutputpc = withdrawOutput.scriptPubKey.begin() + expectedWithdrawScriptPubKeyStart.size(); + valtype vlockTime; + if (!withdrawOutput.scriptPubKey.GetOp(withdrawOutputpc, opcodeTmp, vlockTime)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + int nWithdrawLockTime = CScriptNum(vlockTime, fRequireMinimal).getint(); + if ((unsigned int)nWithdrawLockTime >= LOCKTIME_THRESHOLD || nWithdrawLockTime < 1) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + if (!withdrawOutput.scriptPubKey.GetOp(withdrawOutputpc, opcodeTmp, vlockTime) || opcodeTmp != OP_CHECKSEQUENCEVERIFY) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + if (!withdrawOutput.scriptPubKey.GetOp(withdrawOutputpc, opcodeTmp, vlockTime) || opcodeTmp != OP_DROP) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + if (!withdrawOutput.scriptPubKey.GetOp(withdrawOutputpc, opcodeTmp, vlockTime) || opcodeTmp != OP_HASH160) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + if (!withdrawOutput.scriptPubKey.GetOp(withdrawOutputpc, opcodeTmp, vlockTime) || vlockTime != std::vector(vcontract.begin() + 4, vcontract.begin() + 24)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + if (!withdrawOutput.scriptPubKey.GetOp(withdrawOutputpc, opcodeTmp, vlockTime) || opcodeTmp != OP_EQUAL) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + if (!withdrawOutput.scriptPubKey.GetOp(withdrawOutputpc, opcodeTmp, vlockTime) || opcodeTmp != OP_ENDIF) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + if (withdrawOutput.scriptPubKey.GetOp(withdrawOutputpc, opcodeTmp, vlockTime)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + valtype vsecondScriptPubKeyHashCmp(20); + CHash160().Write(begin_ptr(vsecondScriptPubKey), vsecondScriptPubKey.size()).Finalize(begin_ptr(vsecondScriptPubKeyHashCmp)); + if (vsecondScriptPubKeyHash.size() != 20 || vsecondScriptPubKeyHashCmp != vsecondScriptPubKeyHash) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + vector > withdrawStack; + if (!EvalScript(withdrawStack, CScript(vsecondScriptSig), flags & ~SCRIPT_VERIFY_WITHDRAW, checker, serror)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + // Push: + // 1. fee of this transaction (int64) + // 2. Fraud bounty (int64) + // 3. relative locktime + // 4. <1> indicating we are checking a withdraw proof + withdrawStack.push_back(CScriptNum(checker.GetTransactionFee()).getvch()); + withdrawStack.push_back(CScriptNum(withdrawOutput.nValue - withdrawVal).getvch()); + withdrawStack.push_back(vlockTime); + withdrawStack.push_back(std::vector(1, 1)); +#ifndef FEDERATED_PEG_SIDECHAIN_ONLY + //TODO: Push a bunch of other info onto the withdrawStack about the SPV proof +#endif + if (!EvalScript(withdrawStack, CScript(vsecondScriptPubKey), flags & ~SCRIPT_VERIFY_WITHDRAW, checker, serror)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + if (withdrawStack.empty() || CastToBool(withdrawStack.back()) == false) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + +#ifdef FEDERATED_PEG_SIDECHAIN_ONLY + //TODO: Check that we're spending from a valid, buried bitcoin block +#endif + } catch (std::exception& e) { + // Probably invalid encoding of something which was deserialized + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + } + } + } // else...OP_NOP3 + } + break; + + case OP_REORGPROOFVERIFY: + { + if (flags & SCRIPT_VERIFY_WITHDRAW) { + // Reads the following from the stack: + // 1. Genesis block hash + // 2. HASH160(<...>) script which is used to extend checks + // 3. Fraud bounty value (64-bit CScriptNum!) +#ifndef FEDERATED_PEG_SIDECHAIN_ONLY + // 4. Work used to create original proof +#endif + // 5. lock transaction output spent + // 6. lock transaction hash + // 7. lock transaction block height + // 8. proof type + int stackReadPos = -1; + if (stack.size() < 8) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + assert(checker.GetValueIn() != -1); // Not using a NoWithdrawSignatureChecker + + const valtype &vgenesisHash = stacktop(stackReadPos--); + if (vgenesisHash.size() != 32) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + const valtype &vsecondScriptPubKeyHash = stacktop(stackReadPos--); + if (vsecondScriptPubKeyHash.size() != 20) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + + const valtype &vfraudBountyValue = stacktop(stackReadPos--); +#ifndef FEDERATED_PEG_SIDECHAIN_ONLY + const valtype &voriginalProofWork = stacktop(stackReadPos--); +#endif + int nlockTxOutIndex = CScriptNum(stacktop(stackReadPos--), fRequireMinimal).getint(); + const valtype &vlockTxHashStack = stacktop(stackReadPos--); + if (vlockTxHashStack.size() != 32) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + valtype vlockTxHash; + vlockTxHash.resize(32); + std::reverse_copy(vlockTxHashStack.begin(), vlockTxHashStack.end(), vlockTxHash.begin()); + + const valtype &vlockBlockHeight = stacktop(stackReadPos--); + int proofType = CScriptNum(stacktop(stackReadPos--), fRequireMinimal).getint(); + if (proofType == 1) { // Double-spent withdraw + // We need to have complete proof that two transactions double-spent each other: + // So we read the following from the stack: + // 9. merkle block with tx we are proving against (ie our input tx) + // 10. the full transaction of the original withdraw + // 11. the input index in the transaction above which shows the double-spend + // 12. the transaction which is spent in the above input + // 13. merkle block containing the original withdraw tx, required only if they are not in the same block + valtype vmerkleBlockInputTx; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, vmerkleBlockInputTx)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype voriginalWithdrawTx; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, voriginalWithdrawTx)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype voriginalWithdrawTxInIndex; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, voriginalWithdrawTxInIndex)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype voriginalWithdrawOutputTx; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, voriginalWithdrawOutputTx)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + try { + CMerkleBlock merkleBlockInputTx; + CDataStream(vmerkleBlockInputTx, SER_NETWORK, PROTOCOL_VERSION) >> merkleBlockInputTx; + if (!CheckProofOfWork(merkleBlockInputTx.header.GetHash(), merkleBlockInputTx.header.nBits)) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + vector inputTxHashes; + if (merkleBlockInputTx.txn.ExtractMatches(inputTxHashes) != merkleBlockInputTx.header.hashMerkleRoot) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + if (inputTxHashes.size() != 1 && inputTxHashes.size() != 2) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + if (inputTxHashes[0] != checker.GetPrevOut().hash) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + CTransaction originalWithdrawTx; + CDataStream(voriginalWithdrawTx, SER_NETWORK, PROTOCOL_VERSION) >> originalWithdrawTx; + + CMerkleBlock merkleBlockOriginalWithdrawTx; + vector originalWithdrawHashes; + if (inputTxHashes.size() == 1) { + valtype vmerkleBlockOriginalWithdrawTx; + if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, vmerkleBlockOriginalWithdrawTx)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + CDataStream(vmerkleBlockOriginalWithdrawTx, SER_NETWORK, PROTOCOL_VERSION) >> merkleBlockOriginalWithdrawTx; + if (!CheckProofOfWork(merkleBlockOriginalWithdrawTx.header.GetHash(), merkleBlockOriginalWithdrawTx.header.nBits)) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + if (merkleBlockOriginalWithdrawTx.txn.ExtractMatches(originalWithdrawHashes) != merkleBlockOriginalWithdrawTx.header.hashMerkleRoot) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + if (originalWithdrawHashes.size() != 1 || merkleBlockOriginalWithdrawTx.header.GetHash() == merkleBlockInputTx.header.GetHash()) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + if (originalWithdrawHashes[0] != originalWithdrawTx.GetHash()) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + } else if (inputTxHashes[1] != originalWithdrawTx.GetHash()) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + int noriginalWithdrawTxIn = CScriptNum(voriginalWithdrawTxInIndex, fRequireMinimal).getint(); + if (noriginalWithdrawTxIn < 0 || (unsigned int)noriginalWithdrawTxIn >= originalWithdrawTx.vin.size()) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + const CTxIn& originalWithdrawTxInput = originalWithdrawTx.vin[noriginalWithdrawTxIn]; + + CTransaction originalWithdrawOutputTx; + CDataStream(voriginalWithdrawOutputTx, SER_NETWORK, PROTOCOL_VERSION) >> originalWithdrawOutputTx; + if (originalWithdrawTxInput.prevout.hash != originalWithdrawOutputTx.GetHash()) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + if (originalWithdrawOutputTx.vout.size() <= originalWithdrawTxInput.prevout.n) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + const CTxOut& originalWithdrawOutputTxOut = originalWithdrawOutputTx.vout[originalWithdrawTxInput.prevout.n]; + + if (!originalWithdrawOutputTxOut.scriptPubKey.IsWithdrawLock(0) || !originalWithdrawTxInput.scriptSig.IsWithdrawProof()) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + COutPoint withdrawSpent = originalWithdrawTxInput.scriptSig.GetWithdrawSpent(); + uint256 withdrawGenesisHash = originalWithdrawOutputTxOut.scriptPubKey.GetWithdrawLockGenesisHash(); + if (withdrawGenesisHash != uint256(vgenesisHash) || withdrawSpent.hash != uint256(vlockTxHash) || withdrawSpent.n != uint32_t(nlockTxOutIndex) || nlockTxOutIndex < 0) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + const CTxOut newLockOutput = checker.GetOutputOffsetFromCurrent(0); + if (newLockOutput.scriptPubKey != (CScript() << vgenesisHash << vsecondScriptPubKeyHash << OP_WITHDRAWPROOFVERIFY)) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + CAmount fraudBounty = CScriptNum(vfraudBountyValue, fRequireMinimal, 8).getint64(); + if (newLockOutput.nValue < checker.GetValueIn() - fraudBounty) + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + + //TODO: if (!checker.IsInBlockTreeAboveMe(merkleBlock.header.GetHash())) + //TODO: return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + } catch (std::exception& e) { + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + } +#ifndef FEDERATED_PEG_SIDECHAIN_ONLY + } else if (proofType == 2) { // Longer SPV chain + // Reads the following from the stack: + // 8. SPV Proof + // 9. <...> script which is used to extend checks + // 10. The scriptSig used to satisfy the <...> script + //TODO +#endif + } else + return set_error(serror, SCRIPT_ERR_REORG_VERIFY); + } // else...OP_NOP4 + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } @@ -1261,6 +1707,20 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigne return set_error(serror, SCRIPT_ERR_SIG_PUSHONLY); } + // If the scriptPubKey is not in exactly the withdrawlock format we expect, + // we will not execute WITHDRAW-related opcodes (treating them as NOPs) + // Additionally, if the scriptPubKey is a withdraw lock, the scriptSig must + // be exactly a withdraw proof (push only, in the right format), otherwise + // it would either not be valid, or confuse the processing of double-spend + // fraud proofs later. + if ((flags & SCRIPT_VERIFY_WITHDRAW) != 0) { + if (scriptPubKey.IsWithdrawLock(0)) { + if (!scriptSig.IsWithdrawProof()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY); + } else if (!scriptPubKey.IsWithdrawOutput()) + flags &= ~SCRIPT_VERIFY_WITHDRAW; + } + vector > stack, stackCopy; if (!EvalScript(stack, scriptSig, flags, checker, serror)) // serror is set @@ -1276,8 +1736,31 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigne if (CastToBool(stack.back()) == false) return set_error(serror, SCRIPT_ERR_EVAL_FALSE); + bool checkP2SH = false; + // Additional P2SH setup for withdraw outputs + if ((flags & SCRIPT_VERIFY_WITHDRAW) != 0 && scriptPubKey.IsWithdrawOutput()) + { + // stackCopy cannot be empty here, because if it was the + // OP_IF that starts off the scriptPubKey would have failed + assert(!stackCopy.empty()); + + // If the stackCopy top is true, then we ran the + // OP_REORGPROOFVERIFY branch, and do not need P2SH validation + if (CastToBool(stackCopy[stackCopy.size() - 1])) + return set_success(serror); + + popstack(stackCopy); + + if (stackCopy.empty()) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + checkP2SH = true; + } + + checkP2SH |= (flags & SCRIPT_VERIFY_P2SH) && scriptPubKey.IsPayToScriptHash(); + // Additional validation for spend-to-script-hash transactions: - if ((flags & SCRIPT_VERIFY_P2SH) && scriptPubKey.IsPayToScriptHash()) + if (checkP2SH) { // scriptSig must be literals-only or validation fails if (!scriptSig.IsPushOnly()) diff --git a/src/script/interpreter.h b/src/script/interpreter.h index bdb1390a50..bcb6ab53c5 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -13,10 +13,13 @@ #include #include +#include "amount.h" + class CPubKey; class COutPoint; class CScript; class CTransaction; +class CTxOut; class uint256; /** Signature hash types/flags */ @@ -75,7 +78,10 @@ enum SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1U << 9), // support CHECKSEQUENCEVERIFY opcode - SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = (1U << 10) + SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = (1U << 10), + + // Execute sidechain-related opcodes instead of treating them as NOPs + SCRIPT_VERIFY_WITHDRAW = (1U << 11), }; uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index 788d7ff468..3aeb198ee9 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -67,6 +67,10 @@ const char* ScriptErrorString(const ScriptError serror) return "NOPx reserved for soft-fork upgrades"; case SCRIPT_ERR_PUBKEYTYPE: return "Public key is neither compressed or uncompressed"; + case SCRIPT_ERR_WITHDRAW_VERIFY: + return "Withdraw proof validation failed"; + case SCRIPT_ERR_REORG_VERIFY: + return "Reorg/Fraud proof validation failed"; case SCRIPT_ERR_UNKNOWN_ERROR: case SCRIPT_ERR_ERROR_COUNT: default: break; diff --git a/src/script/script_error.h b/src/script/script_error.h index d29202b4e2..e74c906f86 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -51,6 +51,10 @@ typedef enum ScriptError_t /* softfork safeness */ SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS, + /* sidechains */ + SCRIPT_ERR_WITHDRAW_VERIFY, + SCRIPT_ERR_REORG_VERIFY, + SCRIPT_ERR_ERROR_COUNT } ScriptError;