diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 84040bf847..4c55488b1f 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -794,6 +794,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 MakeScripts(const std::vector& keys, Span, FlatSigningProvider&) const override { + CScript ret; + std::vector 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> 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 { @@ -1032,7 +1056,6 @@ std::unique_ptr ParseScript(uint32_t& key_exp_index, Span ParseScript(uint32_t& key_exp_index, Span> providers; @@ -1078,9 +1106,12 @@ std::unique_ptr ParseScript(uint32_t& key_exp_index, Span 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; @@ -1101,10 +1132,17 @@ std::unique_ptr ParseScript(uint32_t& key_exp_index, Span(thres, std::move(providers), sorted_multi); - } else if (Func("sortedmulti", expr) || Func("multi", expr)) { + if (multi || sortedmulti) { + return std::make_unique(thres, std::move(providers), sortedmulti); + } else { + return std::make_unique(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); diff --git a/src/script/script.h b/src/script/script.h index 3425bf8102..ce4e885b50 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -29,6 +29,9 @@ static const int MAX_OPS_PER_SCRIPT = 201; // Maximum number of public keys per multisig static const int MAX_PUBKEYS_PER_MULTISIG = 20; +/** The limit of keys in OP_CHECKSIGADD-based scripts. It is due to the stack limit in BIP342. */ +static constexpr unsigned int MAX_PUBKEYS_PER_MULTI_A = 999; + // Maximum script length in bytes static const int MAX_SCRIPT_SIZE = 10000; diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 0dd8eba1ba..806b3169cd 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -139,6 +139,40 @@ static bool MatchMultisig(const CScript& script, int& required_sigs, std::vector return (it + 1 == script.end()); } +std::optional>>> MatchMultiA(const CScript& script) +{ + std::vector> keyspans; + + // Redundant, but very fast and selective test. + if (script.size() == 0 || script[0] != 32 || script.back() != OP_NUMEQUAL) return {}; + + // Parse keys + auto it = script.begin(); + while (script.end() - it >= 34) { + if (*it != 32) return {}; + ++it; + keyspans.emplace_back(&*it, 32); + it += 32; + if (*it != (keyspans.size() == 1 ? OP_CHECKSIG : OP_CHECKSIGADD)) return {}; + ++it; + } + if (keyspans.size() == 0 || keyspans.size() > MAX_PUBKEYS_PER_MULTI_A) return {}; + + // Parse threshold. + opcodetype opcode; + std::vector data; + if (!script.GetOp(it, opcode, data)) return {}; + if (it == script.end()) return {}; + if (*it != OP_NUMEQUAL) return {}; + ++it; + if (it != script.end()) return {}; + auto threshold = GetScriptNumber(opcode, data, 1, (int)keyspans.size()); + if (!threshold) return {}; + + // Construct result. + return std::pair{*threshold, std::move(keyspans)}; +} + TxoutType Solver(const CScript& scriptPubKey, std::vector>& vSolutionsRet) { vSolutionsRet.clear(); diff --git a/src/script/standard.h b/src/script/standard.h index eb50421768..75bfe2db38 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -191,6 +191,10 @@ CScript GetScriptForDestination(const CTxDestination& dest); /** Generate a P2PK script for the given pubkey. */ CScript GetScriptForRawPubKey(const CPubKey& pubkey); +/** Determine if script is a "multi_a" script. Returns (threshold, keyspans) if so, and nullopt otherwise. + * The keyspans refer to bytes in the passed script. */ +std::optional>>> MatchMultiA(const CScript& script LIFETIMEBOUND); + /** Generate a multisig script. */ CScript GetScriptForMultisig(int nRequired, const std::vector& keys);