0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-03-05 14:06:27 -05:00

policy: Add OP_1 <0x4e73> as a standard output type

These outputs are called anchors, and allow
key-less anchor spends which are vsize-minimized
versus keyed anchors which require larger outputs
when creating and inputs when spending.
This commit is contained in:
Greg Sanders 2023-11-08 14:07:49 -05:00
parent 8754d055c6
commit 455fca86cf
17 changed files with 106 additions and 3 deletions

View file

@ -87,6 +87,10 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
addressRet = tap; addressRet = tap;
return true; return true;
} }
case TxoutType::ANCHOR: {
addressRet = PayToAnchor();
return true;
}
case TxoutType::WITNESS_UNKNOWN: { case TxoutType::WITNESS_UNKNOWN: {
addressRet = WitnessUnknown{vSolutions[0][0], vSolutions[1]}; addressRet = WitnessUnknown{vSolutions[0][0], vSolutions[1]};
return true; return true;

View file

@ -9,6 +9,7 @@
#include <pubkey.h> #include <pubkey.h>
#include <script/script.h> #include <script/script.h>
#include <uint256.h> #include <uint256.h>
#include <util/check.h>
#include <util/hash_type.h> #include <util/hash_type.h>
#include <algorithm> #include <algorithm>
@ -116,6 +117,13 @@ public:
} }
}; };
struct PayToAnchor : public WitnessUnknown
{
PayToAnchor() : WitnessUnknown(1, {0x4e, 0x73}) {
Assume(CScript::IsPayToAnchor(1, {0x4e, 0x73}));
};
};
/** /**
* A txout script categorized into standard templates. * A txout script categorized into standard templates.
* * CNoDestination: Optionally a script, no corresponding address. * * CNoDestination: Optionally a script, no corresponding address.
@ -125,10 +133,11 @@ public:
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address) * * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address)
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH address) * * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH address)
* * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR address) * * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR address)
* * PayToAnchor: TxoutType::ANCHOR destination (P2A address)
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address) * * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? address)
* A CTxDestination is the internal data type encoded in a bitcoin address * A CTxDestination is the internal data type encoded in a bitcoin address
*/ */
using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>; using CTxDestination = std::variant<CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown>;
/** Check whether a CTxDestination corresponds to one with an address. */ /** Check whether a CTxDestination corresponds to one with an address. */
bool IsValidDestination(const CTxDestination& dest); bool IsValidDestination(const CTxDestination& dest);

View file

@ -181,6 +181,10 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
return tap; return tap;
} }
if (CScript::IsPayToAnchor(version, data)) {
return PayToAnchor();
}
if (version > 16) { if (version > 16) {
error_str = "Invalid Bech32 address witness version"; error_str = "Invalid Bech32 address witness version";
return CNoDestination(); return CNoDestination();

View file

@ -557,6 +557,7 @@ static RPCHelpMan decodescript()
case TxoutType::SCRIPTHASH: case TxoutType::SCRIPTHASH:
case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT: case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
// Should not be wrapped // Should not be wrapped
return false; return false;
} // no default case, so the compiler can warn about missing cases } // no default case, so the compiler can warn about missing cases
@ -599,6 +600,7 @@ static RPCHelpMan decodescript()
case TxoutType::WITNESS_V0_KEYHASH: case TxoutType::WITNESS_V0_KEYHASH:
case TxoutType::WITNESS_V0_SCRIPTHASH: case TxoutType::WITNESS_V0_SCRIPTHASH:
case TxoutType::WITNESS_V1_TAPROOT: case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
// Should not be wrapped // Should not be wrapped
return false; return false;
} // no default case, so the compiler can warn about missing cases } // no default case, so the compiler can warn about missing cases

View file

