Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,12 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
}
}

// Check policy limits for P2MR spends:
// Check policy limits for native P2MR spends:
// - MAX_STANDARD_P2MR_STACK_ITEM_SIZE limit for stack item size
// - Script path only (no key path spending)
// - No annexes
if (witnessversion == 2 && witnessprogram.size() == WITNESS_V2_P2MR_SIZE) {
// P2MR spend (non-P2SH-wrapped, version 3, witness program size 32)
if (witnessversion == 2 && witnessprogram.size() == WITNESS_V2_P2MR_SIZE && !p2sh) {
// P2MR spend (native witness v2, witness program size 32)
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.
Expand All @@ -359,13 +359,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
// Leaf version 0xc0 (aka Tapscript, see BIP 342)
for (const auto& item : stack) {
// Allow larger items for SLH-DSA signatures (OP_SUCCESS127)
if (item.size() > MAX_STANDARD_P2MR_STACK_ITEM_SIZE) {
// Check if this is an SLH-DSA signature by looking at the script
// You'd need to parse the script to see if it contains OP_SUCCESS127
// For now, we could allow larger items when OP_SUCCESS127 is present
return false; // Keep existing behavior until SLH-DSA is implemented
}
if (item.size() > MAX_STANDARD_P2MR_STACK_ITEM_SIZE) return false;
}
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ static constexpr unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS{100};
static constexpr unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE{80};
/** The maximum size in bytes of each witness stack item in a standard BIP 342 script (Taproot, leaf version 0xc0) */
static constexpr unsigned int MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE{80};
/** The maximum size in bytes of each witness stack item in a standard P2MR script */
static constexpr unsigned int MAX_STANDARD_P2MR_STACK_ITEM_SIZE{8000};
/** The maximum size in bytes of each witness stack item in a standard BIP 360 script */
static constexpr unsigned int MAX_STANDARD_P2MR_STACK_ITEM_SIZE{80};
/** The maximum size in bytes of a standard witnessScript */
static constexpr unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE{3600};
/** The maximum size of a standard ScriptSig */
Expand Down
2 changes: 1 addition & 1 deletion src/script/descriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1631,7 +1631,7 @@ class P2MRDescriptor final : public DescriptorImpl
uint256 merkle_root = builder.GetSpendData().merkle_root;

CScript output_script;
output_script << OP_3 << ToByteVector(merkle_root);
output_script << OP_2 << ToByteVector(merkle_root);

return {output_script};
}
Expand Down
389 changes: 8 additions & 381 deletions src/script/interpreter.cpp

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
static constexpr size_t TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
static constexpr size_t P2MR_CONTROL_BASE_SIZE = 1; // no tweaked pubkey
static constexpr size_t P2MR_CONTROL_MAX_SIZE = TAPROOT_CONTROL_MAX_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
static constexpr size_t P2MR_CONTROL_MAX_SIZE = P2MR_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;

extern const HashWriter HASHER_TAPSIGHASH; //!< Hasher with tag "TapSighash" pre-fed to it.
extern const HashWriter HASHER_TAPLEAF; //!< Hasher with tag "TapLeaf" pre-fed to it.
Expand Down Expand Up @@ -296,10 +296,6 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker
unsigned int nIn;
const CAmount amount;
const PrecomputedTransactionData* txdata;

// Friend function for SLH-DSA signature verification
template<typename U>
friend bool HandleSLHDSASignature(std::vector<std::vector<unsigned char>>& stack, const CScript& exec_script, unsigned int flags, const GenericTransactionSignatureChecker<U>& checker, ScriptExecutionData& execdata, ScriptError* serror);

protected:
virtual bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
Expand Down
3 changes: 0 additions & 3 deletions src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@
// Maximum number of bytes pushable to the stack
static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520;

// Add this line after the existing MAX_SCRIPT_ELEMENT_SIZE definition
static const unsigned int MAX_SCRIPT_ELEMENT_SIZE_SLHDSA = 8000;

// Maximum number of non-push operations per script
static const int MAX_OPS_PER_SCRIPT = 201;

Expand Down
2 changes: 1 addition & 1 deletion src/script/script_error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ std::string ScriptErrorString(const ScriptError serror)
case SCRIPT_ERR_P2MR_WRONG_CONTROL_SIZE:
return "Invalid P2MR control block size";
case SCRIPT_ERR_P2MR_WRONG_PARITY_BIT:
return "P2MR leaf must use parity bit 1 (0xc1)";
return "P2MR control byte must use parity bit 1";
case SCRIPT_ERR_UNKNOWN_ERROR:
case SCRIPT_ERR_ERROR_COUNT:
default: break;
Expand Down
6 changes: 3 additions & 3 deletions src/test/data/script_tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -2694,8 +2694,8 @@
"",
"0x52 0x20 0x7e04991706edb31549f786fa7113bb857ceb26174f7d08e9efa91decf13324a0",
"P2SH,WITNESS,TAPROOT,P2MR",
"EVAL_FALSE",
"P2MR: Simple negative OP_EQUAL test post-activation"
"OK",
"P2MR: Depth-zero OP_EQUAL succeeds without executing script"
],
[
[
Expand Down Expand Up @@ -2725,7 +2725,7 @@
[
[
"aa",
"aa",
"ab",
0.00000001
],
"",
Expand Down
20 changes: 20 additions & 0 deletions src/test/descriptor_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <addresstype.h>
#include <key_io.h>
#include <pubkey.h>
#include <script/descriptor.h>
#include <script/sign.h>
#include <script/solver.h>
#include <test/util/setup_common.h>
#include <util/check.h>
#include <util/strencodings.h>
Expand All @@ -15,6 +18,7 @@
#include <optional>
#include <regex>
#include <string>
#include <variant>
#include <vector>

using namespace util::hex_literals;
Expand Down Expand Up @@ -580,6 +584,22 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32, /*op_desc_id=*/uint256{"4a47b7f497721bf3fc48c69a5d22bc1f3617238649a8ba7cb96fbd92fec84a7e"});
Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT, /*op_desc_id=*/uint256{"a13112753066b5c59473a87c5771b1694a10531944a60e0ab2d7ad66ecb65bcd"});
Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE | XONLY_KEYS, {{"512077aab6e066f8a7419c5ab714c12c67d25007ed55a43cadcacb4d7a970a093f11"}}, OutputType::BECH32M, /*op_desc_id=*/uint256{"4290f3d017b270be53b91abc56d9d2f23a3ff361d5b1d39550ba011e6cae0da5"});
{
FlatSigningProvider provider, out;
std::string error;
auto descs{Parse("tmr(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", provider, error)};
BOOST_REQUIRE_MESSAGE(descs.size() == 1, error);
std::vector<CScript> scripts;
BOOST_REQUIRE(descs[0]->Expand(0, provider, scripts, out));
BOOST_REQUIRE_EQUAL(scripts.size(), 1U);
BOOST_CHECK_EQUAL(HexStr(scripts[0]).substr(0, 4), "5220");
std::vector<std::vector<unsigned char>> solutions;
BOOST_CHECK(Solver(scripts[0], solutions) == TxoutType::WITNESS_V2_P2MR);
CTxDestination destination;
BOOST_REQUIRE(ExtractDestination(scripts[0], destination));
BOOST_CHECK(std::get_if<WitnessV2P2MR>(&destination));
BOOST_CHECK_EQUAL(EncodeDestination(destination).substr(0, 4), "bc1z");
}
CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "wpkh(): Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey
CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2h/3/4h]03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh(): Key origin start '[ character expected but not found, got 'd' instead"); // Missing start bracket in key origin
CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh(): Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin
Expand Down
19 changes: 5 additions & 14 deletions src/test/fuzz/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,21 +157,12 @@ FUZZ_TARGET(script, .init = initialize_script)
if (!std::get_if<PubKeyDestination>(&tx_destination_1)) {
// Only try to round trip non-pubkey destinations since PubKeyDestination has no encoding
Assert(dest.empty() != valid);

// TODO: Temporary workaround: Skip round-trip test for P2MR during transition
if (!std::get_if<WitnessV2P2MR>(&tx_destination_1)) {
// Add debugging to see what type of destination is failing
if (std::get_if<WitnessUnknown>(&tx_destination_1)) {
// Skip WitnessUnknown as well during transition
// TODO: Remove this once all witness types are properly handled
} else {
Assert(tx_destination_1 == DecodeDestination(encoded_dest));
}

if (!std::get_if<WitnessUnknown>(&tx_destination_1)) {
Assert(tx_destination_1 == DecodeDestination(encoded_dest));
}

// TODO: Temporary workaround: Skip validity check for problematic destination types
if (!std::get_if<WitnessV2P2MR>(&tx_destination_1) &&
!std::get_if<WitnessUnknown>(&tx_destination_1)) {

if (!std::get_if<WitnessUnknown>(&tx_destination_1)) {
Assert(valid == IsValidDestinationString(encoded_dest));
}
}
Expand Down
46 changes: 24 additions & 22 deletions src/test/key_io_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <test/data/key_io_invalid.json.h>
#include <test/data/key_io_valid.json.h>

#include <addresstype.h>
#include <key.h>
#include <key_io.h>
#include <script/script.h>
Expand All @@ -17,9 +18,26 @@
#include <boost/test/unit_test.hpp>

#include <algorithm>
#include <variant>

BOOST_FIXTURE_TEST_SUITE(key_io_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(key_io_p2mr_roundtrip)
{
SelectParams(ChainType::MAIN);

uint256 root;
std::fill(root.begin(), root.end(), uint8_t{0x11});
const WitnessV2P2MR p2mr{root};
const std::string address{EncodeDestination(p2mr)};
BOOST_CHECK_EQUAL(address.substr(0, 4), "bc1z");

const CTxDestination decoded{DecodeDestination(address)};
BOOST_REQUIRE(IsValidDestination(decoded));
BOOST_CHECK(std::get<WitnessV2P2MR>(decoded) == p2mr);
BOOST_CHECK_EQUAL(HexStr(GetScriptForDestination(decoded)).substr(0, 4), "5220");
}

// Goal: check that parsed keys match test payload
BOOST_AUTO_TEST_CASE(key_io_valid_parse)
{
Expand Down Expand Up @@ -56,19 +74,8 @@ BOOST_AUTO_TEST_CASE(key_io_valid_parse)
// Must be valid public key
destination = DecodeDestination(exp_base58string);
CScript script = GetScriptForDestination(destination);

// Check if this is a witness version 2 address (P2MR or other v2 types)
bool is_p2mr = false;
if (exp_payload.size() >= 2 && static_cast<int>(exp_payload[0]) == 0x52) {
is_p2mr = true;
}

if (is_p2mr) {
// TODO: Add P2MR-specific validation here
} else {
BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest);
BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));
}
BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest);
BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));

