aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGreg Sanders <gsanders87@gmail.com>2023-11-08 14:07:49 -0500
committerGreg Sanders <gsanders87@gmail.com>2024-07-30 14:06:58 -0400
commit455fca86cfada1823aa28615b5683f9dc73dbb9a (patch)
tree5d87a5a2520bf891297f01481bdb475a9b15e012 /src
parent8754d055c65e11fd2afa59f9e5de7c60a9e0ec23 (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.cpp4
-rw-r--r--src/addresstype.h11
-rw-r--r--src/key_io.cpp4
-rw-r--r--src/rpc/rawtransaction.cpp2
-rw-r--r--src/rpc/util.cpp8
-rw-r--r--src/script/script.cpp17
-rw-r--r--src/script/script.h8
-rw-r--r--src/script/sign.cpp3
-rw-r--r--src/script/solver.cpp4
-rw-r--r--src/script/solver.h1
-rw-r--r--src/test/fuzz/script.cpp7
-rw-r--r--src/test/fuzz/util.cpp3
-rw-r--r--src/test/script_standard_tests.cpp26
-rw-r--r--src/test/transaction_tests.cpp8
-rw-r--r--src/wallet/rpc/addresses.cpp1
-rw-r--r--src/wallet/rpc/backup.cpp1
-rw-r--r--src/wallet/scriptpubkeyman.cpp1
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();