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;
return true;
}
case TxoutType::ANCHOR: {
addressRet = PayToAnchor();
return true;
}
case TxoutType::WITNESS_UNKNOWN: {
addressRet = WitnessUnknown{vSolutions[0][0], vSolutions[1]};
return true;

View file

@ -9,6 +9,7 @@
#include <pubkey.h>
#include <script/script.h>
#include <uint256.h>
#include <util/check.h>
#include <util/hash_type.h>
#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.
* * CNoDestination: Optionally a script, no corresponding address.
@ -125,10 +133,11 @@ public:
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH address)
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH address)
* * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR address)
* * PayToAnchor: TxoutType::ANCHOR destination (P2A address)
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W??? 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. */
bool IsValidDestination(const CTxDestination& dest);

View file

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

View file

@ -557,6 +557,7 @@ static RPCHelpMan decodescript()
case TxoutType::SCRIPTHASH:
case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
// Should not be wrapped
return false;
} // 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_SCRIPTHASH:
case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
// Should not be wrapped
return false;
} // no default case, so the compiler can warn about missing cases

View file

@ -332,6 +332,14 @@ public:
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 obj(UniValue::VOBJ);

View file

@ -204,6 +204,23 @@ unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const
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
{
// Extra-fast test for pay-to-script-hash CScripts:

View file

@ -533,6 +533,14 @@ public:
*/
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 IsPayToWitnessScriptHash() 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:
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
assert(false);
}

View file

@ -24,6 +24,7 @@ std::string GetTxnOutputType(TxoutType t)
case TxoutType::SCRIPTHASH: return "scripthash";
case TxoutType::MULTISIG: return "multisig";
case TxoutType::NULL_DATA: return "nulldata";
case TxoutType::ANCHOR: return "anchor";
case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
case TxoutType::WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
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));
return TxoutType::WITNESS_V1_TAPROOT;
}
if (scriptPubKey.IsPayToAnchor()) {
return TxoutType::ANCHOR;
}
if (witnessversion != 0) {
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
vSolutionsRet.push_back(std::move(witnessprogram));

View file

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

View file

@ -76,11 +76,13 @@ FUZZ_TARGET(script, .init = initialize_script)
assert(which_type == TxoutType::PUBKEY ||
which_type == TxoutType::NONSTANDARD ||
which_type == TxoutType::NULL_DATA ||
which_type == TxoutType::MULTISIG);
which_type == TxoutType::MULTISIG ||
which_type == TxoutType::ANCHOR);
}
if (which_type == TxoutType::NONSTANDARD ||
which_type == TxoutType::NULL_DATA ||
which_type == TxoutType::MULTISIG) {
which_type == TxoutType::MULTISIG ||
which_type == TxoutType::ANCHOR) {
assert(!extract_destination_ret);
}
@ -94,6 +96,7 @@ FUZZ_TARGET(script, .init = initialize_script)
(void)Solver(script, solutions);
(void)script.HasValidOps();
(void)script.IsPayToAnchor();
(void)script.IsPayToScriptHash();
(void)script.IsPayToWitnessScriptHash();
(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 = PayToAnchor{};
},
[&] {
std::vector<unsigned char> program{ConsumeRandomLengthByteVector(fuzzed_data_provider, /*max_length=*/40)};
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[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
s.clear();
s << OP_9 << OP_ADD << OP_11 << OP_EQUAL;
@ -186,6 +200,18 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_failure)
s.clear();
s << OP_0 << std::vector<unsigned char>(19, 0x01);
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)

View file

@ -1026,6 +1026,14 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
t.vout[0].nValue = 239;
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()

View file

@ -501,6 +501,7 @@ public:
}
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); }
};

View file

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

View file

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