mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-20 13:37:28 +02:00
Verify Withdraw proofs in script execution
This commit is contained in:
parent
8baa1b5ef5
commit
67d4bc8cf4
6 changed files with 504 additions and 6 deletions
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue