Skip to content
Closed
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
18 changes: 18 additions & 0 deletions src/addresstype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ WitnessV0ScriptHash::WitnessV0ScriptHash(const CScript& in)
CSHA256().Write(in.data(), in.size()).Finalize(begin());
}

WitnessV2P2TSH::WitnessV2P2TSH(const CScript& in)
{
CSHA256().Write(in.data(), in.size()).Finalize(begin());
}

bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
{
std::vector<valtype> vSolutions;
Expand Down Expand Up @@ -87,6 +92,12 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
addressRet = tap;
return true;
}
case TxoutType::WITNESS_V2_P2TSH: {
WitnessV2P2TSH p2tsh;
std::copy(vSolutions[0].begin(), vSolutions[0].end(), p2tsh.begin());
addressRet = p2tsh;
return true;
}
case TxoutType::ANCHOR: {
addressRet = PayToAnchor();
return true;
Expand Down Expand Up @@ -147,6 +158,12 @@ class CScriptVisitor
{
return CScript() << CScript::EncodeOP_N(id.GetWitnessVersion()) << id.GetWitnessProgram();
}

CScript operator()(const WitnessV2P2TSH& id) const
{
// P2TSH is version 2
return CScript() << CScript::EncodeOP_N(2) << ToByteVector(id);
}
};

class ValidDestinationVisitor
Expand All @@ -160,6 +177,7 @@ class ValidDestinationVisitor
bool operator()(const WitnessV0ScriptHash& dest) const { return true; }
bool operator()(const WitnessV1Taproot& dest) const { return true; }
bool operator()(const WitnessUnknown& dest) const { return true; }
bool operator()(const WitnessV2P2TSH& dest) const { return true; }
};
} // namespace

Expand Down
10 changes: 9 additions & 1 deletion src/addresstype.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ struct WitnessV1Taproot : public XOnlyPubKey
explicit WitnessV1Taproot(const XOnlyPubKey& xpk) : XOnlyPubKey(xpk) {}
};

struct WitnessV2P2TSH : public BaseHash<uint256>
{
WitnessV2P2TSH() : BaseHash() {}
explicit WitnessV2P2TSH(const uint256& hash) : BaseHash(hash) {}
explicit WitnessV2P2TSH(const CScript& script);
};

//! CTxDestination subtype to encode any future Witness version
struct WitnessUnknown
{
Expand Down Expand Up @@ -138,9 +145,10 @@ struct PayToAnchor : public WitnessUnknown
* * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR address)
* * PayToAnchor: TxoutType::ANCHOR destination (P2A address)
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address)
* * WitnessV2P2TSH: TxoutType::WITNESS_V2_P2TSH destination (P2TSH address)
* A CTxDestination is the internal data type encoded in a bitcoin address
*/
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown>;
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown, WitnessV2P2TSH>;

/** Check whether a CTxDestination corresponds to one with an address. */
bool IsValidDestination(const CTxDestination& dest);
Expand Down
18 changes: 18 additions & 0 deletions src/key_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ class DestinationEncoder
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
}

std::string operator()(const WitnessV2P2TSH& id) const
{
std::vector<unsigned char> data = {2}; // Version 2
data.reserve(53); // Reserve space for the hash
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end());
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
}

std::string operator()(const CNoDestination& no) const { return {}; }
std::string operator()(const PubKeyDestination& pk) const { return {}; }
};
Expand Down Expand Up @@ -181,6 +189,16 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
return tap;
}

if (version == 2 && data.size() == WITNESS_V2_P2TSH_SIZE) {
WitnessV2P2TSH tsh;
if (data.size() == tsh.size()) {
std::copy(data.begin(), data.end(), tsh.begin());
return tsh;
}
error_str = strprintf("Invalid P2TSH address program size (%d %s)", data.size(), byte_str);
return CNoDestination();
}

if (CScript::IsPayToAnchor(version, data)) {
return PayToAnchor();
}
Expand Down
34 changes: 34 additions & 0 deletions src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType)
return false;
if (m < 1 || m > n)
return false;
} else if (whichType == TxoutType::WITNESS_V2_P2TSH) {
// Accept as standard
return true;
}

return true;
Expand Down Expand Up @@ -242,6 +245,9 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) {
return false;
}
} else if (whichType == TxoutType::WITNESS_V2_P2TSH) {
// Accept as standard
continue;
}
}

Expand Down Expand Up @@ -333,6 +339,34 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
return false;
}
}

// Check policy limits for P2TSH spends:
// - MAX_STANDARD_P2TSH_STACK_ITEM_SIZE limit for stack item size
// - Script path only (no key path spending)
// - No annexes
if (witnessversion == 2 && witnessprogram.size() == WITNESS_V2_P2TSH_SIZE) {
// P2TSH spend (non-P2SH-wrapped, version 3, 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.
return false;
}
if (stack.size() >= 2) {
// Script path spend (2 or more stack elements after removing optional annex)
const auto& control_block = SpanPopBack(stack);
SpanPopBack(stack); // Ignore script
if (control_block.empty()) return false; // Empty control block is invalid
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
// Leaf version 0xc0 (aka Tapscript, see BIP 342)
for (const auto& item : stack) {
if (item.size() > MAX_STANDARD_P2TSH_STACK_ITEM_SIZE) return false;
}
}
} else {
// P2TSH only supports script path spending, no key path spending allowed
return false;
}
}
}
return true;
}
Expand Down
8 changes: 6 additions & 2 deletions src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +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 P2TSH script */
static constexpr unsigned int MAX_STANDARD_P2TSH_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 Expand Up @@ -104,7 +106,8 @@ static constexpr unsigned int MANDATORY_SCRIPT_VERIFY_FLAGS{SCRIPT_VERIFY_P2SH |
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY |
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY |
SCRIPT_VERIFY_WITNESS |
SCRIPT_VERIFY_TAPROOT};
SCRIPT_VERIFY_TAPROOT |
SCRIPT_VERIFY_P2TSH};

/**
* Standard script verification flags that standard transactions will comply
Expand All @@ -125,7 +128,8 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI
SCRIPT_VERIFY_CONST_SCRIPTCODE |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION |
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE};
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE |
SCRIPT_VERIFY_P2TSH};

/** For convenience, standard but not mandatory verify flags. */
static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS};
Expand Down
2 changes: 2 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ static RPCHelpMan decodescript()
case TxoutType::SCRIPTHASH:
case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::WITNESS_V2_P2TSH:
case TxoutType::ANCHOR:
// Should not be wrapped
return false;
Expand Down Expand Up @@ -590,6 +591,7 @@ static RPCHelpMan decodescript()
case TxoutType::WITNESS_V0_KEYHASH:
case TxoutType::WITNESS_V0_SCRIPTHASH:
case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::WITNESS_V2_P2TSH:
case TxoutType::ANCHOR:
// Should not be wrapped
return false;
Expand Down
10 changes: 10 additions & 0 deletions src/rpc/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,16 @@ class DescribeAddressVisitor
obj.pushKV("witness_program", HexStr(id.GetWitnessProgram()));
return obj;
}

UniValue operator()(const WitnessV2P2TSH& id) const
{
UniValue obj(UniValue::VOBJ);
obj.pushKV("isscript", true);
obj.pushKV("iswitness", true);
obj.pushKV("witness_version", 2);
obj.pushKV("witness_program", HexStr(id));
return obj;
}
};

UniValue DescribeAddress(const CTxDestination& dest)
Expand Down
Loading
Loading