|
| 1 | +// Copyright (c) 2019-2020 The Bitcoin Core developers |
| 2 | +// Distributed under the MIT software license, see the accompanying |
| 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 4 | + |
| 5 | +#include <signet.h> |
| 6 | + |
| 7 | +#include <array> |
| 8 | +#include <cstdint> |
| 9 | +#include <vector> |
| 10 | + |
| 11 | +#include <consensus/merkle.h> |
| 12 | +#include <consensus/params.h> |
| 13 | +#include <consensus/validation.h> |
| 14 | +#include <core_io.h> |
| 15 | +#include <hash.h> |
| 16 | +#include <primitives/block.h> |
| 17 | +#include <primitives/transaction.h> |
| 18 | +#include <span.h> |
| 19 | +#include <script/interpreter.h> |
| 20 | +#include <script/standard.h> |
| 21 | +#include <streams.h> |
| 22 | +#include <util/strencodings.h> |
| 23 | +#include <util/system.h> |
| 24 | +#include <uint256.h> |
| 25 | + |
| 26 | +static constexpr uint8_t SIGNET_HEADER[4] = {0xec, 0xc7, 0xda, 0xa2}; |
| 27 | + |
| 28 | +static constexpr unsigned int BLOCK_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_NULLDUMMY; |
| 29 | + |
| 30 | +static bool FetchAndClearCommitmentSection(const Span<const uint8_t> header, CScript& witness_commitment, std::vector<uint8_t>& result) |
| 31 | +{ |
| 32 | + CScript replacement; |
| 33 | + bool found_header = false; |
| 34 | + result.clear(); |
| 35 | + |
| 36 | + opcodetype opcode; |
| 37 | + CScript::const_iterator pc = witness_commitment.begin(); |
| 38 | + std::vector<uint8_t> pushdata; |
| 39 | + while (witness_commitment.GetOp(pc, opcode, pushdata)) { |
| 40 | + if (pushdata.size() > 0) { |
| 41 | + if (!found_header && pushdata.size() > (size_t) header.size() && Span<const uint8_t>(pushdata.data(), header.size()) == header) { |
| 42 | + // pushdata only counts if it has the header _and_ some data |
| 43 | + result.insert(result.end(), pushdata.begin() + header.size(), pushdata.end()); |
| 44 | + pushdata.erase(pushdata.begin() + header.size(), pushdata.end()); |
| 45 | + found_header = true; |
| 46 | + } |
| 47 | + replacement << pushdata; |
| 48 | + } else { |
| 49 | + replacement << opcode; |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + if (found_header) witness_commitment = replacement; |
| 54 | + return found_header; |
| 55 | +} |
| 56 | + |
| 57 | +static uint256 ComputeModifiedMerkleRoot(const CMutableTransaction& cb, const CBlock& block) |
| 58 | +{ |
| 59 | + std::vector<uint256> leaves; |
| 60 | + leaves.resize(block.vtx.size()); |
| 61 | + leaves[0] = cb.GetHash(); |
| 62 | + for (size_t s = 1; s < block.vtx.size(); ++s) { |
| 63 | + leaves[s] = block.vtx[s]->GetHash(); |
| 64 | + } |
| 65 | + return ComputeMerkleRoot(std::move(leaves)); |
| 66 | +} |
| 67 | + |
| 68 | +SignetTxs SignetTxs::Create(const CBlock& block, const CScript& challenge) |
| 69 | +{ |
| 70 | + CMutableTransaction tx_to_spend; |
| 71 | + tx_to_spend.nVersion = 0; |
| 72 | + tx_to_spend.nLockTime = 0; |
| 73 | + tx_to_spend.vin.emplace_back(COutPoint(), CScript(OP_0), 0); |
| 74 | + tx_to_spend.vout.emplace_back(0, challenge); |
| 75 | + |
| 76 | + CMutableTransaction tx_spending; |
| 77 | + tx_spending.nVersion = 0; |
| 78 | + tx_spending.nLockTime = 0; |
| 79 | + tx_spending.vin.emplace_back(COutPoint(), CScript(), 0); |
| 80 | + tx_spending.vout.emplace_back(0, CScript(OP_RETURN)); |
| 81 | + |
| 82 | + // can't fill any other fields before extracting signet |
| 83 | + // responses from block coinbase tx |
| 84 | + |
| 85 | + // find and delete signet signature |
| 86 | + if (block.vtx.empty()) return invalid(); // no coinbase tx in block; invalid |
| 87 | + CMutableTransaction modified_cb(*block.vtx.at(0)); |
| 88 | + |
| 89 | + const int cidx = GetWitnessCommitmentIndex(block); |
| 90 | + if (cidx == NO_WITNESS_COMMITMENT) { |
| 91 | + return invalid(); // require a witness commitment |
| 92 | + } |
| 93 | + |
| 94 | + CScript& witness_commitment = modified_cb.vout.at(cidx).scriptPubKey; |
| 95 | + |
| 96 | + std::vector<uint8_t> signet_solution; |
| 97 | + if (!FetchAndClearCommitmentSection(SIGNET_HEADER, witness_commitment, signet_solution)) { |
| 98 | + // no signet solution -- allow this to support OP_TRUE as trivial block challenge |
| 99 | + } else { |
| 100 | + try { |
| 101 | + VectorReader v(SER_NETWORK, INIT_PROTO_VERSION, signet_solution, 0); |
| 102 | + v >> tx_spending.vin[0].scriptSig; |
| 103 | + v >> tx_spending.vin[0].scriptWitness.stack; |
| 104 | + if (!v.empty()) return invalid(); // extraneous data encountered |
| 105 | + } catch (const std::exception&) { |
| 106 | + return invalid(); // parsing error |
| 107 | + } |
| 108 | + } |
| 109 | + uint256 signet_merkle = ComputeModifiedMerkleRoot(modified_cb, block); |
| 110 | + |
| 111 | + std::vector<uint8_t> block_data; |
| 112 | + CVectorWriter writer(SER_NETWORK, INIT_PROTO_VERSION, block_data, 0); |
| 113 | + writer << block.nVersion; |
| 114 | + writer << block.hashPrevBlock; |
| 115 | + writer << signet_merkle; |
| 116 | + writer << block.nTime; |
| 117 | + tx_to_spend.vin[0].scriptSig << block_data; |
| 118 | + tx_spending.vin[0].prevout = COutPoint(tx_to_spend.GetHash(), 0); |
| 119 | + |
| 120 | + return {tx_to_spend, tx_spending}; |
| 121 | +} |
| 122 | + |
| 123 | +// Signet block solution checker |
| 124 | +bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& consensusParams) |
| 125 | +{ |
| 126 | + if (block.GetHash() == consensusParams.hashGenesisBlock) { |
| 127 | + // genesis block solution is always valid |
| 128 | + return true; |
| 129 | + } |
| 130 | + |
| 131 | + const CScript challenge(consensusParams.signet_challenge.begin(), consensusParams.signet_challenge.end()); |
| 132 | + const SignetTxs signet_txs(block, challenge); |
| 133 | + |
| 134 | + if (!signet_txs.m_valid) { |
| 135 | + LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution parse failure)\n"); |
| 136 | + return false; |
| 137 | + } |
| 138 | + |
| 139 | + const CScript& scriptSig = signet_txs.m_to_sign.vin[0].scriptSig; |
| 140 | + const CScriptWitness& witness = signet_txs.m_to_sign.vin[0].scriptWitness; |
| 141 | + |
| 142 | + TransactionSignatureChecker sigcheck(&signet_txs.m_to_sign, /*nIn=*/ 0, /*amount=*/ signet_txs.m_to_spend.vout[0].nValue); |
| 143 | + |
| 144 | + if (!VerifyScript(scriptSig, signet_txs.m_to_spend.vout[0].scriptPubKey, &witness, BLOCK_SCRIPT_VERIFY_FLAGS, sigcheck)) { |
| 145 | + LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution invalid)\n"); |
| 146 | + return false; |
| 147 | + } |
| 148 | + return true; |
| 149 | +} |
0 commit comments