@ -332,6 +332,14 @@ public:
return obj; return obj;
} }
UniValue operator()(const PayToAnchor& anchor) const
{
UniValue obj(UniValue::VOBJ);
obj.pushKV("isscript", true);
obj.pushKV("iswitness", true);
return obj;
}
UniValue operator()(const WitnessUnknown& id) const UniValue operator()(const WitnessUnknown& id) const
{ {
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);

View file

@ -204,6 +204,23 @@ unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const
return subscript.GetSigOpCount(true); return subscript.GetSigOpCount(true);
} }
bool CScript::IsPayToAnchor() const
{
return (this->size() == 4 &&
(*this)[0] == OP_1 &&
(*this)[1] == 0x02 &&
(*this)[2] == 0x4e &&
(*this)[3] == 0x73);
}
bool CScript::IsPayToAnchor(int version, const std::vector<unsigned char>& program)
{
return version == 1 &&
program.size() == 2 &&
program[0] == 0x4e &&
program[1] == 0x73;
}
bool CScript::IsPayToScriptHash() const bool CScript::IsPayToScriptHash() const
{ {
// Extra-fast test for pay-to-script-hash CScripts: // Extra-fast test for pay-to-script-hash CScripts:

View file

@ -533,6 +533,14 @@ public:
*/ */
unsigned int GetSigOpCount(const CScript& scriptSig) const; unsigned int GetSigOpCount(const CScript& scriptSig) const;
/*
* OP_1 <0x4e73>
*/
bool IsPayToAnchor() const;
/** Checks if output of IsWitnessProgram comes from a P2A output script
*/
static bool IsPayToAnchor(int version, const std::vector<unsigned char>& program);
bool IsPayToScriptHash() const; bool IsPayToScriptHash() const;
bool IsPayToWitnessScriptHash() const; bool IsPayToWitnessScriptHash() const;
bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const; bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const;

View file

@ -475,6 +475,9 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
case TxoutType::WITNESS_V1_TAPROOT: case TxoutType::WITNESS_V1_TAPROOT:
return SignTaproot(provider, creator, WitnessV1Taproot(XOnlyPubKey{vSolutions[0]}), sigdata, ret); return SignTaproot(provider, creator, WitnessV1Taproot(XOnlyPubKey{vSolutions[0]}), sigdata, ret);
case TxoutType::ANCHOR:
return true;
} // no default case, so the compiler can warn about missing cases } // no default case, so the compiler can warn about missing cases
assert(false); assert(false);
} }

View file

@ -24,6 +24,7 @@ std::string GetTxnOutputType(TxoutType t)
case TxoutType::SCRIPTHASH: return "scripthash"; case TxoutType::SCRIPTHASH: return "scripthash";
case TxoutType::MULTISIG: return "multisig"; case TxoutType::MULTISIG: return "multisig";
case TxoutType::NULL_DATA: return "nulldata"; case TxoutType::NULL_DATA: return "nulldata";
case TxoutType::ANCHOR: return "anchor";
case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash"; case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
case TxoutType::WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash"; case TxoutType::WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
case TxoutType::WITNESS_V1_TAPROOT: return "witness_v1_taproot"; case TxoutType::WITNESS_V1_TAPROOT: return "witness_v1_taproot";
@ -165,6 +166,9 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned c
vSolutionsRet.push_back(std::move(witnessprogram)); vSolutionsRet.push_back(std::move(witnessprogram));
return TxoutType::WITNESS_V1_TAPROOT; return TxoutType::WITNESS_V1_TAPROOT;
} }
if (scriptPubKey.IsPayToAnchor()) {
return TxoutType::ANCHOR;
}
if (witnessversion != 0) { if (witnessversion != 0) {
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion}); vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
vSolutionsRet.push_back(std::move(witnessprogram)); vSolutionsRet.push_back(std::move(witnessprogram));

View file

@ -22,6 +22,7 @@ template <typename C> class Span;
enum class TxoutType { enum class TxoutType {
NONSTANDARD, NONSTANDARD,
// 'standard' transaction types: // 'standard' transaction types:
ANCHOR, //!< anyone can spend script
PUBKEY, PUBKEY,
PUBKEYHASH, PUBKEYHASH,
SCRIPTHASH, SCRIPTHASH,

View file

@ -76,11 +76,13 @@ FUZZ_TARGET(script, .init = initialize_script)
assert(which_type == TxoutType::PUBKEY || assert(which_type == TxoutType::PUBKEY ||
which_type == TxoutType::NONSTANDARD || which_type == TxoutType::NONSTANDARD ||
which_type == TxoutType::NULL_DATA || which_type == TxoutType::NULL_DATA ||
which_type == TxoutType::MULTISIG); which_type == TxoutType::MULTISIG ||
which_type == TxoutType::ANCHOR);
} }
if (which_type == TxoutType::NONSTANDARD || if (which_type == TxoutType::NONSTANDARD ||
which_type == TxoutType::NULL_DATA || which_type == TxoutType::NULL_DATA ||
which_type == TxoutType::MULTISIG) { which_type == TxoutType::MULTISIG ||
which_type == TxoutType::ANCHOR) {
assert(!extract_destination_ret); assert(!extract_destination_ret);
} }
@ -94,6 +96,7 @@ FUZZ_TARGET(script, .init = initialize_script)
(void)Solver(script, solutions); (void)Solver(script, solutions);
(void)script.HasValidOps(); (void)script.HasValidOps();
(void)script.IsPayToAnchor();
(void)script.IsPayToScriptHash(); (void)script.IsPayToScriptHash();
(void)script.IsPayToWitnessScriptHash(); (void)script.IsPayToWitnessScriptHash();
(void)script.IsPushOnly(); (void)script.IsPushOnly();

