/Users/eugenesiegel/btc/bitcoin/src/signet.cpp
| Line | Count | Source (jump to first uncovered line) | 
| 1 |  | // Copyright (c) 2019-present 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 <common/system.h> | 
| 8 |  | #include <consensus/merkle.h> | 
| 9 |  | #include <consensus/params.h> | 
| 10 |  | #include <consensus/validation.h> | 
| 11 |  | #include <core_io.h> | 
| 12 |  | #include <hash.h> | 
| 13 |  | #include <logging.h> | 
| 14 |  | #include <primitives/block.h> | 
| 15 |  | #include <primitives/transaction.h> | 
| 16 |  | #include <script/interpreter.h> | 
| 17 |  | #include <span.h> | 
| 18 |  | #include <streams.h> | 
| 19 |  | #include <uint256.h> | 
| 20 |  | #include <util/strencodings.h> | 
| 21 |  |  | 
| 22 |  | #include <algorithm> | 
| 23 |  | #include <array> | 
| 24 |  | #include <cstdint> | 
| 25 |  | #include <vector> | 
| 26 |  |  | 
| 27 |  | static constexpr uint8_t SIGNET_HEADER[4] = {0xec, 0xc7, 0xda, 0xa2}; | 
| 28 |  |  | 
| 29 |  | static constexpr unsigned int BLOCK_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_NULLDUMMY; | 
| 30 |  |  | 
| 31 |  | static bool FetchAndClearCommitmentSection(const std::span<const uint8_t> header, CScript& witness_commitment, std::vector<uint8_t>& result) | 
| 32 | 0 | { | 
| 33 | 0 |     CScript replacement; | 
| 34 | 0 |     bool found_header = false; | 
| 35 | 0 |     result.clear(); | 
| 36 |  | 
 | 
| 37 | 0 |     opcodetype opcode; | 
| 38 | 0 |     CScript::const_iterator pc = witness_commitment.begin(); | 
| 39 | 0 |     std::vector<uint8_t> pushdata; | 
| 40 | 0 |     while (witness_commitment.GetOp(pc, opcode, pushdata)) { | 
| 41 | 0 |         if (pushdata.size() > 0) { | 
| 42 | 0 |             if (!found_header && pushdata.size() > header.size() && std::ranges::equal(std::span{pushdata}.first(header.size()), header)) { | 
| 43 |  |                 // pushdata only counts if it has the header _and_ some data | 
| 44 | 0 |                 result.insert(result.end(), pushdata.begin() + header.size(), pushdata.end()); | 
| 45 | 0 |                 pushdata.erase(pushdata.begin() + header.size(), pushdata.end()); | 
| 46 | 0 |                 found_header = true; | 
| 47 | 0 |             } | 
| 48 | 0 |             replacement << pushdata; | 
| 49 | 0 |         } else { | 
| 50 | 0 |             replacement << opcode; | 
| 51 | 0 |         } | 
| 52 | 0 |     } | 
| 53 |  | 
 | 
| 54 | 0 |     if (found_header) witness_commitment = replacement; | 
| 55 | 0 |     return found_header; | 
| 56 | 0 | } | 
| 57 |  |  | 
| 58 |  | static uint256 ComputeModifiedMerkleRoot(const CMutableTransaction& cb, const CBlock& block) | 
| 59 | 0 | { | 
| 60 | 0 |     std::vector<uint256> leaves; | 
| 61 | 0 |     leaves.resize(block.vtx.size()); | 
| 62 | 0 |     leaves[0] = cb.GetHash().ToUint256(); | 
| 63 | 0 |     for (size_t s = 1; s < block.vtx.size(); ++s) { | 
| 64 | 0 |         leaves[s] = block.vtx[s]->GetHash().ToUint256(); | 
| 65 | 0 |     } | 
| 66 | 0 |     return ComputeMerkleRoot(std::move(leaves)); | 
| 67 | 0 | } | 
| 68 |  |  | 
| 69 |  | std::optional<SignetTxs> SignetTxs::Create(const CBlock& block, const CScript& challenge) | 
| 70 | 0 | { | 
| 71 | 0 |     CMutableTransaction tx_to_spend; | 
| 72 | 0 |     tx_to_spend.version = 0; | 
| 73 | 0 |     tx_to_spend.nLockTime = 0; | 
| 74 | 0 |     tx_to_spend.vin.emplace_back(COutPoint(), CScript(OP_0), 0); | 
| 75 | 0 |     tx_to_spend.vout.emplace_back(0, challenge); | 
| 76 |  | 
 | 
| 77 | 0 |     CMutableTransaction tx_spending; | 
| 78 | 0 |     tx_spending.version = 0; | 
| 79 | 0 |     tx_spending.nLockTime = 0; | 
| 80 | 0 |     tx_spending.vin.emplace_back(COutPoint(), CScript(), 0); | 
| 81 | 0 |     tx_spending.vout.emplace_back(0, CScript(OP_RETURN)); | 
| 82 |  |  | 
| 83 |  |     // can't fill any other fields before extracting signet | 
| 84 |  |     // responses from block coinbase tx | 
| 85 |  |  | 
| 86 |  |     // find and delete signet signature | 
| 87 | 0 |     if (block.vtx.empty()) return std::nullopt; // no coinbase tx in block; invalid | 
| 88 | 0 |     CMutableTransaction modified_cb(*block.vtx.at(0)); | 
| 89 |  | 
 | 
| 90 | 0 |     const int cidx = GetWitnessCommitmentIndex(block); | 
| 91 | 0 |     if (cidx == NO_WITNESS_COMMITMENT) { | 
| 92 | 0 |         return std::nullopt; // require a witness commitment | 
| 93 | 0 |     } | 
| 94 |  |  | 
| 95 | 0 |     CScript& witness_commitment = modified_cb.vout.at(cidx).scriptPubKey; | 
| 96 |  | 
 | 
| 97 | 0 |     std::vector<uint8_t> signet_solution; | 
| 98 | 0 |     if (!FetchAndClearCommitmentSection(SIGNET_HEADER, witness_commitment, signet_solution)) { | 
| 99 |  |         // no signet solution -- allow this to support OP_TRUE as trivial block challenge | 
| 100 | 0 |     } else { | 
| 101 | 0 |         try { | 
| 102 | 0 |             SpanReader v{signet_solution}; | 
| 103 | 0 |             v >> tx_spending.vin[0].scriptSig; | 
| 104 | 0 |             v >> tx_spending.vin[0].scriptWitness.stack; | 
| 105 | 0 |             if (!v.empty()) return std::nullopt; // extraneous data encountered | 
| 106 | 0 |         } catch (const std::exception&) { | 
| 107 | 0 |             return std::nullopt; // parsing error | 
| 108 | 0 |         } | 
| 109 | 0 |     } | 
| 110 | 0 |     uint256 signet_merkle = ComputeModifiedMerkleRoot(modified_cb, block); | 
| 111 |  | 
 | 
| 112 | 0 |     std::vector<uint8_t> block_data; | 
| 113 | 0 |     VectorWriter writer{block_data, 0}; | 
| 114 | 0 |     writer << block.nVersion; | 
| 115 | 0 |     writer << block.hashPrevBlock; | 
| 116 | 0 |     writer << signet_merkle; | 
| 117 | 0 |     writer << block.nTime; | 
| 118 | 0 |     tx_to_spend.vin[0].scriptSig << block_data; | 
| 119 | 0 |     tx_spending.vin[0].prevout = COutPoint(tx_to_spend.GetHash(), 0); | 
| 120 |  | 
 | 
| 121 | 0 |     return SignetTxs{tx_to_spend, tx_spending}; | 
| 122 | 0 | } | 
| 123 |  |  | 
| 124 |  | // Signet block solution checker | 
| 125 |  | bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& consensusParams) | 
| 126 | 0 | { | 
| 127 | 0 |     if (block.GetHash() == consensusParams.hashGenesisBlock) { | 
| 128 |  |         // genesis block solution is always valid | 
| 129 | 0 |         return true; | 
| 130 | 0 |     } | 
| 131 |  |  | 
| 132 | 0 |     const CScript challenge(consensusParams.signet_challenge.begin(), consensusParams.signet_challenge.end()); | 
| 133 | 0 |     const std::optional<SignetTxs> signet_txs = SignetTxs::Create(block, challenge); | 
| 134 |  | 
 | 
| 135 | 0 |     if (!signet_txs) { | 
| 136 | 0 |         LogDebug(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution parse failure)\n"); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 137 | 0 |         return false; | 
| 138 | 0 |     } | 
| 139 |  |  | 
| 140 | 0 |     const CScript& scriptSig = signet_txs->m_to_sign.vin[0].scriptSig; | 
| 141 | 0 |     const CScriptWitness& witness = signet_txs->m_to_sign.vin[0].scriptWitness; | 
| 142 |  | 
 | 
| 143 | 0 |     PrecomputedTransactionData txdata; | 
| 144 | 0 |     txdata.Init(signet_txs->m_to_sign, {signet_txs->m_to_spend.vout[0]}); | 
| 145 | 0 |     TransactionSignatureChecker sigcheck(&signet_txs->m_to_sign, /* nInIn= */ 0, /* amountIn= */ signet_txs->m_to_spend.vout[0].nValue, txdata, MissingDataBehavior::ASSERT_FAIL); | 
| 146 |  | 
 | 
| 147 | 0 |     if (!VerifyScript(scriptSig, signet_txs->m_to_spend.vout[0].scriptPubKey, &witness, BLOCK_SCRIPT_VERIFY_FLAGS, sigcheck)) { | 
| 148 | 0 |         LogDebug(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution invalid)\n"); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 149 | 0 |         return false; | 
| 150 | 0 |     } | 
| 151 | 0 |     return true; | 
| 152 | 0 | } |