diff options
Diffstat (limited to 'src/script')
-rw-r--r-- | src/script/descriptor.cpp | 570 | ||||
-rw-r--r-- | src/script/descriptor.h | 4 |
2 files changed, 439 insertions, 135 deletions
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 83b07ae459..5026470edc 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -220,6 +220,9 @@ public: virtual std::optional<CPubKey> GetRootPubKey() const = 0; /** Return the extended public key for this PubkeyProvider, if it has one. */ virtual std::optional<CExtPubKey> GetRootExtPubKey() const = 0; + + /** Make a deep copy of this PubkeyProvider */ + virtual std::unique_ptr<PubkeyProvider> Clone() const = 0; }; class OriginPubkeyProvider final : public PubkeyProvider @@ -281,6 +284,10 @@ public: { return m_provider->GetRootExtPubKey(); } + std::unique_ptr<PubkeyProvider> Clone() const override + { + return std::make_unique<OriginPubkeyProvider>(m_expr_index, m_origin, m_provider->Clone(), m_apostrophe); + } }; /** An object representing a parsed constant public key in a descriptor. */ @@ -334,6 +341,10 @@ public: { return std::nullopt; } + std::unique_ptr<PubkeyProvider> Clone() const override + { + return std::make_unique<ConstPubkeyProvider>(m_expr_index, m_pubkey, m_xonly); + } }; enum class DeriveType { @@ -557,6 +568,10 @@ public: { return m_root_extkey; } + std::unique_ptr<PubkeyProvider> Clone() const override + { + return std::make_unique<BIP32PubkeyProvider>(m_expr_index, m_root_extkey, m_path, m_derive, m_apostrophe); + } }; /** Base class for all Descriptor implementations. */ @@ -772,6 +787,8 @@ public: arg->GetPubKeys(pubkeys, ext_pubs); } } + + virtual std::unique_ptr<DescriptorImpl> Clone() const = 0; }; /** A parsed addr(A) descriptor. */ @@ -793,6 +810,10 @@ public: bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; } std::optional<int64_t> ScriptSize() const override { return GetScriptForDestination(m_destination).size(); } + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<AddressDescriptor>(m_destination); + } }; /** A parsed raw(H) descriptor. */ @@ -816,6 +837,11 @@ public: bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; } std::optional<int64_t> ScriptSize() const override { return m_script.size(); } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<RawDescriptor>(m_script); + } }; /** A parsed pk(P) descriptor. */ @@ -851,6 +877,11 @@ public: } std::optional<int64_t> MaxSatisfactionElems() const override { return 1; } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<PKDescriptor>(m_pubkey_args.at(0)->Clone(), m_xonly); + } }; /** A parsed pkh(P) descriptor. */ @@ -880,6 +911,11 @@ public: } std::optional<int64_t> MaxSatisfactionElems() const override { return 2; } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<PKHDescriptor>(m_pubkey_args.at(0)->Clone()); + } }; /** A parsed wpkh(P) descriptor. */ @@ -909,6 +945,11 @@ public: } std::optional<int64_t> MaxSatisfactionElems() const override { return 2; } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<WPKHDescriptor>(m_pubkey_args.at(0)->Clone()); + } }; /** A parsed combo(P) descriptor. */ @@ -933,6 +974,10 @@ protected: public: ComboDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "combo") {} bool IsSingleType() const final { return false; } + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<ComboDescriptor>(m_pubkey_args.at(0)->Clone()); + } }; /** A parsed multi(...) or sortedmulti(...) descriptor */ @@ -971,6 +1016,14 @@ public: } std::optional<int64_t> MaxSatisfactionElems() const override { return 1 + m_threshold; } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + std::vector<std::unique_ptr<PubkeyProvider>> providers; + providers.reserve(m_pubkey_args.size()); + std::transform(m_pubkey_args.begin(), m_pubkey_args.end(), providers.begin(), [](const std::unique_ptr<PubkeyProvider>& p) { return p->Clone(); }); + return std::make_unique<MultisigDescriptor>(m_threshold, std::move(providers), m_sorted); + } }; /** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */ @@ -1007,6 +1060,16 @@ public: } std::optional<int64_t> MaxSatisfactionElems() const override { return m_pubkey_args.size(); } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + std::vector<std::unique_ptr<PubkeyProvider>> providers; + providers.reserve(m_pubkey_args.size()); + for (const auto& arg : m_pubkey_args) { + providers.push_back(arg->Clone()); + } + return std::make_unique<MultiADescriptor>(m_threshold, std::move(providers), m_sorted); + } }; /** A parsed sh(...) descriptor. */ @@ -1052,6 +1115,11 @@ public: if (const auto sub_elems = m_subdescriptor_args[0]->MaxSatisfactionElems()) return 1 + *sub_elems; return {}; } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<SHDescriptor>(m_subdescriptor_args.at(0)->Clone()); + } }; /** A parsed wsh(...) descriptor. */ @@ -1088,6 +1156,11 @@ public: if (const auto sub_elems = m_subdescriptor_args[0]->MaxSatisfactionElems()) return 1 + *sub_elems; return {}; } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<WSHDescriptor>(m_subdescriptor_args.at(0)->Clone()); + } }; /** A parsed tr(...) descriptor. */ @@ -1153,6 +1226,14 @@ public: // FIXME: See above, we assume keypath spend. return 1; } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + std::vector<std::unique_ptr<DescriptorImpl>> subdescs; + subdescs.reserve(m_subdescriptor_args.size()); + std::transform(m_subdescriptor_args.begin(), m_subdescriptor_args.end(), subdescs.begin(), [](const std::unique_ptr<DescriptorImpl>& d) { return d->Clone(); }); + return std::make_unique<TRDescriptor>(m_pubkey_args.at(0)->Clone(), std::move(subdescs), m_depths); + } }; /* We instantiate Miniscript here with a simple integer as key type. @@ -1271,6 +1352,16 @@ public: std::optional<int64_t> MaxSatisfactionElems() const override { return m_node->GetStackSize(); } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + std::vector<std::unique_ptr<PubkeyProvider>> providers; + providers.reserve(m_pubkey_args.size()); + for (const auto& arg : m_pubkey_args) { + providers.push_back(arg->Clone()); + } + return std::make_unique<MiniscriptDescriptor>(std::move(providers), miniscript::MakeNodeRef<uint32_t>(*m_node)); + } }; /** A parsed rawtr(...) descriptor. */ @@ -1301,6 +1392,11 @@ public: // See above, we assume keypath spend. return 1; } + + std::unique_ptr<DescriptorImpl> Clone() const override + { + return std::make_unique<RawTRDescriptor>(m_pubkey_args.at(0)->Clone()); + } }; //////////////////////////////////////////////////////////////////////////// @@ -1315,50 +1411,110 @@ enum class ParseScriptContext { P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf) }; +std::optional<uint32_t> ParseKeyPathNum(Span<const char> elem, bool& apostrophe, std::string& error) +{ + bool hardened = false; + if (elem.size() > 0) { + const char last = elem[elem.size() - 1]; + if (last == '\'' || last == 'h') { + elem = elem.first(elem.size() - 1); + hardened = true; + apostrophe = last == '\''; + } + } + uint32_t p; + if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p)) { + error = strprintf("Key path value '%s' is not a valid uint32", std::string(elem.begin(), elem.end())); + return std::nullopt; + } else if (p > 0x7FFFFFFFUL) { + error = strprintf("Key path value %u is out of range", p); + return std::nullopt; + } + + return std::make_optional<uint32_t>(p | (((uint32_t)hardened) << 31)); +} + /** - * Parse a key path, being passed a split list of elements (the first element is ignored). + * Parse a key path, being passed a split list of elements (the first element is ignored because it is always the key). * * @param[in] split BIP32 path string, using either ' or h for hardened derivation - * @param[out] out the key path + * @param[out] out Vector of parsed key paths * @param[out] apostrophe only updated if hardened derivation is found * @param[out] error parsing error message + * @param[in] allow_multipath Allows the parsed path to use the multipath specifier * @returns false if parsing failed **/ -[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, bool& apostrophe, std::string& error) +[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath) { + KeyPath path; + std::optional<size_t> multipath_segment_index; + std::vector<uint32_t> multipath_values; + std::unordered_set<uint32_t> seen_multipath; + for (size_t i = 1; i < split.size(); ++i) { - Span<const char> elem = split[i]; - bool hardened = false; - if (elem.size() > 0) { - const char last = elem[elem.size() - 1]; - if (last == '\'' || last == 'h') { - elem = elem.first(elem.size() - 1); - hardened = true; - apostrophe = last == '\''; + const Span<const char>& elem = split[i]; + + // Check if element contain multipath specifier + if (!elem.empty() && elem.front() == '<' && elem.back() == '>') { + if (!allow_multipath) { + error = strprintf("Key path value '%s' specifies multipath in a section where multipath is not allowed", std::string(elem.begin(), elem.end())); + return false; + } + if (multipath_segment_index) { + error = "Multiple multipath key path specifiers found"; + return false; + } + + // Parse each possible value + std::vector<Span<const char>> nums = Split(Span(elem.begin()+1, elem.end()-1), ";"); + if (nums.size() < 2) { + error = "Multipath key path specifiers must have at least two items"; + return false; } + + for (const auto& num : nums) { + const auto& op_num = ParseKeyPathNum(num, apostrophe, error); + if (!op_num) return false; + auto [_, inserted] = seen_multipath.insert(*op_num); + if (!inserted) { + error = strprintf("Duplicated key path value %u in multipath specifier", *op_num); + return false; + } + multipath_values.emplace_back(*op_num); + } + + path.emplace_back(); // Placeholder for multipath segment + multipath_segment_index = path.size()-1; + } else { + const auto& op_num = ParseKeyPathNum(elem, apostrophe, error); + if (!op_num) return false; + path.emplace_back(*op_num); } - uint32_t p; - if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p)) { - error = strprintf("Key path value '%s' is not a valid uint32", std::string(elem.begin(), elem.end())); - return false; - } else if (p > 0x7FFFFFFFUL) { - error = strprintf("Key path value %u is out of range", p); - return false; + } + + if (!multipath_segment_index) { + out.emplace_back(std::move(path)); + } else { + // Replace the multipath placeholder with each value while generating paths + for (size_t i = 0; i < multipath_values.size(); i++) { + KeyPath branch_path = path; + branch_path[*multipath_segment_index] = multipath_values[i]; + out.emplace_back(std::move(branch_path)); } - out.push_back(p | (((uint32_t)hardened) << 31)); } return true; } /** Parse a public key that excludes origin information. */ -std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, bool& apostrophe, std::string& error) +std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, bool& apostrophe, std::string& error) { + std::vector<std::unique_ptr<PubkeyProvider>> ret; bool permit_uncompressed = ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH; auto split = Split(sp, '/'); std::string str(split[0].begin(), split[0].end()); if (str.size() == 0) { error = "No key provided"; - return nullptr; + return {}; } if (split.size() == 1) { if (IsHex(str)) { @@ -1366,35 +1522,38 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S CPubKey pubkey(data); if (pubkey.IsValid() && !pubkey.IsValidNonHybrid()) { error = "Hybrid public keys are not allowed"; - return nullptr; + return {}; } if (pubkey.IsFullyValid()) { if (permit_uncompressed || pubkey.IsCompressed()) { - return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, false); + ret.emplace_back(std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, false)); + return ret; } else { error = "Uncompressed keys are not allowed"; - return nullptr; + return {}; } } else if (data.size() == 32 && ctx == ParseScriptContext::P2TR) { unsigned char fullkey[33] = {0x02}; std::copy(data.begin(), data.end(), fullkey + 1); pubkey.Set(std::begin(fullkey), std::end(fullkey)); if (pubkey.IsFullyValid()) { - return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, true); + ret.emplace_back(std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, true)); + return ret; } } error = strprintf("Pubkey '%s' is invalid", str); - return nullptr; + return {}; } CKey key = DecodeSecret(str); if (key.IsValid()) { if (permit_uncompressed || key.IsCompressed()) { CPubKey pubkey = key.GetPubKey(); out.keys.emplace(pubkey.GetID(), key); - return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, ctx == ParseScriptContext::P2TR); + ret.emplace_back(std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, ctx == ParseScriptContext::P2TR)); + return ret; } else { error = "Uncompressed keys are not allowed"; - return nullptr; + return {}; } } } @@ -1402,9 +1561,9 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S CExtPubKey extpubkey = DecodeExtPubKey(str); if (!extkey.key.IsValid() && !extpubkey.pubkey.IsValid()) { error = strprintf("key '%s' is not valid", str); - return nullptr; + return {}; } - KeyPath path; + std::vector<KeyPath> paths; DeriveType type = DeriveType::NO; if (std::ranges::equal(split.back(), Span{"*"}.first(1))) { split.pop_back(); @@ -1414,21 +1573,25 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S split.pop_back(); type = DeriveType::HARDENED; } - if (!ParseKeyPath(split, path, apostrophe, error)) return nullptr; + if (!ParseKeyPath(split, paths, apostrophe, error, /*allow_multipath=*/true)) return {}; if (extkey.key.IsValid()) { extpubkey = extkey.Neuter(); out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key); } - return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe); + for (auto& path : paths) { + ret.emplace_back(std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe)); + } + return ret; } /** Parse a public key including origin information (if enabled). */ -std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) +std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) { + std::vector<std::unique_ptr<PubkeyProvider>> ret; auto origin_split = Split(sp, ']'); if (origin_split.size() > 2) { error = "Multiple ']' characters found for a single pubkey"; - return nullptr; + return {}; } // This is set if either the origin or path suffix contains a hardened derivation. bool apostrophe = false; @@ -1438,27 +1601,33 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c if (origin_split[0].empty() || origin_split[0][0] != '[') { error = strprintf("Key origin start '[ character expected but not found, got '%c' instead", origin_split[0].empty() ? /** empty, implies split char */ ']' : origin_split[0][0]); - return nullptr; + return {}; } auto slash_split = Split(origin_split[0].subspan(1), '/'); if (slash_split[0].size() != 8) { error = strprintf("Fingerprint is not 4 bytes (%u characters instead of 8 characters)", slash_split[0].size()); - return nullptr; + return {}; } std::string fpr_hex = std::string(slash_split[0].begin(), slash_split[0].end()); if (!IsHex(fpr_hex)) { error = strprintf("Fingerprint '%s' is not hex", fpr_hex); - return nullptr; + return {}; } auto fpr_bytes = ParseHex(fpr_hex); KeyOriginInfo info; static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes"); assert(fpr_bytes.size() == 4); std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint); - if (!ParseKeyPath(slash_split, info.path, apostrophe, error)) return nullptr; - auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, apostrophe, error); - if (!provider) return nullptr; - return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider), apostrophe); + std::vector<KeyPath> path; + if (!ParseKeyPath(slash_split, path, apostrophe, error, /*allow_multipath=*/false)) return {}; + info.path = path.at(0); + auto providers = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, apostrophe, error); + if (providers.empty()) return {}; + ret.reserve(providers.size()); + for (auto& prov : providers) { + ret.emplace_back(std::make_unique<OriginPubkeyProvider>(key_exp_index, info, std::move(prov), apostrophe)); + } + return ret; } std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext ctx, const SigningProvider& provider) @@ -1500,8 +1669,8 @@ struct KeyParser { FlatSigningProvider* m_out; //! Must not be nullptr if parsing from Script. const SigningProvider* m_in; - //! List of keys contained in the Miniscript. - mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys; + //! List of multipath expanded keys contained in the Miniscript. + mutable std::vector<std::vector<std::unique_ptr<PubkeyProvider>>> m_keys; //! Used to detect key parsing errors within a Miniscript. mutable std::string m_key_parsing_error; //! The script context we're operating within (Tapscript or P2WSH). @@ -1514,7 +1683,7 @@ struct KeyParser { : m_out(out), m_in(in), m_script_ctx(ctx), m_offset(offset) {} bool KeyCompare(const Key& a, const Key& b) const { - return *m_keys.at(a) < *m_keys.at(b); + return *m_keys.at(a).at(0) < *m_keys.at(b).at(0); } ParseScriptContext ParseContext() const { @@ -1530,14 +1699,14 @@ struct KeyParser { assert(m_out); Key key = m_keys.size(); auto pk = ParsePubkey(m_offset + key, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error); - if (!pk) return {}; - m_keys.push_back(std::move(pk)); + if (pk.empty()) return {}; + m_keys.emplace_back(std::move(pk)); return key; } std::optional<std::string> ToString(const Key& key) const { - return m_keys.at(key)->ToString(); + return m_keys.at(key).at(0)->ToString(); } template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const @@ -1548,13 +1717,15 @@ struct KeyParser { XOnlyPubKey pubkey; std::copy(begin, end, pubkey.begin()); if (auto pubkey_provider = InferPubkey(pubkey.GetEvenCorrespondingCPubKey(), ParseContext(), *m_in)) { - m_keys.push_back(std::move(pubkey_provider)); + m_keys.emplace_back(); + m_keys.back().push_back(std::move(pubkey_provider)); return key; } } else if (!miniscript::IsTapscript(m_script_ctx)) { CPubKey pubkey(begin, end); if (auto pubkey_provider = InferPubkey(pubkey, ParseContext(), *m_in)) { - m_keys.push_back(std::move(pubkey_provider)); + m_keys.emplace_back(); + m_keys.back().push_back(std::move(pubkey_provider)); return key; } } @@ -1572,7 +1743,8 @@ struct KeyParser { if (m_in->GetPubKey(keyid, pubkey)) { if (auto pubkey_provider = InferPubkey(pubkey, ParseContext(), *m_in)) { Key key = m_keys.size(); - m_keys.push_back(std::move(pubkey_provider)); + m_keys.emplace_back(); + m_keys.back().push_back(std::move(pubkey_provider)); return key; } } @@ -1586,44 +1758,54 @@ struct KeyParser { /** Parse a script in a particular context. */ // NOLINTNEXTLINE(misc-no-recursion) -std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) +std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index, Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) { using namespace script; + std::vector<std::unique_ptr<DescriptorImpl>> ret; auto expr = Expr(sp); if (Func("pk", expr)) { - auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error); - if (!pubkey) { + auto pubkeys = ParsePubkey(key_exp_index, expr, ctx, out, error); + if (pubkeys.empty()) { error = strprintf("pk(): %s", error); - return nullptr; + return {}; } ++key_exp_index; - return std::make_unique<PKDescriptor>(std::move(pubkey), ctx == ParseScriptContext::P2TR); + for (auto& pubkey : pubkeys) { + ret.emplace_back(std::make_unique<PKDescriptor>(std::move(pubkey), ctx == ParseScriptContext::P2TR)); + } + return ret; } if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && Func("pkh", expr)) { - auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error); - if (!pubkey) { + auto pubkeys = ParsePubkey(key_exp_index, expr, ctx, out, error); + if (pubkeys.empty()) { error = strprintf("pkh(): %s", error); - return nullptr; + return {}; } ++key_exp_index; - return std::make_unique<PKHDescriptor>(std::move(pubkey)); + for (auto& pubkey : pubkeys) { + ret.emplace_back(std::make_unique<PKHDescriptor>(std::move(pubkey))); + } + return ret; } else if (ctx != ParseScriptContext::P2TR && Func("pkh", expr)) { // Under Taproot, always the Miniscript parser deal with it. error = "Can only have pkh at top level, in sh(), wsh(), or in tr()"; - return nullptr; + return {}; } if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { - auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error); - if (!pubkey) { + auto pubkeys = ParsePubkey(key_exp_index, expr, ctx, out, error); + if (pubkeys.empty()) { error = strprintf("combo(): %s", error); - return nullptr; + return {}; } ++key_exp_index; - return std::make_unique<ComboDescriptor>(std::move(pubkey)); + for (auto& pubkey : pubkeys) { + ret.emplace_back(std::make_unique<ComboDescriptor>(std::move(pubkey))); + } + return ret; } else if (Func("combo", expr)) { error = "Can only have combo() at top level"; - return nullptr; + return {}; } const bool multi = Func("multi", expr); const bool sortedmulti = !multi && Func("sortedmulti", expr); @@ -1633,118 +1815,157 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const (ctx == ParseScriptContext::P2TR && (multi_a || sortedmulti_a))) { auto threshold = Expr(expr); uint32_t thres; - std::vector<std::unique_ptr<PubkeyProvider>> providers; + std::vector<std::vector<std::unique_ptr<PubkeyProvider>>> providers; // List of multipath expanded pubkeys if (!ParseUInt32(std::string(threshold.begin(), threshold.end()), &thres)) { error = strprintf("Multi threshold '%s' is not valid", std::string(threshold.begin(), threshold.end())); - return nullptr; + return {}; } size_t script_size = 0; + size_t max_providers_len = 0; while (expr.size()) { if (!Const(",", expr)) { error = strprintf("Multi: expected ',', got '%c'", expr[0]); - return nullptr; + return {}; } auto arg = Expr(expr); - auto pk = ParsePubkey(key_exp_index, arg, ctx, out, error); - if (!pk) { + auto pks = ParsePubkey(key_exp_index, arg, ctx, out, error); + if (pks.empty()) { error = strprintf("Multi: %s", error); - return nullptr; + return {}; } - script_size += pk->GetSize() + 1; - providers.emplace_back(std::move(pk)); + script_size += pks.at(0)->GetSize() + 1; + max_providers_len = std::max(max_providers_len, pks.size()); + providers.emplace_back(std::move(pks)); key_exp_index++; } 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; + return {}; } 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; + return {}; } else if (thres < 1) { error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres); - return nullptr; + return {}; } else if (thres > providers.size()) { error = strprintf("Multisig threshold cannot be larger than the number of keys; threshold is %d but only %u keys specified", thres, providers.size()); - return nullptr; + return {}; } if (ctx == ParseScriptContext::TOP) { if (providers.size() > 3) { error = strprintf("Cannot have %u pubkeys in bare multisig; only at most 3 pubkeys", providers.size()); - return nullptr; + return {}; } } if (ctx == ParseScriptContext::P2SH) { // This limits the maximum number of compressed pubkeys to 15. if (script_size + 3 > MAX_SCRIPT_ELEMENT_SIZE) { error = strprintf("P2SH script is too large, %d bytes is larger than %d bytes", script_size + 3, MAX_SCRIPT_ELEMENT_SIZE); - return nullptr; + return {}; } } - 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); + + // Make sure all vecs are of the same length, or exactly length 1 + // For length 1 vectors, clone key providers until vector is the same length + for (auto& vec : providers) { + if (vec.size() == 1) { + for (size_t i = 1; i < max_providers_len; ++i) { + vec.emplace_back(vec.at(0)->Clone()); + } + } else if (vec.size() != max_providers_len) { + error = strprintf("multi(): Multipath derivation paths have mismatched lengths"); + return {}; + } } + + // Build the final descriptors vector + for (size_t i = 0; i < max_providers_len; ++i) { + // Build final pubkeys vectors by retrieving the i'th subscript for each vector in subscripts + std::vector<std::unique_ptr<PubkeyProvider>> pubs; + pubs.reserve(providers.size()); + for (auto& pub : providers) { + pubs.emplace_back(std::move(pub.at(i))); + } + if (multi || sortedmulti) { + ret.emplace_back(std::make_unique<MultisigDescriptor>(thres, std::move(pubs), sortedmulti)); + } else { + ret.emplace_back(std::make_unique<MultiADescriptor>(thres, std::move(pubs), sortedmulti_a)); + } + } + return ret; } else if (multi || sortedmulti) { error = "Can only have multi/sortedmulti at top level, in sh(), or in wsh()"; - return nullptr; + return {}; } else if (multi_a || sortedmulti_a) { error = "Can only have multi_a/sortedmulti_a inside tr()"; - return nullptr; + return {}; } if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) { - auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error); - if (!pubkey) { + auto pubkeys = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error); + if (pubkeys.empty()) { error = strprintf("wpkh(): %s", error); - return nullptr; + return {}; } key_exp_index++; - return std::make_unique<WPKHDescriptor>(std::move(pubkey)); + for (auto& pubkey : pubkeys) { + ret.emplace_back(std::make_unique<WPKHDescriptor>(std::move(pubkey))); + } + return ret; } else if (Func("wpkh", expr)) { error = "Can only have wpkh() at top level or inside sh()"; - return nullptr; + return {}; } if (ctx == ParseScriptContext::TOP && Func("sh", expr)) { - auto desc = ParseScript(key_exp_index, expr, ParseScriptContext::P2SH, out, error); - if (!desc || expr.size()) return nullptr; - return std::make_unique<SHDescriptor>(std::move(desc)); + auto descs = ParseScript(key_exp_index, expr, ParseScriptContext::P2SH, out, error); + if (descs.empty() || expr.size()) return {}; + std::vector<std::unique_ptr<DescriptorImpl>> ret; + ret.reserve(descs.size()); + for (auto& desc : descs) { + ret.push_back(std::make_unique<SHDescriptor>(std::move(desc))); + } + return ret; } else if (Func("sh", expr)) { error = "Can only have sh() at top level"; - return nullptr; + return {}; } if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wsh", expr)) { - auto desc = ParseScript(key_exp_index, expr, ParseScriptContext::P2WSH, out, error); - if (!desc || expr.size()) return nullptr; - return std::make_unique<WSHDescriptor>(std::move(desc)); + auto descs = ParseScript(key_exp_index, expr, ParseScriptContext::P2WSH, out, error); + if (descs.empty() || expr.size()) return {}; + for (auto& desc : descs) { + ret.emplace_back(std::make_unique<WSHDescriptor>(std::move(desc))); + } + return ret; } else if (Func("wsh", expr)) { error = "Can only have wsh() at top level or inside sh()"; - return nullptr; + return {}; } if (ctx == ParseScriptContext::TOP && Func("addr", expr)) { CTxDestination dest = DecodeDestination(std::string(expr.begin(), expr.end())); if (!IsValidDestination(dest)) { error = "Address is not valid"; - return nullptr; + return {}; } - return std::make_unique<AddressDescriptor>(std::move(dest)); + ret.emplace_back(std::make_unique<AddressDescriptor>(std::move(dest))); + return ret; } else if (Func("addr", expr)) { error = "Can only have addr() at top level"; - return nullptr; + return {}; } if (ctx == ParseScriptContext::TOP && Func("tr", expr)) { auto arg = Expr(expr); - auto internal_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error); - if (!internal_key) { + auto internal_keys = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error); + if (internal_keys.empty()) { error = strprintf("tr(): %s", error); - return nullptr; + return {}; } + size_t max_providers_len = internal_keys.size(); ++key_exp_index; - std::vector<std::unique_ptr<DescriptorImpl>> subscripts; //!< list of script subexpressions + std::vector<std::vector<std::unique_ptr<DescriptorImpl>>> subscripts; //!< list of multipath expanded script subexpressions std::vector<int> depths; //!< depth in the tree of each subexpression (same length subscripts) if (expr.size()) { if (!Const(",", expr)) { error = strprintf("tr: expected ',', got '%c'", expr[0]); - return nullptr; + return {}; } /** The path from the top of the tree to what we're currently processing. * branches[i] == false: left branch in the i'th step from the top; true: right branch. @@ -1758,19 +1979,20 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const branches.push_back(false); // new left branch if (branches.size() > TAPROOT_CONTROL_MAX_NODE_COUNT) { error = strprintf("tr() supports at most %i nesting levels", TAPROOT_CONTROL_MAX_NODE_COUNT); - return nullptr; + return {}; } } // Process the actual script expression. auto sarg = Expr(expr); subscripts.emplace_back(ParseScript(key_exp_index, sarg, ParseScriptContext::P2TR, out, error)); - if (!subscripts.back()) return nullptr; + if (subscripts.back().empty()) return {}; + max_providers_len = std::max(max_providers_len, subscripts.back().size()); depths.push_back(branches.size()); // Process closing braces; one is expected for every right branch we were in. while (branches.size() && branches.back()) { if (!Const("}", expr)) { error = strprintf("tr(): expected '}' after script expression"); - return nullptr; + return {}; } branches.pop_back(); // move up one level after encountering '}' } @@ -1778,7 +2000,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const if (branches.size() && !branches.back()) { if (!Const(",", expr)) { error = strprintf("tr(): expected ',' after script expression"); - return nullptr; + return {}; } branches.back() = true; // And now we're in a right branch. } @@ -1786,40 +2008,82 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const // After we've explored a whole tree, we must be at the end of the expression. if (expr.size()) { error = strprintf("tr(): expected ')' after script expression"); - return nullptr; + return {}; } } assert(TaprootBuilder::ValidDepths(depths)); - return std::make_unique<TRDescriptor>(std::move(internal_key), std::move(subscripts), std::move(depths)); + + // Make sure all vecs are of the same length, or exactly length 1 + // For length 1 vectors, clone subdescs until vector is the same length + for (auto& vec : subscripts) { + if (vec.size() == 1) { + for (size_t i = 1; i < max_providers_len; ++i) { + vec.emplace_back(vec.at(0)->Clone()); + } + } else if (vec.size() != max_providers_len) { + error = strprintf("tr(): Multipath subscripts have mismatched lengths"); + return {}; + } + } + + if (internal_keys.size() > 1 && internal_keys.size() != max_providers_len) { + error = strprintf("tr(): Multipath internal key mismatches multipath subscripts lengths"); + return {}; + } + + while (internal_keys.size() < max_providers_len) { + internal_keys.emplace_back(internal_keys.at(0)->Clone()); + } + + // Build the final descriptors vector + for (size_t i = 0; i < max_providers_len; ++i) { + // Build final subscripts vectors by retrieving the i'th subscript for each vector in subscripts + std::vector<std::unique_ptr<DescriptorImpl>> this_subs; + this_subs.reserve(subscripts.size()); + for (auto& subs : subscripts) { + this_subs.emplace_back(std::move(subs.at(i))); + } + ret.emplace_back(std::make_unique<TRDescriptor>(std::move(internal_keys.at(i)), std::move(this_subs), depths)); + } + return ret; + + } else if (Func("tr", expr)) { error = "Can only have tr at top level"; - return nullptr; + return {}; } if (ctx == ParseScriptContext::TOP && Func("rawtr", expr)) { auto arg = Expr(expr); if (expr.size()) { error = strprintf("rawtr(): only one key expected."); - return nullptr; + return {}; + } + auto output_keys = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error); + if (output_keys.empty()) { + error = strprintf("rawtr(): %s", error); + return {}; } - auto output_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error); - if (!output_key) return nullptr; ++key_exp_index; - return std::make_unique<RawTRDescriptor>(std::move(output_key)); + for (auto& pubkey : output_keys) { + ret.emplace_back(std::make_unique<RawTRDescriptor>(std::move(pubkey))); + } + return ret; } else if (Func("rawtr", expr)) { error = "Can only have rawtr at top level"; - return nullptr; + return {}; } if (ctx == ParseScriptContext::TOP && Func("raw", expr)) { std::string str(expr.begin(), expr.end()); if (!IsHex(str)) { error = "Raw script is not hex"; - return nullptr; + return {}; } auto bytes = ParseHex(str); - return std::make_unique<RawDescriptor>(CScript(bytes.begin(), bytes.end())); + ret.emplace_back(std::make_unique<RawDescriptor>(CScript(bytes.begin(), bytes.end()))); + return ret; } else if (Func("raw", expr)) { error = "Can only have raw() at top level"; - return nullptr; + return {}; } // Process miniscript expressions. { @@ -1828,12 +2092,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser); if (parser.m_key_parsing_error != "") { error = std::move(parser.m_key_parsing_error); - return nullptr; + return {}; } if (node) { if (ctx != ParseScriptContext::P2WSH && ctx != ParseScriptContext::P2TR) { error = "Miniscript expressions can only be used in wsh or tr."; - return nullptr; + return {}; } if (!node->IsSane() || node->IsNotSatisfiable()) { // Try to find the first insane sub for better error reporting. @@ -1858,24 +2122,52 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const } else { error += " is not satisfiable"; } - return nullptr; + return {}; } // A signature check is required for a miniscript to be sane. Therefore no sane miniscript // may have an empty list of public keys. CHECK_NONFATAL(!parser.m_keys.empty()); key_exp_index += parser.m_keys.size(); - return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node)); + // Make sure all vecs are of the same length, or exactly length 1 + // For length 1 vectors, clone subdescs until vector is the same length + size_t num_multipath = std::max_element(parser.m_keys.begin(), parser.m_keys.end(), + [](const std::vector<std::unique_ptr<PubkeyProvider>>& a, const std::vector<std::unique_ptr<PubkeyProvider>>& b) { + return a.size() < b.size(); + })->size(); + + for (auto& vec : parser.m_keys) { + if (vec.size() == 1) { + for (size_t i = 1; i < num_multipath; ++i) { + vec.emplace_back(vec.at(0)->Clone()); + } + } else if (vec.size() != num_multipath) { + error = strprintf("Miniscript: Multipath derivation paths have mismatched lengths"); + return {}; + } + } + + // Build the final descriptors vector + for (size_t i = 0; i < num_multipath; ++i) { + // Build final pubkeys vectors by retrieving the i'th subscript for each vector in subscripts + std::vector<std::unique_ptr<PubkeyProvider>> pubs; + pubs.reserve(parser.m_keys.size()); + for (auto& pub : parser.m_keys) { + pubs.emplace_back(std::move(pub.at(i))); + } + ret.emplace_back(std::make_unique<MiniscriptDescriptor>(std::move(pubs), node)); + } + return ret; } } if (ctx == ParseScriptContext::P2SH) { error = "A function is needed within P2SH"; - return nullptr; + return {}; } else if (ctx == ParseScriptContext::P2WSH) { error = "A function is needed within P2WSH"; - return nullptr; + return {}; } error = strprintf("'%s' is not a valid descriptor function", std::string(expr.begin(), expr.end())); - return nullptr; + return {}; } std::unique_ptr<DescriptorImpl> InferMultiA(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider) @@ -2013,7 +2305,12 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo KeyParser parser(/* out = */nullptr, /* in = */&provider, /* ctx = */script_ctx); auto node = miniscript::FromScript(script, parser); if (node && node->IsSane()) { - return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node)); + std::vector<std::unique_ptr<PubkeyProvider>> keys; + keys.reserve(parser.m_keys.size()); + for (auto& key : parser.m_keys) { + keys.emplace_back(std::move(key.at(0))); + } + return std::make_unique<MiniscriptDescriptor>(std::move(keys), std::move(node)); } } @@ -2068,14 +2365,21 @@ bool CheckChecksum(Span<const char>& sp, bool require_checksum, std::string& err return true; } -std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, std::string& error, bool require_checksum) +std::vector<std::unique_ptr<Descriptor>> Parse(const std::string& descriptor, FlatSigningProvider& out, std::string& error, bool require_checksum) { Span<const char> sp{descriptor}; - if (!CheckChecksum(sp, require_checksum, error)) return nullptr; + if (!CheckChecksum(sp, require_checksum, error)) return {}; uint32_t key_exp_index = 0; auto ret = ParseScript(key_exp_index, sp, ParseScriptContext::TOP, out, error); - if (sp.size() == 0 && ret) return std::unique_ptr<Descriptor>(std::move(ret)); - return nullptr; + if (sp.size() == 0 && !ret.empty()) { + std::vector<std::unique_ptr<Descriptor>> descs; + descs.reserve(ret.size()); + for (auto& r : ret) { + descs.emplace_back(std::unique_ptr<Descriptor>(std::move(r))); + } + return descs; + } + return {}; } std::string GetDescriptorChecksum(const std::string& descriptor) diff --git a/src/script/descriptor.h b/src/script/descriptor.h index e78a775330..473649a314 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -173,9 +173,9 @@ struct Descriptor { * is set, the checksum is mandatory - otherwise it is optional. * * If a parse error occurs, or the checksum is missing/invalid, or anything - * else is wrong, `nullptr` is returned. + * else is wrong, an empty vector is returned. */ -std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, std::string& error, bool require_checksum = false); +std::vector<std::unique_ptr<Descriptor>> Parse(const std::string& descriptor, FlatSigningProvider& out, std::string& error, bool require_checksum = false); /** Get the checksum for a `descriptor`. * |