diff --git a/src/Makefile.am b/src/Makefile.am index 6e9f51402c..473f397b11 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -239,6 +239,8 @@ libbitcoin_wallet_a_SOURCES = \ wallet/wallet.cpp \ wallet/walletdb.cpp \ policy/rbf.cpp \ + primitives/bitcoin/merkleblock.cpp \ + primitives/bitcoin/block.cpp \ $(BITCOIN_CORE_H) # crypto primitives library diff --git a/src/script/script.h b/src/script/script.h index ca3343016d..da376c3ae5 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -325,6 +325,12 @@ public: return m_value; } + // Only used for peg-in witness values + int64_t getint64() const + { + return m_value; + } + std::vector getvch() const { return serialize(m_value); diff --git a/src/validation.cpp b/src/validation.cpp index 4dbc42e6d9..e567579bf5 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -22,6 +22,9 @@ #include "pow.h" #include "primitives/block.h" #include "primitives/transaction.h" +#include "primitives/bitcoin/block.h" +#include "primitives/bitcoin/transaction.h" +#include "primitives/bitcoin/merkleblock.h" #include "random.h" #include "script/script.h" #include "script/sigcache.h" @@ -2204,6 +2207,173 @@ bool BitcoindRPCCheck(const bool init) return true; } +bool IsValidPeginWitness(const CScriptWitness& pegin_witness) { + + // Format on stack is as follows: + // 1) txid - txid of parent chain transaction - TODO-PEGIN unneeded? + // 2) nout - output number that is a peg-in output - unneeded? + // 3) value - the value of the pegin output + // 4) asset type - the asset type being pegged in + // 5) genesis blockhash - genesis block of the parent chain + // 6) witness program - program that script evaluation will be evaluated against + // 7) serialized transaction - serialized bitcoin transaction + // 8) txout proof - merkle proof connecting transaction to header + // + // First 6 values are enough to validate a peg-in without any internal knowledge + // of Bitcoin serialization. This is useful for further abstraction by outsourcing + // the other validity checks to RPC calls. The last two stack items + + const std::vector >& stack = pegin_witness.stack; + // Must include all elements + if (stack.size() != 8) { + return false; + } + + // Txid must be included + if (stack[0].size() != 32) { + return false; + } + uint256 txid(stack[0]); + + // Get output number + int nOut = CScriptNum(stack[1], true).getint(); + + // Get output value. Special 8 byte length to capture possible bitcoin values + CAmount value = CScriptNum(stack[2], true, 8).getint64(); + + // Get asset type + if (stack[3].size() != 32) { + return false; + } + CAsset asset(stack[3]); + + // Get genesis blockhash + if (stack[4].size() != 32) { + return false; + } + uint256 gen_hash(stack[4]); + + // Get witness program + CScript witness_program(stack[5].begin(), stack[5].end()); + int version = -1; + std::vector witnessProgram; + if (!witness_program.IsWitnessProgram(version, witnessProgram)) { + return false; + } + + // Get serialized transaction + Sidechain::Bitcoin::CTransactionRef pegtx; + try { + CDataStream pegtx_stream(stack[6], SER_NETWORK, PROTOCOL_VERSION); + pegtx_stream >> pegtx; + if (!pegtx_stream.empty()) { + return false; + } + } catch (std::exception& e) { + // Invalid encoding of transaction + return false; + } + + // Get txout proof + Sidechain::Bitcoin::CMerkleBlock merkle_block; + std::vector txHashes; + std::vector txIndices; + + try { + CDataStream merkleBlockStream(stack[7], SER_NETWORK, PROTOCOL_VERSION); + merkleBlockStream >> merkle_block; + if (!merkleBlockStream.empty() || !CheckBitcoinProof(merkle_block.header.GetHash(), merkle_block.header.nBits)) { + return false; + } + if (merkle_block.txn.ExtractMatches(txHashes, txIndices) != merkle_block.header.hashMerkleRoot || txHashes.size() != 1) { + return false; + } + } catch (std::exception& e) { + // Invalid encoding of merkle block + return false; + } + + // Check that transaction matches txid + if (pegtx->GetHash() != txid) { + return false; + } + + // Check that the merkle proof corresponds to the txid + if (txid != txHashes[0]) { + return false; + } + + // Check the genesis block corresponds to a valid peg (only one for now) + if (gen_hash != Params().ParentGenesisBlockHash()) { + return false; + } + + // Check the asset type corresponds to a valid pegged asset (only one for now) + if (asset != Params().GetConsensus().pegged_asset) { + return false; + } + + // Check the transaction nout/value matches + if (nOut < 0 || (unsigned int)nOut >= pegtx->vout.size() || value != pegtx->vout[nOut].nValue) { + return false; + } + + // Check that the witness program matches the p2ch on the transaction output + opcodetype opcodeTmp; + unsigned char tweak[32]; + CSHA256().Write(witness_program.data(), witness_program.size()).Finalize(tweak); + CScript fedpegscript = Params().GetConsensus().fedpegScript; + // fedpegscript should be multisig or OP_TRUE + txnouttype type; + std::vector > solutions; + if (!Solver(fedpegscript, type, solutions) || (type != TX_MULTISIG && type != TX_TRUE)) { + assert(false); + } + { + CScript::iterator sdpc = fedpegscript.begin(); + std::vector vch; + while (fedpegscript.GetOp(sdpc, opcodeTmp, vch)) + { + if (vch.size() == 33) + { + size_t pub_len = 33; + int ret; + unsigned char *pub_start = &(*(sdpc - pub_len)); + secp256k1_pubkey pubkey; + ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_verify_amounts, &pubkey, pub_start, pub_len); + assert(ret == 1); + // If someone creates a tweak that makes this fail, they broke SHA256 + ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx_verify_amounts, &pubkey, tweak); + assert(ret == 1); + ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx_verify_amounts, pub_start, &pub_len, &pubkey, SECP256K1_EC_COMPRESSED); + assert(ret == 1); + assert(pub_len == 33); + } + } + } + + // Check against expected p2sh output + CScriptID expectedP2SH(fedpegscript); + CScript expected_script(CScript() << OP_HASH160 << ToByteVector(expectedP2SH) << OP_EQUAL); + if (pegtx->vout[nOut].scriptPubKey != expected_script) { + return false; + } + + // Finally, validate peg-in via rpc call + if (GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) { + // TODO-PEGIN return useful error message + return IsConfirmedBitcoinBlock(merkle_block.header.GetHash(), GetArg("-peginconfirmationdepth", DEFAULT_PEGIN_CONFIRMATION_DEPTH)); + } + return true; +} + +// Constructs unblinded output to be used in amount and scriptpubkey checks during pegin +CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness) { + // Must check validity first for formatting reasons + assert(IsValidPeginWitness(pegin_witness)); + return CTxOut(CAsset(pegin_witness.stack[3]), CScriptNum(pegin_witness.stack[2], true, 8).getint64(), CScript(pegin_witness.stack[5].begin(), pegin_witness.stack[5].end())); +} + // Protected by cs_main VersionBitsCache versionbitscache; diff --git a/src/validation.h b/src/validation.h index 492f3efa60..b6afa688e6 100644 --- a/src/validation.h +++ b/src/validation.h @@ -262,6 +262,10 @@ void UnloadBlockIndex(); void ThreadScriptCheck(); /** Check if bitcoind connection via RPC is correctly working*/ bool BitcoindRPCCheck(bool init); +/** Checks pegin witness for validity */ +bool IsValidPeginWitness(const CScriptWitness& pegin_witness); +/** Extracts an output from pegin witness for evaluation as a normal output */ +CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness); /** Check whether we are doing an initial block download (synchronizing from disk or network) */ bool IsInitialBlockDownload(); /** Format a string that describes several potential problems detected by the core.