Verify Withdraw proofs in script execution

This commit is contained in:
Matt Corallo 2014-12-06 14:35:32 -08:00
parent 8baa1b5ef5
commit 67d4bc8cf4
6 changed files with 504 additions and 6 deletions

View file

@ -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 \

View file

@ -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

View file

@ -5,21 +5,43 @@
#include "interpreter.h"
#include <secp256k1.h>
#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<unsigned char> 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<valtype>& 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<vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
{
static const CScriptNum bnZero(0);
@ -421,7 +463,6 @@ bool EvalScript(vector<vector<unsigned char> >& 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<vector<unsigned char> >& 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<unsigned char>(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<uint256> 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<unsigned char> 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<unsigned char> vlocktxHash(locktxHash.begin(), locktxHash.end());
CScript expectedWithdrawScriptPubKeyStart = CScript() << OP_IF << nLockHeight
<< std::vector<unsigned char>(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<unsigned char>(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<unsigned char>(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<vector<unsigned char> > 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<unsigned char>(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<uint256> 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<uint256> 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<vector<unsigned char> > 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())

View file

@ -13,10 +13,13 @@
#include <stdint.h>
#include <string>
#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);

View file

@ -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;

View file

@ -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;