aboutsummaryrefslogtreecommitdiff
path: root/src/script/descriptor.cpp
diff options
context:
space:
mode:
authorAndrew Chow <achow101-github@achow101.com>2022-03-04 07:12:16 -0500
committerAndrew Chow <achow101-github@achow101.com>2022-03-04 07:28:23 -0500
commitbada9636d7f2efbc620fd89107baa2bf3e64a6b8 (patch)
tree89bb23c7472e0c52330c0ef809ddd138dbb0b802 /src/script/descriptor.cpp
parent4fae737f4b749b0e2e3cf3fd4dc28db7a1a93b19 (diff)
parent4828d53eccd52a67631c64cef0ba7df90dff138d (diff)
Merge bitcoin/bitcoin#24043: Add (sorted)multi_a descriptor for k-of-n multisig inside tr
4828d53eccd52a67631c64cef0ba7df90dff138d Add (sorted)multi_a descriptors to doc/descriptors.md (Pieter Wuille) b5f33ac1f82aea290b4653af36ac2ad1bf1cce7b Simplify wallet_taproot.py functional test (Pieter Wuille) eb0667ea96d52db9135514a5e95ab943f6abd8a6 Add tests for (sorted)multi_a derivation/signing (Pieter Wuille) c17c6aa08df81aa0086d80b50187c8cd60ecc222 Add signing support for (sorted)multi_a scripts (Pieter Wuille) 3eed6fca57d1fa7544f372e6e7de0a9ae1b5715a Add multi_a descriptor inference (Pieter Wuille) 79728c4a3d8a74f276daf1e72abbdecdab85a5d8 Add (sorted)multi_a descriptor and script derivation (Pieter Wuille) 25e95f9ff89a97b87ce218f28274c3c821b2d54d Merge/generalize IsValidMultisigKeyCount/GetMultisigKeyCount (Pieter Wuille) Pull request description: This adds a new `multi_a(k,key_1,key_2,...,key_n)` (and corresponding `sortedmulti_a`) descriptor for k-of-n policies inside `tr()`. Semantically it is very similar to the existing `multi()` descriptor, but with the following changes: * The corresponding script is `<key1> OP_CHECKSIG <key2> OP_CHECKSIGADD <key3> OP_CHECKSIGADD ... <key_n> OP_CHECKSIGADD <k> OP_NUMEQUAL`, rather than the traditional `OP_CHECKMULTISIG`-based script, making it usable inside the `tr()` descriptor. * The keys can optionally be specified in x-only notation. * Both the number of keys and the threshold can be as high as 999; this is the limit due to the consensus stacksize=1000 limit I expect that this functionality will later be replaced with a miniscript-based implementation, but I don't think it's necessary to wait for that. Limitations: * The wallet code will for not estimate witness size incorrectly for script path spends, which may result in a (dramatic) fee underpayment with large multi_a scripts. * The multi_a script construction is (slightly) suboptimal for n-of-n (where a `<key1> OP_CHECKSIGVERIFY ... <key_n-1> OP_CHECKSIGVERIFY <key_n> OP_CHECKSIG` would be better). Such a construction is not included here. ACKs for top commit: achow101: ACK 4828d53eccd52a67631c64cef0ba7df90dff138d gruve-p: ACK https://github.com/bitcoin/bitcoin/pull/24043/commits/4828d53eccd52a67631c64cef0ba7df90dff138d sanket1729: code review ACK 4828d53eccd52a67631c64cef0ba7df90dff138d darosior: Code review ACK 4828d53eccd52a67631c64cef0ba7df90dff138d Tree-SHA512: 5dcd434b79585f0ff830f7d501d27df5e346f5749f47a3109ec309ebf2cbbad0e1da541eec654026d911ab67fd7cf7793fab0f765628d68d81b96ef2a4d234ce
Diffstat (limited to 'src/script/descriptor.cpp')
-rw-r--r--src/script/descriptor.cpp68
1 files changed, 63 insertions, 5 deletions
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 798c4b3ea0..23540f6aef 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -802,6 +802,30 @@ public:
bool IsSingleType() const final { return true; }
};
+/** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */
+class MultiADescriptor final : public DescriptorImpl
+{
+ const int m_threshold;
+ const bool m_sorted;
+protected:
+ std::string ToStringExtra() const override { return strprintf("%i", m_threshold); }
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override {
+ CScript ret;
+ std::vector<XOnlyPubKey> xkeys;
+ for (const auto& key : keys) xkeys.emplace_back(key);
+ if (m_sorted) std::sort(xkeys.begin(), xkeys.end());
+ ret << ToByteVector(xkeys[0]) << OP_CHECKSIG;
+ for (size_t i = 1; i < keys.size(); ++i) {
+ ret << ToByteVector(xkeys[i]) << OP_CHECKSIGADD;
+ }
+ ret << m_threshold << OP_NUMEQUAL;
+ return Vector(std::move(ret));
+ }
+public:
+ MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {}
+ bool IsSingleType() const final { return true; }
+};
+
/** A parsed sh(...) descriptor. */
class SHDescriptor final : public DescriptorImpl
{
@@ -1040,7 +1064,6 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
using namespace spanparsing;
auto expr = Expr(sp);
- bool sorted_multi = false;
if (Func("pk", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
if (!pubkey) return nullptr;
@@ -1065,7 +1088,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
error = "Can only have combo() at top level";
return nullptr;
}
- if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr))) {
+ const bool multi = Func("multi", expr);
+ const bool sortedmulti = !multi && Func("sortedmulti", expr);
+ const bool multi_a = !(multi || sortedmulti) && Func("multi_a", expr);
+ const bool sortedmulti_a = !(multi || sortedmulti || multi_a) && Func("sortedmulti_a", expr);
+ if (((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && (multi || sortedmulti)) ||
+ (ctx == ParseScriptContext::P2TR && (multi_a || sortedmulti_a))) {
auto threshold = Expr(expr);
uint32_t thres;
std::vector<std::unique_ptr<PubkeyProvider>> providers;
@@ -1086,9 +1114,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
providers.emplace_back(std::move(pk));
key_exp_index++;
}
- if (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG) {
+ if ((multi || sortedmulti) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG)) {
error = strprintf("Cannot have %u keys in multisig; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTISIG);
return nullptr;
+ } else if ((multi_a || sortedmulti_a) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTI_A)) {
+ error = strprintf("Cannot have %u keys in multi_a; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTI_A);
+ return nullptr;
} else if (thres < 1) {
error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres);
return nullptr;
@@ -1109,10 +1140,17 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
return nullptr;
}
}
- return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi);
- } else if (Func("sortedmulti", expr) || Func("multi", expr)) {
+ if (multi || sortedmulti) {
+ return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sortedmulti);
+ } else {
+ return std::make_unique<MultiADescriptor>(thres, std::move(providers), sortedmulti_a);
+ }
+ } else if (multi || sortedmulti) {
error = "Can only have multi/sortedmulti at top level, in sh(), or in wsh()";
return nullptr;
+ } else if (multi_a || sortedmulti_a) {
+ error = "Can only have multi_a/sortedmulti_a inside tr()";
+ return nullptr;
}
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error);
@@ -1257,6 +1295,21 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS
return key_provider;
}
+std::unique_ptr<DescriptorImpl> InferMultiA(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
+{
+ auto match = MatchMultiA(script);
+ if (!match) return {};
+ std::vector<std::unique_ptr<PubkeyProvider>> keys;
+ keys.reserve(match->second.size());
+ for (const auto keyspan : match->second) {
+ if (keyspan.size() != 32) return {};
+ auto key = InferXOnlyPubkey(XOnlyPubKey{keyspan}, ctx, provider);
+ if (!key) return {};
+ keys.push_back(std::move(key));
+ }
+ return std::make_unique<MultiADescriptor>(match->first, std::move(keys));
+}
+
std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
{
if (ctx == ParseScriptContext::P2TR && script.size() == 34 && script[0] == 32 && script[33] == OP_CHECKSIG) {
@@ -1264,6 +1317,11 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
return std::make_unique<PKDescriptor>(InferXOnlyPubkey(key, ctx, provider), true);
}
+ if (ctx == ParseScriptContext::P2TR) {
+ auto ret = InferMultiA(script, ctx, provider);
+ if (ret) return ret;
+ }
+
std::vector<std::vector<unsigned char>> data;
TxoutType txntype = Solver(script, data);