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;
|
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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Reference in a new issue