diff --git a/src/Makefile.am b/src/Makefile.am index 2950cd541c..a00e854f33 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -178,8 +178,8 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrman.cpp \ addrdb.cpp \ - bloom.cpp \ blockencodings.cpp \ + bloom.cpp \ chain.cpp \ checkpoints.cpp \ httprpc.cpp \ @@ -270,6 +270,8 @@ libbitcoin_consensus_a_SOURCES = \ consensus/validation.h \ hash.cpp \ hash.h \ + merkleblock.cpp \ + pow.cpp \ prevector.h \ primitives/block.cpp \ primitives/block.h \ @@ -298,6 +300,7 @@ libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_common_a_SOURCES = \ amount.cpp \ base58.cpp \ + bloom.cpp \ chainparams.cpp \ coins.cpp \ compressor.cpp \ @@ -306,7 +309,9 @@ libbitcoin_common_a_SOURCES = \ key.cpp \ keystore.cpp \ netaddress.cpp \ + merkleblock.cpp \ netbase.cpp \ + pow.cpp \ protocol.cpp \ scheduler.cpp \ script/sign.cpp \ diff --git a/src/script/bitcoinconsensus.h b/src/script/bitcoinconsensus.h index a8578e13a4..ea85996364 100644 --- a/src/script/bitcoinconsensus.h +++ b/src/script/bitcoinconsensus.h @@ -57,6 +57,7 @@ enum bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY = (1U << 9), // enable CHECKLOCKTIMEVERIFY (BIP65) bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKSEQUENCEVERIFY = (1U << 10), // enable CHECKSEQUENCEVERIFY (BIP112) bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS = (1U << 11), // enable WITNESS (BIP141) + bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITHDRAWS = (1U << 13), // evaluate withdrawproof opcodes bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL = bitcoinconsensus_SCRIPT_FLAGS_VERIFY_P2SH | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_DERSIG | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_NULLDUMMY | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKSEQUENCEVERIFY | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 91a4e7d5e8..8013957d58 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -5,13 +5,21 @@ #include "interpreter.h" +#include + #include "primitives/transaction.h" #include "crypto/ripemd160.h" #include "crypto/sha1.h" #include "crypto/sha256.h" +#include "crypto/hmac_sha256.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; @@ -19,6 +27,19 @@ typedef vector valtype; namespace { +static secp256k1_context *secp256k1_ctx; + +class CSecp256k1Init { +public: + CSecp256k1Init() { + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + } + ~CSecp256k1Init() { + secp256k1_context_destroy(secp256k1_ctx); + } +}; +static CSecp256k1Init instance_of_csecp256k1; + inline bool set_success(ScriptError* ret) { if (ret) @@ -245,6 +266,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, SigVersion sigversion, ScriptError* serror) { static const CScriptNum bnZero(0); @@ -428,7 +469,6 @@ bool EvalScript(vector >& stack, const CScript& script, un case OP_NOP1: case OP_NOP5: case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10: - case OP_WITHDRAWPROOFVERIFY: { if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS); @@ -1026,6 +1066,161 @@ bool EvalScript(vector >& stack, const CScript& script, un } break; + case OP_WITHDRAWPROOFVERIFY: + { + // In the make-withdraw case, reads the following from the stack: + // 1. genesis block hash of the chain the withdraw is coming from + // 2. the index within the locking tx's outputs we are claiming + // 3. the locking tx itself (WithdrawProofReadStackItem) + // 4. the merkle block structure which contains the block in which + // the locking transaction is present (WithdrawProofReadStackItem) + // 5. The contract which we are expected to send coins to + // + // In the combine-outputs case, reads the following from the stack: + // 1. genesis block hash of the chain the withdraw is coming from + + if (flags & SCRIPT_VERIFY_WITHDRAW) { + if (stack.size() < 7 && stack.size() != 1) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + const valtype &vgenesisHash = stacktop(-1); + if (vgenesisHash.size() != 32) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_FORMAT); + + assert(checker.GetValueIn() != -1); // Not using a NoWithdrawSignatureChecker + + CScript relockScript = CScript() << vgenesisHash << OP_WITHDRAWPROOFVERIFY; + + if (stack.size() == 1) { // 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 != relockScript || newOutput.nValue < minValue) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT); + } else { // stack.size() >= 7...ie regular withdraw + int stackReadPos = -2; + + 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); + + if (stack.size() < size_t(-stackReadPos)) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + valtype vcontract = std::vector(stacktop(stackReadPos--)); + + uint256 genesishash(vgenesisHash); + + try { + CMerkleBlock merkleBlock; + CDataStream merkleBlockStream(vmerkleBlock, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_BITCOIN_BLOCK_OR_TX); + merkleBlockStream >> merkleBlock; + if (!merkleBlockStream.empty() || !CheckBitcoinProof(merkleBlock.header)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK); + + vector txHashes; + vector txIndices; + if (merkleBlock.txn.ExtractMatches(txHashes, txIndices) != merkleBlock.header.hashMerkleRoot || txHashes.size() != 1) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK); + + // 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_BLOCK); + + CTransaction locktx; + CDataStream locktxStream(vlockTx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_BITCOIN_BLOCK_OR_TX); + locktxStream >> locktx; + if (!locktxStream.empty()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX); + + int nlocktxOut = CScriptNum(vlockTxOutIndex, fRequireMinimal).getint(); + if (nlocktxOut < 0 || (unsigned int)nlocktxOut >= locktx.vout.size()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX); + + if (locktx.GetHash() != txHashes[0]) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX); + + if (vcontract.size() != 40) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_FORMAT); + + opcodetype opcodeTmp; + 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]; + size_t pub_len = 33; + unsigned char *pub_start = &(*(sdpc - pub_len)); + CHMAC_SHA256(pub_start, pub_len).Write(&vcontract[0], 40).Finalize(tweak); + secp256k1_pubkey pubkey; + assert(secp256k1_ec_pubkey_parse(secp256k1_ctx, &pubkey, pub_start, pub_len) == 1); + // If someone creates a tweak that makes this fail, they broke SHA256 + assert(secp256k1_ec_pubkey_tweak_add(secp256k1_ctx, &pubkey, tweak) == 1); + assert(secp256k1_ec_pubkey_serialize(secp256k1_ctx, pub_start, &pub_len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + assert(pub_len == 33); + } + } + } + + CScriptID expectedP2SH(scriptDestination); + if (locktx.vout[nlocktxOut].scriptPubKey != GetScriptForDestination(expectedP2SH)) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT); + + vcontract.erase(vcontract.begin() + 4, vcontract.begin() + 20); // Remove the nonce from the contract before further processing + assert(vcontract.size() == 24); + + // We check values by doing the following: + // * Tx must relock at least - + // * Tx must send at least the withdraw value to its P2SH withdraw, but may send more + CAmount withdrawVal = locktx.vout[nlocktxOut].nValue; + CAmount lockValueRequired = checker.GetValueIn() - withdrawVal; + if (lockValueRequired > 0) { + const CTxOut newLockOutput = checker.GetOutputOffsetFromCurrent(1); + if (newLockOutput.IsNull() || newLockOutput.scriptPubKey != relockScript || newLockOutput.nValue < lockValueRequired) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT); + } + + const CTxOut withdrawOutput = checker.GetOutputOffsetFromCurrent(0); + if (withdrawOutput.nValue < withdrawVal) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT); + + CScript expectedWithdrawScriptPubKey; + if (vcontract[0] == 'P' && vcontract[1] == '2' && vcontract[2] == 'S' && vcontract[3] == 'H') + expectedWithdrawScriptPubKey = CScript() << OP_HASH160 << std::vector(vcontract.begin() + 4, vcontract.begin() + 24) << OP_EQUAL; + else if (vcontract[0] == 'P' && vcontract[1] == '2' && vcontract[2] == 'P' && vcontract[3] == 'H') + expectedWithdrawScriptPubKey = CScript() << OP_DUP << OP_HASH160 << std::vector(vcontract.begin() + 4, vcontract.begin() + 24) << OP_EQUALVERIFY << OP_CHECKSIG; + else + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_FORMAT); + + if (withdrawOutput.scriptPubKey != expectedWithdrawScriptPubKey) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT); + + //TODO: Check that we're spending from a valid, buried bitcoin block + } catch (std::exception& e) { + // Probably invalid encoding of something which was deserialized + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_FORMAT); + } + } + } // else...OP_NOP3 + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } @@ -1454,6 +1649,18 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C 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 push only. + if ((flags & SCRIPT_VERIFY_WITHDRAW) != 0) { + if (scriptPubKey.IsWithdrawLock()) { + if (!scriptSig.IsPushOnly()) + return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_FORMAT); + } else + flags &= ~SCRIPT_VERIFY_WITHDRAW; + } + vector > stack, stackCopy; if (!EvalScript(stack, scriptSig, flags, checker, SIGVERSION_BASE, serror)) // serror is set @@ -1541,7 +1748,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C // would be possible, which is not a softfork (and P2SH should be one). assert((flags & SCRIPT_VERIFY_P2SH) != 0); assert((flags & SCRIPT_VERIFY_WITNESS) != 0); - if (stack.size() != 1) { + if (stack.size() != 1 && (flags & SCRIPT_VERIFY_WITHDRAW) == 0) { return set_error(serror, SCRIPT_ERR_CLEANSTACK); } } diff --git a/src/script/interpreter.h b/src/script/interpreter.h index dcd5bdff6b..175354f98b 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -107,6 +107,10 @@ enum // Public keys in segregated witness scripts must be compressed // SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = (1U << 15), + + // Execute sidechain-related opcodes instead of treating them as NOPs + // + SCRIPT_VERIFY_WITHDRAW = (1U << 16), }; bool CheckSignatureEncoding(const std::vector &vchSig, unsigned int flags, ScriptError* serror); diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index c9d13c92a8..737af82ac5 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -87,6 +87,14 @@ const char* ScriptErrorString(const ScriptError serror) return "Witness provided for non-witness script"; case SCRIPT_ERR_WITNESS_PUBKEYTYPE: return "Using non-compressed keys in segwit"; + case SCRIPT_ERR_WITHDRAW_VERIFY_FORMAT: + return "Withdraw proof validation failed - invalid proof format"; + case SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK: + return "Withdraw proof validation failed - SPV proof/block coinbase invalid"; + case SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX: + return "Withdraw proof validation failed - locking transaction misformatted"; + case SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT: + return "Withdraw proof validation failed - output does not match expected"; 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 3200e94707..b20fc42941 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -64,6 +64,12 @@ typedef enum ScriptError_t SCRIPT_ERR_WITNESS_UNEXPECTED, SCRIPT_ERR_WITNESS_PUBKEYTYPE, + /* sidechains */ + SCRIPT_ERR_WITHDRAW_VERIFY_FORMAT, + SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK, + SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX, + SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT, + SCRIPT_ERR_ERROR_COUNT } ScriptError;