diff options
author | Greg Sanders <gsanders87@gmail.com> | 2023-11-08 14:07:49 -0500 |
---|---|---|
committer | Greg Sanders <gsanders87@gmail.com> | 2024-07-30 14:06:58 -0400 |
commit | 455fca86cfada1823aa28615b5683f9dc73dbb9a (patch) | |
tree | 5d87a5a2520bf891297f01481bdb475a9b15e012 /src | |
parent | 8754d055c65e11fd2afa59f9e5de7c60a9e0ec23 (diff) |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/addresstype.cpp | 4 | ||||
-rw-r--r-- | src/addresstype.h | 11 | ||||
-rw-r--r-- | src/key_io.cpp | 4 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 2 | ||||
-rw-r--r-- | src/rpc/util.cpp | 8 | ||||
-rw-r--r-- | src/script/script.cpp | 17 | ||||
-rw-r--r-- | src/script/script.h | 8 | ||||
-rw-r--r-- | src/script/sign.cpp | 3 | ||||
-rw-r--r-- | src/script/solver.cpp | 4 | ||||
-rw-r--r-- | src/script/solver.h | 1 | ||||
-rw-r--r-- | src/test/fuzz/script.cpp | 7 | ||||
-rw-r--r-- | src/test/fuzz/util.cpp | 3 | ||||
-rw-r--r-- | src/test/script_standard_tests.cpp | 26 | ||||
-rw-r--r-- | src/test/transaction_tests.cpp | 8 | ||||
-rw-r--r-- | src/wallet/rpc/addresses.cpp | 1 | ||||
-rw-r--r-- | src/wallet/rpc/backup.cpp | 1 | ||||
-rw-r--r-- | src/wallet/scriptpubkeyman.cpp | 1 |
17 files changed, 106 insertions, 3 deletions
diff --git a/src/addresstype.cpp b/src/addresstype.cpp index f199d1b479..67e643943d 100644 --- a/src/addresstype.cpp +++ b/src/addresstype.cpp @@ -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; diff --git a/src/addresstype.h b/src/addresstype.h index 0152858bad..93cdf66c5b 100644 --- a/src/addresstype.h +++ b/src/addresstype.h @@ -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); diff --git a/src/key_io.cpp b/src/key_io.cpp index a373a2201d..29002afc45 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -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(); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index ed9ef2c159..adb3fd0cd2 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -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 diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 4df4466c49..680882c989 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -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); diff --git a/src/script/script.cpp b/src/script/script.cpp index 73ea336c4f..d650db9a0d 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -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: diff --git a/src/script/script.h b/src/script/script.h index 035152ee51..323411251c 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -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; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 6e26ec11e0..9568348bf6 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -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); } diff --git a/src/script/solver.cpp b/src/script/solver.cpp index 3dfa9cd6ba..bd3c5cdf72 100644 --- a/src/script/solver.cpp +++ b/src/script/solver.cpp @@ -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)); diff --git a/src/script/solver.h b/src/script/solver.h index dc8f4c357d..5a7b685a6f 100644 --- a/src/script/solver.h +++ b/src/script/solver.h @@ -22,6 +22,7 @@ template <typename C> class Span; enum class TxoutType { NONSTANDARD, // 'standard' transaction types: + ANCHOR, //!< anyone can spend script PUBKEY, PUBKEYHASH, SCRIPTHASH, diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp index fe41a8c6ae..a23543d70e 100644 --- a/src/test/fuzz/script.cpp +++ b/src/test/fuzz/script.cpp @@ -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(); diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp index 92ded99917..425b9559a7 100644 --- a/src/test/fuzz/util.cpp +++ b/src/test/fuzz/util.cpp @@ -214,6 +214,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) { program = {0, 0}; diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 29e2d4a569..cd4c6ba3ba 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -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) diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index a7fda5865c..165a55590f 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -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() diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp index 35c93337c1..5b2fed7158 100644 --- a/src/wallet/rpc/addresses.cpp +++ b/src/wallet/rpc/addresses.cpp @@ -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); } }; diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 8cddb8b099..0d0e86ed24 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -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(); diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index e4632777cc..ae61696dd0 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -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(); |