View file

@ -213,6 +213,9 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no
[&] { [&] {
tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}}; tx_destination = WitnessV1Taproot{XOnlyPubKey{ConsumeUInt256(fuzzed_data_provider)}};
}, },
[&] {
tx_destination = PayToAnchor{};
},
[&] { [&] {
std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)}; std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)};
if (program.size() < 2) { if (program.size() < 2) {

View file

@ -128,6 +128,20 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_success)
BOOST_CHECK(solutions[0] == std::vector<unsigned char>{16}); BOOST_CHECK(solutions[0] == std::vector<unsigned char>{16});
BOOST_CHECK(solutions[1] == ToByteVector(uint256::ONE)); BOOST_CHECK(solutions[1] == ToByteVector(uint256::ONE));
// TxoutType::ANCHOR
std::vector<unsigned char> anchor_bytes{0x4e, 0x73};
s.clear();
s << OP_1 << anchor_bytes;
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::ANCHOR);
BOOST_CHECK(solutions.empty());
// Sanity-check IsPayToAnchor
int version{-1};
std::vector<unsigned char> witness_program;
BOOST_CHECK(s.IsPayToAnchor());
BOOST_CHECK(s.IsWitnessProgram(version, witness_program));
BOOST_CHECK(CScript::IsPayToAnchor(version, witness_program));
// TxoutType::NONSTANDARD // TxoutType::NONSTANDARD
s.clear(); s.clear();
s << OP_9 << OP_ADD << OP_11 << OP_EQUAL; s << OP_9 << OP_ADD << OP_11 << OP_EQUAL;
@ -186,6 +200,18 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_failure)
s.clear(); s.clear();
s << OP_0 << std::vector<unsigned char>(19, 0x01); s << OP_0 << std::vector<unsigned char>(19, 0x01);
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::NONSTANDARD); BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::NONSTANDARD);
// TxoutType::ANCHOR but wrong witness version
s.clear();
s << OP_2 << std::vector<unsigned char>{0x4e, 0x73};
BOOST_CHECK(!s.IsPayToAnchor());
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::WITNESS_UNKNOWN);
// TxoutType::ANCHOR but wrong 2-byte data push
s.clear();
s << OP_1 << std::vector<unsigned char>{0xff, 0xff};
BOOST_CHECK(!s.IsPayToAnchor());
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::WITNESS_UNKNOWN);
} }
BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination) BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination)

View file

@ -1026,6 +1026,14 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
t.vout[0].nValue = 239; t.vout[0].nValue = 239;
CheckIsNotStandard(t, "dust"); CheckIsNotStandard(t, "dust");
} }
// Check anchor outputs
t.vout[0].scriptPubKey = CScript() << OP_1 << std::vector<unsigned char>{0x4e, 0x73};
BOOST_CHECK(t.vout[0].scriptPubKey.IsPayToAnchor());
t.vout[0].nValue = 240;
CheckIsStandard(t);
t.vout[0].nValue = 239;
CheckIsNotStandard(t, "dust");
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View file

@ -501,6 +501,7 @@ public:
} }
UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const PayToAnchor& id) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); }
}; };

View file

@ -910,6 +910,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
case TxoutType::NONSTANDARD: case TxoutType::NONSTANDARD:
case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT: case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
return "unrecognized script"; return "unrecognized script";
} // no default case, so the compiler can warn about missing cases } // no default case, so the compiler can warn about missing cases
NONFATAL_UNREACHABLE(); NONFATAL_UNREACHABLE();

View file

@ -115,6 +115,7 @@ IsMineResult IsMineInner(const LegacyDataSPKM& keystore, const CScript& scriptPu
case TxoutType::NULL_DATA: case TxoutType::NULL_DATA:
case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT: case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
break; break;
case TxoutType::PUBKEY: case TxoutType::PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID(); keyID = CPubKey(vSolutions[0]).GetID();