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:
parent
8754d055c6
commit
455fca86cf
17 changed files with 106 additions and 3 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -22,6 +22,7 @@ template <typename C> class Span;
|
|||
enum class TxoutType {
|
||||
NONSTANDARD,
|
||||
// 'standard' transaction types:
|
||||
ANCHOR, //!< anyone can spend script
|
||||
PUBKEY,
|
||||
PUBKEYHASH,
|
||||
SCRIPTHASH,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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); }
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue