diff --git a/doc/block-reference.md b/doc/block-reference.md new file mode 100644 index 000000000000..871da1877e2d --- /dev/null +++ b/doc/block-reference.md @@ -0,0 +1,125 @@ +# Witness v2 Taproot with block references + +This document specifies the consensus rules for witness version 2 outputs +with a 32-byte program, as implemented in this prototype. Activation +parameters are out of scope; on regtest the rules are always active. + +## Motivation + +A transaction that is valid on both sides of a chain split can be replayed. +Holders who want to spend on one side only currently need a coin whose +history already differs between the two chains, such as a coinbase +descendant. A block reference lets any spender opt in to validity on one +chain, at the cost of a few witness bytes, with no coordination with anyone +else. + +## Rules + +A witness v2 output with a 32-byte program is spent under the +[BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) and +[BIP342](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki) +rules, with one addition. + +### Block reference annex + +If the spend carries an annex and the first byte after the `0x50` annex +marker is `0x01`, the annex is a *block reference*: + + 0x50 || 0x01 || height (4 bytes, little endian) || further bytes (ignored) + +An annex shorter than 6 bytes whose second byte is `0x01` makes the spend +invalid. Bytes after the height have no consensus meaning, exactly like the +rest of the annex today. Annexes whose second byte is not `0x01` keep their +BIP341 treatment: no meaning, always valid. + +The type byte `0x01` means "this input references the block at `height`". +This document attaches two effects to the reference; future rules may attach +more. + +### Maturity and activation + +A block containing the transaction at height `H` is invalid unless +`height + 100 <= H` for every block reference in the transaction. The +constant is `COINBASE_MATURITY`. As a consequence the referenced block is at +least 100 deep, and a transaction with a block reference is exactly as safe +against reorganisation as a spend of a matured coinbase output. + +A reference to a block below the activation height of these rules is +invalid. Before activation, witness v2 spends are anyone-can-spend and an +annex has no meaning, exactly as for any other undefined witness version; +neither rule in this document applies to blocks before activation. (The +mempool applies the witness v2 rules as policy regardless.) + +### Signature message + +Every signature checked for an input with a block reference, on the key +path or in tapscript, is over the BIP341 message with `ext_flag` bit 1 set +(so `spend_type` gains the value 4) and the 32-byte hash of the block at +`height` in the active chain appended after the message, after the BIP342 +extension if present. + +Since the hash of the referenced block is part of the message and not of +the transaction, a signature made for one chain does not verify on any chain +whose block at that height differs, nor on a chain that computes the plain +BIP341 message. An input without a block reference is signed exactly as a +witness v1 input and is valid on every chain. + +## Non-consensus considerations + +- The annex is committed to by every signature on the input, so a third + party cannot add, remove, or alter a block reference on a signed input. + A script-path spend that checks no signature can carry a reference, but + it then only imposes the maturity rule. +- A transaction with a block reference never expires. It becomes invalid + only if a reorganisation removes the referenced block, which requires a + reorganisation of at least 100 blocks. +- Wallets that want a spend to be specific to the current chain should + reference the most recent eligible block, that is the block at + `tip - 99`. +- Policy: a block reference annex of exactly 6 bytes is standard for v2 + spends. All other annexes remain non-standard. A transaction whose + reference is not yet mature is rejected from the mempool with a + retryable, non-punishable error, like a premature coinbase spend. + +## Wallet + +- `tr2(KEY, TREE)` is `tr()` with a witness v2 output. Addresses are + bech32m with witness version 2 (`bc1z...`). +- `send`, `sendall` and `walletcreatefundedpsbt` accept a + `block_reference` option. When set, every witness v2 input references + the block at `tip - 99`. +- A PSBT input carries the reference in field type `0x7f` (prototype, + not assigned by any BIP) as a 4-byte little endian height followed by + the 32-byte block hash. Signers use it to build the annex and the + message; it is kept after finalization so the final witness can be + verified. `decodepsbt` shows it as `block_reference`. +- For a nonzero height-based locktime between the reference height and the + wallet's tip, the wallet also includes headers from the referenced block + through the locktime height, inclusive. No headers are included for zero + or timestamp-based locktimes, or when the locktime height is before the + referenced block or beyond the tip. The chosen locktime is not changed. + Headers are shared by all inputs in global PSBT records of type `0x7f` + (prototype, not assigned by any BIP). + Each key is the type byte followed by a 4-byte little endian height; + its value is the serialized 80-byte header. `decodepsbt` exposes these + as `block_headers`, an object mapping heights to hex-encoded headers. + Combining and joining PSBTs reject conflicting headers at the same height. + A chain-aware combiner can resolve such conflicts before combining. + Combining, joining and finalizing PSBTs preserve these records. They are + not part of the final transaction. +- These optional headers let an offline signer inspect the chain extending + a referenced block and its proof of work. A verifier can derive each block + hash from its header and check continuity using the next header's previous + block hash. Their presence does not establish the claimed height, the + intended side of a fork, or that this is the most-work chain. Signers must + apply their own verification and trust policy; + this prototype continues to sign PSBTs without headers. An external signer + can use the transaction's height locktime as the endpoint without knowing + the wallet's tip. Reference maturity is still checked against the containing + block's height, not the locktime; this metadata does not change consensus. +- `signrawtransactionwithwallet` and `signrawtransactionwithkey` sign v2 + inputs without a reference. +- When a reorg replaces a block that an unconfirmed wallet transaction + references, the wallet abandons that transaction: its signatures can never + verify again, and its inputs become spendable. A reorg that puts the same + block back leaves the transaction alone. diff --git a/src/addresstype.cpp b/src/addresstype.cpp index 410879b40d60..e714852e5095 100644 --- a/src/addresstype.cpp +++ b/src/addresstype.cpp @@ -91,6 +91,10 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) addressRet = PayToAnchor(); return true; } + case TxoutType::WITNESS_V2_TAPROOT: { + addressRet = WitnessUnknown{2, vSolutions[0]}; + return true; + } case TxoutType::WITNESS_UNKNOWN: { addressRet = WitnessUnknown{vSolutions[0][0], vSolutions[1]}; return true; diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index b6d73b8679ac..99b10bb088fd 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -23,13 +23,16 @@ #include #include +#include #include +#include #include enum class ScriptType { P2WPKH, // segwitv0, witness-pubkey-hash (ECDSA signature) P2TR_KeyPath, // segwitv1, taproot key-path spend (Schnorr signature) P2TR_ScriptPath, // segwitv1, taproot script-path spend (Tapscript leaf with a single OP_CHECKSIG) + P2TR2_KeyPath_BlockRef, // segwitv2, taproot key-path spend with a block reference (annex, message extension) }; static size_t ExpectedWitnessStackSize(ScriptType script_type) @@ -38,6 +41,7 @@ static size_t ExpectedWitnessStackSize(ScriptType script_type) case ScriptType::P2WPKH: return 2; // [pubkey, signature] case ScriptType::P2TR_KeyPath: return 1; // [signature] case ScriptType::P2TR_ScriptPath: return 3; // [signature, tapscript, control block] + case ScriptType::P2TR2_KeyPath_BlockRef: return 2; // [signature, annex] } // no default case, so the compiler can warn about missing cases assert(false); } @@ -63,6 +67,7 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type) switch (script_type) { case ScriptType::P2WPKH: return WitnessV0KeyHash(pubkey); case ScriptType::P2TR_KeyPath: return WitnessV1Taproot(xonly_pubkey); + case ScriptType::P2TR2_KeyPath_BlockRef: return WitnessUnknown{2, ToByteVector(xonly_pubkey)}; case ScriptType::P2TR_ScriptPath: TaprootBuilder builder; builder.Add(0, CScript() << ToByteVector(xonly_pubkey) << OP_CHECKSIG, TAPROOT_LEAF_TAPSCRIPT); @@ -83,11 +88,15 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type) {txSpend.vin[0].prevout, Coin(txCredit.vout[0], /*nHeightIn=*/100, /*fCoinBaseIn=*/false)} }; std::map input_errors; - bool complete = SignTransaction(txSpend, &keystore, coins, {.sighash_type = SIGHASH_ALL}, input_errors); + const std::pair block_ref{100, uint256::ONE}; + SignOptions options{.sighash_type = SIGHASH_ALL}; + if (script_type == ScriptType::P2TR2_KeyPath_BlockRef) options.block_reference = block_ref; + bool complete = SignTransaction(txSpend, &keystore, coins, options, input_errors); assert(complete); // Weak sanity check on witness data to ensure we produced the intended spending type assert(txSpend.vin[0].scriptWitness.stack.size() == ExpectedWitnessStackSize(script_type)); txdata.Init(txSpend, /*spent_outputs=*/{txCredit.vout[0]}); + if (options.block_reference) txdata.m_block_hashes.push_back(block_ref); } // Benchmark. @@ -108,6 +117,7 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type) static void VerifyScriptP2WPKH(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2WPKH); } static void VerifyScriptP2TR_KeyPath(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR_KeyPath); } static void VerifyScriptP2TR_ScriptPath(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR_ScriptPath); } +static void VerifyScriptP2TR2_KeyPath_BlockRef(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR2_KeyPath_BlockRef); } static void VerifyNestedIfScript(benchmark::Bench& bench) { @@ -134,4 +144,5 @@ static void VerifyNestedIfScript(benchmark::Bench& bench) BENCHMARK(VerifyScriptP2WPKH); BENCHMARK(VerifyScriptP2TR_KeyPath); BENCHMARK(VerifyScriptP2TR_ScriptPath); +BENCHMARK(VerifyScriptP2TR2_KeyPath_BlockRef); BENCHMARK(VerifyNestedIfScript); diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index f62e455fac45..526614443d90 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -16,7 +16,7 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-chain=", "Use the chain (default: main). Allowed values: " LIST_CHAIN_NAMES, ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " "This is intended for regression testing tools and app development. Equivalent to -chain=regtest.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); - argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv). (test-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv, taproot_v2). (test-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-testnet", "Use the testnet3 chain. Equivalent to -chain=test. Support for testnet3 is deprecated and will be removed in an upcoming release. Consider moving to testnet4 now by using -testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-testnet4", "Use the testnet4 chain. Equivalent to -chain=testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-vbparams=deployment:start:end[:min_activation_height]", "Use given start/end times and min_activation_height for specified version bits deployment (test-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); diff --git a/src/consensus/params.h b/src/consensus/params.h index 99096e06d9a8..9f98e9276bf4 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -32,8 +32,9 @@ enum BuriedDeployment : int16_t { // SCRIPT_VERIFY_WITNESS is enforced from genesis, but the check for downloading // missing witness data is not. BIP 147 also relies on hardcoded activation height. DEPLOYMENT_SEGWIT, + DEPLOYMENT_TAPROOT_V2, }; -constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_SEGWIT; } +constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_TAPROOT_V2; } enum DeploymentPos : uint16_t { DEPLOYMENT_TESTDUMMY, @@ -108,6 +109,8 @@ struct Params { * Note that segwit v0 script rules are enforced on all blocks except the * BIP 16 exception blocks. */ int SegwitHeight; + /** Block height at which witness v2 Taproot (with block references) becomes active */ + int TaprootV2Height; /** Don't warn about unknown BIP 9 activations below this height. * This prevents us from warning about the CSV, segwit and taproot activations. */ int MinBIP9WarningHeight; @@ -153,6 +156,8 @@ struct Params { return CSVHeight; case DEPLOYMENT_SEGWIT: return SegwitHeight; + case DEPLOYMENT_TAPROOT_V2: + return TaprootV2Height; } // no default case, so the compiler can warn about missing cases return std::numeric_limits::max(); } diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index e580a9d2947b..7841c8d33641 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -18,6 +18,8 @@ #include #include +#include +#include #include bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) @@ -167,6 +169,24 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i return nSigOps; } +std::vector Consensus::GetBlockReferences(const CTransaction& tx, const CCoinsViewCache& inputs) +{ + std::vector heights; + for (const CTxIn& txin : tx.vin) { + const auto& stack{txin.scriptWitness.stack}; + if (stack.size() < 2 || stack.back().size() < BLOCK_REF_ANNEX_SIZE || stack.back()[0] != ANNEX_TAG) continue; + int witnessversion; + std::vector witnessprogram; + const CScript& spk{inputs.AccessCoin(txin.prevout).out.scriptPubKey}; + if (!spk.IsWitnessProgram(witnessversion, witnessprogram) || witnessversion != 2 || witnessprogram.size() != WITNESS_V2_TAPROOT_SIZE) continue; + std::optional height; + if (ParseBlockReference(stack.back(), height) && height) heights.push_back(*height); + } + std::ranges::sort(heights); + heights.erase(std::ranges::unique(heights).begin(), heights.end()); + return heights; +} + bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee) { // are the actual inputs available? diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index 76faeaeaea8e..505a196e0dc3 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -27,6 +27,14 @@ namespace Consensus { * Preconditions: tx.IsCoinBase() is false. */ [[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); + +/** + * Heights of the blocks referenced by the inputs of a transaction (block reference annexes of + * witness v2 spends, see doc/block-reference.md), sorted and without duplicates. Malformed + * references are skipped; script validation rejects them. + * Preconditions: all inputs are available in `inputs`. + */ +std::vector GetBlockReferences(const CTransaction& tx, const CCoinsViewCache& inputs); } // namespace Consensus /** Auxiliary functions for transaction validation (ideally should not be exposed) */ diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp index 551982b23ae7..8f88b9961b4a 100644 --- a/src/deploymentinfo.cpp +++ b/src/deploymentinfo.cpp @@ -29,6 +29,8 @@ std::string DeploymentName(Consensus::BuriedDeployment dep) return "csv"; case Consensus::DEPLOYMENT_SEGWIT: return "segwit"; + case Consensus::DEPLOYMENT_TAPROOT_V2: + return "taproot_v2"; } // no default case, so the compiler can warn about missing cases return ""; } @@ -45,6 +47,8 @@ std::optional GetBuriedDeployment(const std::string return Consensus::BuriedDeployment::DEPLOYMENT_CLTV; } else if (name == "csv") { return Consensus::BuriedDeployment::DEPLOYMENT_CSV; + } else if (name == "taproot_v2") { + return Consensus::BuriedDeployment::DEPLOYMENT_TAPROOT_V2; } return std::nullopt; } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 1ee433a02a67..2b3571c6dd36 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -25,6 +25,7 @@ #include class CBlock; +class CBlockHeader; class CFeeRate; class CRPCCommand; class CScheduler; @@ -52,6 +53,8 @@ class FoundBlock { public: FoundBlock& hash(uint256& hash) { m_hash = &hash; return *this; } + //! Return the header from the block index, including for pruned blocks. + FoundBlock& header(CBlockHeader& header) { m_header = &header; return *this; } FoundBlock& height(int& height) { m_height = &height; return *this; } FoundBlock& time(int64_t& time) { m_time = &time; return *this; } FoundBlock& maxTime(int64_t& max_time) { m_max_time = &max_time; return *this; } @@ -67,6 +70,7 @@ class FoundBlock FoundBlock& data(CBlock& data) { m_data = &data; return *this; } uint256* m_hash = nullptr; + CBlockHeader* m_header = nullptr; int* m_height = nullptr; int64_t* m_time = nullptr; int64_t* m_max_time = nullptr; diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 9764ba95c807..82b504c4d619 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,9 @@ void CChainParams::ApplyDeploymentOptions(const DeploymentOptions& opts) case Consensus::BuriedDeployment::DEPLOYMENT_CSV: consensus.CSVHeight = int{height}; break; + case Consensus::BuriedDeployment::DEPLOYMENT_TAPROOT_V2: + consensus.TaprootV2Height = int{height}; + break; } } @@ -122,6 +126,7 @@ class CMainParams : public CChainParams { consensus.BIP66Height = 363725; // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931 consensus.CSVHeight = 419328; // 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5 consensus.SegwitHeight = 481824; // 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893 + consensus.TaprootV2Height = std::numeric_limits::max(); // Not deployed consensus.MinBIP9WarningHeight = 711648; // taproot activation height + miner confirmation window consensus.powLimit = uint256{"00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}; consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -246,6 +251,7 @@ class CTestNetParams : public CChainParams { consensus.BIP66Height = 330776; // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 consensus.CSVHeight = 770112; // 00000000025e930139bac5c6c31a403776da130831ab85be56578f3fa75369bb consensus.SegwitHeight = 834624; // 00000000002b980fcd729daaa248fd9316a5200e9b367f4ff2c42453e84201ca + consensus.TaprootV2Height = std::numeric_limits::max(); // Not deployed consensus.MinBIP9WarningHeight = 2013984; // taproot activation height + miner confirmation window consensus.powLimit = uint256{"00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}; consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -346,6 +352,7 @@ class CTestNet4Params : public CChainParams { consensus.BIP66Height = 1; consensus.CSVHeight = 1; consensus.SegwitHeight = 1; + consensus.TaprootV2Height = std::numeric_limits::max(); // Not deployed consensus.MinBIP9WarningHeight = 0; consensus.powLimit = uint256{"00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}; consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -492,6 +499,7 @@ class SigNetParams : public CChainParams { consensus.BIP66Height = 1; consensus.CSVHeight = 1; consensus.SegwitHeight = 1; + consensus.TaprootV2Height = std::numeric_limits::max(); // Not deployed consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks consensus.nPowTargetSpacing = 10 * 60; consensus.fPowAllowMinDifficultyBlocks = false; @@ -575,6 +583,7 @@ class CRegTestParams : public CChainParams consensus.BIP66Height = 1; // Always active unless overridden consensus.CSVHeight = 1; // Always active unless overridden consensus.SegwitHeight = 0; // Always active unless overridden + consensus.TaprootV2Height = 0; // Always active unless overridden consensus.MinBIP9WarningHeight = 0; consensus.powLimit = uint256{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}; consensus.nPowTargetTimespan = 24 * 60 * 60; // one day diff --git a/src/kernel/mempool_entry.h b/src/kernel/mempool_entry.h index 29ca1fd0e280..be254cb4ec33 100644 --- a/src/kernel/mempool_entry.h +++ b/src/kernel/mempool_entry.h @@ -81,13 +81,17 @@ class CTxMemPoolEntry : public TxGraph::Ref const int64_t sigOpCost; //!< Total sigop cost mutable CAmount m_modified_fee; //!< Used for determining the priority of the transaction for mining in a block mutable LockPoints lockPoints; //!< Track the height and time at which tx was final + /** Blocks referenced by the inputs (see doc/block-reference.md), as resolved at acceptance. The tx is only + * valid while these are in the active chain and mature; a reorg that changes that must evict it. */ + const std::vector m_block_refs; public: virtual ~CTxMemPoolEntry() = default; CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, int64_t time, unsigned int entry_height, uint64_t entry_sequence, bool spends_coinbase, - int64_t sigops_cost, LockPoints lp) + int64_t sigops_cost, LockPoints lp, + std::vector block_refs = {}) : tx{tx}, nFee{fee}, nTxWeight{GetTransactionWeight(*tx)}, @@ -98,7 +102,8 @@ class CTxMemPoolEntry : public TxGraph::Ref spendsCoinbase{spends_coinbase}, sigOpCost{sigops_cost}, m_modified_fee{nFee}, - lockPoints{lp} {} + lockPoints{lp}, + m_block_refs{std::move(block_refs)} {} CTxMemPoolEntry& operator=(const CTxMemPoolEntry&) = delete; CTxMemPoolEntry(CTxMemPoolEntry&&) = default; @@ -120,6 +125,7 @@ class CTxMemPoolEntry : public TxGraph::Ref CAmount GetModifiedFee() const { return m_modified_fee; } size_t DynamicMemoryUsage() const { return nUsageSize; } const LockPoints& GetLockPoints() const { return lockPoints; } + const std::vector& GetBlockReferences() const { return m_block_refs; } // Updates the modified fees with descendants/ancestors. void UpdateModifiedFee(CAmount fee_diff) const diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index c1dd8e7a4dd7..3f708401a911 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -450,6 +450,7 @@ bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLockGetBlockHash(); + if (block.m_header) *block.m_header = index->GetBlockHeader(); if (block.m_height) *block.m_height = index->nHeight; if (block.m_time) *block.m_time = index->GetBlockTime(); if (block.m_max_time) *block.m_max_time = index->GetBlockTimeMax(); diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index b0988d868282..d561e98fccfd 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -31,8 +31,14 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) result.inputs.resize(psbtx.inputs.size()); - // PrecomputePSBTData calls GetUnsignedTx() which we checked already works - const PrecomputedTransactionData txdata = *PrecomputePSBTData(psbtx); + // PrecomputePSBTData calls GetUnsignedTx() which we checked already works, but it can still + // reject conflicting block references. + const std::optional txdata_opt{PrecomputePSBTData(psbtx)}; + if (!txdata_opt) { + result.SetInvalid("PSBT inputs reference conflicting block hashes for the same height"); + return result; + } + const PrecomputedTransactionData& txdata{*txdata_opt}; for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { PSBTInput& input = psbtx.inputs[i]; diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 83ceb63cbcd7..18c257106ac7 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -320,13 +320,15 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) // Check policy limits for Taproot spends: // - MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE limit for stack item size - // - No annexes - if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) { - // Taproot spend (non-P2SH-wrapped, version 1, witness program size 32; see BIP 341) + // - No annexes, except a witness v2 block reference + if ((witnessversion == 1 || witnessversion == 2) && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) { + // Taproot spend (non-P2SH-wrapped, version 1 or 2, witness program size 32; see BIP 341) std::span stack{tx.vin[i].scriptWitness.stack}; if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { - // Annexes are nonstandard as long as no semantics are defined for them. - return false; + // Annexes are nonstandard as long as no semantics are defined for them. The exception is a + // witness v2 block reference of exactly the defined size (see doc/block-reference.md). + if (witnessversion != 2 || stack.back().size() != BLOCK_REF_ANNEX_SIZE || stack.back()[1] != BLOCK_REF_ANNEX_TYPE) return false; + SpanPopBack(stack); } if (stack.size() >= 2) { // Script path spend (2 or more stack elements after removing optional annex) diff --git a/src/policy/policy.h b/src/policy/policy.h index ea66fa4b84e9..ca15916efef0 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -107,7 +107,8 @@ inline constexpr script_verify_flags MANDATORY_SCRIPT_VERIFY_FLAGS{SCRIPT_VERIFY SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | SCRIPT_VERIFY_WITNESS | - SCRIPT_VERIFY_TAPROOT}; + SCRIPT_VERIFY_TAPROOT | + SCRIPT_VERIFY_TAPROOT_V2}; /** * Standard script verification flags that standard transactions will comply diff --git a/src/psbt.cpp b/src/psbt.cpp index 51fb19591a56..1d64c3831072 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -45,6 +45,7 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt) if (GetVersion() != psbt.GetVersion()) { return false; } + if (!MergeBlockHeaders(psbt)) return false; for (unsigned int i = 0; i < inputs.size(); ++i) { inputs[i].Merge(psbt.inputs[i]); @@ -83,6 +84,16 @@ void PartiallySignedTransaction::MergeGlobalXPubs(const PartiallySignedTransacti } } +bool PartiallySignedTransaction::MergeBlockHeaders(const PartiallySignedTransaction& psbt) +{ + for (const auto& [height, header] : psbt.m_block_headers) { + const auto it{m_block_headers.find(height)}; + if (it != m_block_headers.end() && it->second.GetHash() != header.GetHash()) return false; + } + m_block_headers.insert(psbt.m_block_headers.begin(), psbt.m_block_headers.end()); + return true; +} + std::optional PartiallySignedTransaction::ComputeTimeLock() const { if (GetVersion() >= 2) { @@ -461,6 +472,7 @@ void PSBTInput::Merge(const PSBTInput& input) m_musig2_partial_sigs[agg_key_lh].insert(psigs.begin(), psigs.end()); } if (sighash_type == std::nullopt && input.sighash_type != std::nullopt) sighash_type = input.sighash_type; + if (m_block_reference == std::nullopt && input.m_block_reference != std::nullopt) m_block_reference = input.m_block_reference; if (sequence == std::nullopt && input.sequence != std::nullopt) sequence = input.sequence; if (time_locktime == std::nullopt && input.time_locktime != std::nullopt) time_locktime = input.time_locktime; if (height_locktime == std::nullopt && input.height_locktime != std::nullopt) height_locktime = input.height_locktime; @@ -637,6 +649,14 @@ std::optional PrecomputePSBTData(const PartiallySign } else { txdata.Init(tx, {}, true); } + // Block references: one hash per height; inputs claiming different hashes for one height conflict. + std::map block_hashes; + for (const PSBTInput& input : psbt.inputs) { + if (!input.m_block_reference) continue; + const auto [it, inserted] = block_hashes.insert(*input.m_block_reference); + if (!inserted && it->second != input.m_block_reference->second) return std::nullopt; + } + txdata.m_block_hashes.assign(block_hashes.begin(), block_hashes.end()); return txdata; } @@ -726,7 +746,7 @@ util::Expected SignPSBTInput(const SigningProvider& provider, P if (txdata == nullptr) { sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); } else { - MutableTransactionSignatureCreator creator(tx, index, utxo.nValue, txdata, {.sighash_type = sighash}); + MutableTransactionSignatureCreator creator(tx, index, utxo.nValue, txdata, {.sighash_type = sighash, .block_reference = input.m_block_reference}); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); } // Verify that a witness signature was produced in case one was required. diff --git a/src/psbt.h b/src/psbt.h index 467dffb0ca93..b27411295f53 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include