From 0d640c6f02bc20e5c1be773443dd74d8806d953b Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 7 Aug 2024 16:29:03 -0400 Subject: [PATCH] descriptors: Have ParseKeypath handle multipath specifiers Multipath specifiers are derivation path indexes of the form `` used for specifying multiple derivation paths for a descriptor. Only one multipath specifier is allowed per PubkeyProvider. This is syntactic sugar which is parsed into multiple distinct descriptors. One descriptor will have all of the `i` paths, the second all of the `j` paths, the third all of the `k` paths, and so on. ParseKeypath will always return a vector of keypaths with the same size as the multipath specifier. The callers of this function are updated to deal with this case and return multiple PubkeyProviders. Their callers have also been updated to handle vectors of PubkeyProviders. Co-Authored-By: furszy --- src/script/descriptor.cpp | 109 ++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index bb4bc2656f2..ab77bb63841 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -1410,37 +1410,96 @@ enum class ParseScriptContext { P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf) }; +std::optional ParseKeyPathNum(Span 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(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>& split, KeyPath& out, bool& apostrophe, std::string& error) +[[nodiscard]] bool ParseKeyPath(const std::vector>& split, std::vector& out, bool& apostrophe, std::string& error, bool allow_multipath) { + KeyPath path; + std::optional multipath_segment_index; + std::vector multipath_values; + std::unordered_set seen_multipath; + for (size_t i = 1; i < split.size(); ++i) { - Span 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& 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> 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; } @@ -1503,7 +1562,7 @@ std::vector> ParsePubkeyInner(uint32_t key_exp_i error = strprintf("key '%s' is not valid", str); return {}; } - KeyPath path; + std::vector paths; DeriveType type = DeriveType::NO; if (split.back() == Span{"*"}.first(1)) { split.pop_back(); @@ -1513,12 +1572,14 @@ std::vector> ParsePubkeyInner(uint32_t key_exp_i split.pop_back(); type = DeriveType::HARDENED; } - if (!ParseKeyPath(split, path, apostrophe, error)) return {}; + 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); } - ret.emplace_back(std::make_unique(key_exp_index, extpubkey, std::move(path), type, apostrophe)); + for (auto& path : paths) { + ret.emplace_back(std::make_unique(key_exp_index, extpubkey, std::move(path), type, apostrophe)); + } return ret; } @@ -1556,7 +1617,9 @@ std::vector> ParsePubkey(uint32_t key_exp_index, 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 {}; + std::vector 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());