Skip to content

Commit 0d640c6

Browse files
achow101furszy
andcommitted
descriptors: Have ParseKeypath handle multipath specifiers
Multipath specifiers are derivation path indexes of the form `<i;j;k;...>` 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 <[email protected]>
1 parent a5f39b1 commit 0d640c6

File tree

1 file changed

+86
-23
lines changed

1 file changed

+86
-23
lines changed

src/script/descriptor.cpp

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,37 +1410,96 @@ enum class ParseScriptContext {
14101410
P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf)
14111411
};
14121412

1413+
std::optional<uint32_t> ParseKeyPathNum(Span<const char> elem, bool& apostrophe, std::string& error)
1414+
{
1415+
bool hardened = false;
1416+
if (elem.size() > 0) {
1417+
const char last = elem[elem.size() - 1];
1418+
if (last == '\'' || last == 'h') {
1419+
elem = elem.first(elem.size() - 1);
1420+
hardened = true;
1421+
apostrophe = last == '\'';
1422+
}
1423+
}
1424+
uint32_t p;
1425+
if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p)) {
1426+
error = strprintf("Key path value '%s' is not a valid uint32", std::string(elem.begin(), elem.end()));
1427+
return std::nullopt;
1428+
} else if (p > 0x7FFFFFFFUL) {
1429+
error = strprintf("Key path value %u is out of range", p);
1430+
return std::nullopt;
1431+
}
1432+
1433+
return std::make_optional<uint32_t>(p | (((uint32_t)hardened) << 31));
1434+
}
1435+
14131436
/**
1414-
* Parse a key path, being passed a split list of elements (the first element is ignored).
1437+
* Parse a key path, being passed a split list of elements (the first element is ignored because it is always the key).
14151438
*
14161439
* @param[in] split BIP32 path string, using either ' or h for hardened derivation
1417-
* @param[out] out the key path
1440+
* @param[out] out Vector of parsed key paths
14181441
* @param[out] apostrophe only updated if hardened derivation is found
14191442
* @param[out] error parsing error message
1443+
* @param[in] allow_multipath Allows the parsed path to use the multipath specifier
14201444
* @returns false if parsing failed
14211445
**/
1422-
[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, bool& apostrophe, std::string& error)
1446+
[[nodiscard]] bool ParseKeyPath(const std::vector<Span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath)
14231447
{
1448+
KeyPath path;
1449+
std::optional<size_t> multipath_segment_index;
1450+
std::vector<uint32_t> multipath_values;
1451+
std::unordered_set<uint32_t> seen_multipath;
1452+
14241453
for (size_t i = 1; i < split.size(); ++i) {
1425-
Span<const char> elem = split[i];
1426-
bool hardened = false;
1427-
if (elem.size() > 0) {
1428-
const char last = elem[elem.size() - 1];
1429-
if (last == '\'' || last == 'h') {
1430-
elem = elem.first(elem.size() - 1);
1431-
hardened = true;
1432-
apostrophe = last == '\'';
1454+
const Span<const char>& elem = split[i];
1455+
1456+
// Check if element contain multipath specifier
1457+
if (!elem.empty() && elem.front() == '<' && elem.back() == '>') {
1458+
if (!allow_multipath) {
1459+
error = strprintf("Key path value '%s' specifies multipath in a section where multipath is not allowed", std::string(elem.begin(), elem.end()));
1460+
return false;
1461+
}
1462+
if (multipath_segment_index) {
1463+
error = "Multiple multipath key path specifiers found";
1464+
return false;
1465+
}
1466+
1467+
// Parse each possible value
1468+
std::vector<Span<const char>> nums = Split(Span(elem.begin()+1, elem.end()-1), ";");
1469+
if (nums.size() < 2) {
1470+
error = "Multipath key path specifiers must have at least two items";
1471+
return false;
1472+
}
1473+
1474+
for (const auto& num : nums) {
1475+
const auto& op_num = ParseKeyPathNum(num, apostrophe, error);
1476+
if (!op_num) return false;
1477+
auto [_, inserted] = seen_multipath.insert(*op_num);
1478+
if (!inserted) {
1479+
error = strprintf("Duplicated key path value %u in multipath specifier", *op_num);
1480+
return false;
1481+
}
1482+
multipath_values.emplace_back(*op_num);
14331483
}
1484+
1485+
path.emplace_back(); // Placeholder for multipath segment
1486+
multipath_segment_index = path.size()-1;
1487+
} else {
1488+
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error);
1489+
if (!op_num) return false;
1490+
path.emplace_back(*op_num);
14341491
}
1435-
uint32_t p;
1436-
if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p)) {
1437-
error = strprintf("Key path value '%s' is not a valid uint32", std::string(elem.begin(), elem.end()));
1438-
return false;
1439-
} else if (p > 0x7FFFFFFFUL) {
1440-
error = strprintf("Key path value %u is out of range", p);
1441-
return false;
1492+
}
1493+
1494+
if (!multipath_segment_index) {
1495+
out.emplace_back(std::move(path));
1496+
} else {
1497+
// Replace the multipath placeholder with each value while generating paths
1498+
for (size_t i = 0; i < multipath_values.size(); i++) {
1499+
KeyPath branch_path = path;
1500+
branch_path[*multipath_segment_index] = multipath_values[i];
1501+
out.emplace_back(std::move(branch_path));
14421502
}
1443-
out.push_back(p | (((uint32_t)hardened) << 31));
14441503
}
14451504
return true;
14461505
}
@@ -1503,7 +1562,7 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_i
15031562
error = strprintf("key '%s' is not valid", str);
15041563
return {};
15051564
}
1506-
KeyPath path;
1565+
std::vector<KeyPath> paths;
15071566
DeriveType type = DeriveType::NO;
15081567
if (split.back() == Span{"*"}.first(1)) {
15091568
split.pop_back();
@@ -1513,12 +1572,14 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_i
15131572
split.pop_back();
15141573
type = DeriveType::HARDENED;
15151574
}
1516-
if (!ParseKeyPath(split, path, apostrophe, error)) return {};
1575+
if (!ParseKeyPath(split, paths, apostrophe, error, /*allow_multipath=*/true)) return {};
15171576
if (extkey.key.IsValid()) {
15181577
extpubkey = extkey.Neuter();
15191578
out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key);
15201579
}
1521-
ret.emplace_back(std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe));
1580+
for (auto& path : paths) {
1581+
ret.emplace_back(std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type, apostrophe));
1582+
}
15221583
return ret;
15231584
}
15241585

@@ -1556,7 +1617,9 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t key_exp_index,
15561617
static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes");
15571618
assert(fpr_bytes.size() == 4);
15581619
std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint);
1559-
if (!ParseKeyPath(slash_split, info.path, apostrophe, error)) return {};
1620+
std::vector<KeyPath> path;
1621+
if (!ParseKeyPath(slash_split, path, apostrophe, error, /*allow_multipath=*/false)) return {};
1622+
info.path = path.at(0);
15601623
auto providers = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, apostrophe, error);
15611624
if (providers.empty()) return {};
15621625
ret.reserve(providers.size());

0 commit comments

Comments
 (0)