// Try flipped case version
for (char& c : exp_base58string) {
Expand All @@ -79,15 +86,10 @@ BOOST_AUTO_TEST_CASE(key_io_valid_parse)
}
}
destination = DecodeDestination(exp_base58string);

if (is_p2mr) {
// TODO: Add P2MR-specific case flip validation here
} else {
BOOST_CHECK_MESSAGE(IsValidDestination(destination) == try_case_flip, "!IsValid case flipped:" + strTest);
if (IsValidDestination(destination)) {
script = GetScriptForDestination(destination);
BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));
}
BOOST_CHECK_MESSAGE(IsValidDestination(destination) == try_case_flip, "!IsValid case flipped:" + strTest);
if (IsValidDestination(destination)) {
script = GetScriptForDestination(destination);
BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));
}

// Public key must be invalid private key
Expand Down
2 changes: 1 addition & 1 deletion src/test/script_assets_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ static void AssetTest(const UniValue& test, SignatureCache& signature_cache)
// Check if this is a P2MR script (witness version 2, 32-byte program)
bool is_p2mr_script = false;
if (prevouts[idx].scriptPubKey.size() >= 2 &&
prevouts[idx].scriptPubKey[0] == 0x53 && // witness version 2
prevouts[idx].scriptPubKey[0] == 0x52 && // witness version 2
prevouts[idx].scriptPubKey[1] == 0x20 && // 32-byte program
tx.vin[idx].scriptSig.empty()) { // P2MR should have empty ScriptSig
is_p2mr_script = true;
Expand Down
28 changes: 28 additions & 0 deletions src/test/script_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <rpc/util.h>
#include <script/script.h>
#include <script/script_error.h>
#include <script/interpreter.h>
#include <script/sigcache.h>
#include <script/sign.h>
#include <script/signingprovider.h>
Expand Down Expand Up @@ -423,6 +424,33 @@ std::string JSONPrettyPrint(const UniValue& univalue)

BOOST_FIXTURE_TEST_SUITE(script_tests, ScriptTest)

BOOST_AUTO_TEST_CASE(p2mr_control_size)
{
CScriptWitness witness;
const CScript leaf_script{CScript() << OP_TRUE};
witness.stack.emplace_back(leaf_script.begin(), leaf_script.end());

std::vector<unsigned char> control(P2MR_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * (TAPROOT_CONTROL_MAX_NODE_COUNT + 1), 0);
control[0] = P2MR_LEAF_TAPSCRIPT;
witness.stack.push_back(control);

CScript script_pubkey;
script_pubkey << OP_2 << std::vector<unsigned char>(WITNESS_V2_P2MR_SIZE, 1);
DoTest(script_pubkey, CScript{}, witness, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT | SCRIPT_VERIFY_P2MR, "P2MR control block depth above 128", SCRIPT_ERR_P2MR_WRONG_CONTROL_SIZE);
}

BOOST_AUTO_TEST_CASE(p2mr_future_leaf_parity)
{
CScriptWitness witness;
const CScript leaf_script{CScript() << OP_TRUE};
witness.stack.emplace_back(leaf_script.begin(), leaf_script.end());
witness.stack.push_back({0xc2});

CScript script_pubkey;
script_pubkey << OP_2 << ToByteVector(ComputeTapleafHash(0xc2, witness.stack[0]));
DoTest(script_pubkey, CScript{}, witness, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT | SCRIPT_VERIFY_P2MR, "P2MR future leaf version with parity bit zero", SCRIPT_ERR_P2MR_WRONG_PARITY_BIT);
}

BOOST_AUTO_TEST_CASE(script_build)
{
const KeyData keys;
Expand Down
29 changes: 29 additions & 0 deletions src/test/transaction_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,35 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
CheckIsNotStandard(t, "dust");
}

BOOST_AUTO_TEST_CASE(p2mr_witness_standard_stack_item_size)
{
CCoinsView coins_dummy;
CCoinsViewCache coins(&coins_dummy);

CMutableTransaction funding;
funding.vin.resize(1);
funding.vin[0].prevout.SetNull();
funding.vout.emplace_back(
CENT,
CScript{} << OP_2 << std::vector<unsigned char>(WITNESS_V2_P2MR_SIZE, 1));
AddCoins(coins, CTransaction{funding}, 0, false);

const auto make_spend = [&](const size_t item_size) {
CMutableTransaction spend;
spend.vin.emplace_back(COutPoint{funding.GetHash(), 0});
spend.vout.emplace_back(CENT - 1, CScript{} << OP_TRUE);
spend.vin[0].scriptWitness.stack = {
std::vector<unsigned char>(item_size, 1),
std::vector<unsigned char>{OP_TRUE},
std::vector<unsigned char>{P2MR_LEAF_TAPSCRIPT},
};
return CTransaction{spend};
};

BOOST_CHECK(IsWitnessStandard(make_spend(MAX_STANDARD_P2MR_STACK_ITEM_SIZE), coins));
BOOST_CHECK(!IsWitnessStandard(make_spend(MAX_STANDARD_P2MR_STACK_ITEM_SIZE + 1), coins));
}

BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
{
CCoinsView coins_dummy;
Expand Down
4 changes: 2 additions & 2 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2360,9 +2360,9 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch
// mainnet and testnet).
// Similarly, only one historical block violated the TAPROOT rules on
// mainnet.
// For simplicity, always leave P2SH+WITNESS+TAPROOT on except for the two
// For simplicity, always leave P2SH+WITNESS+TAPROOT+P2MR on except for the two
// violating blocks.
uint32_t flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT};
uint32_t flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT | SCRIPT_VERIFY_P2MR};
const auto it{consensusparams.script_flag_exceptions.find(*Assert(block_index.phashBlock))};
if (it != consensusparams.script_flag_exceptions.end()) {
flags = it->second;
Expand Down
Loading