Skip to content

Commit a53924b

Browse files
committed
descriptor: Parse musig() key expressions
1 parent 9473e96 commit a53924b

File tree

1 file changed

+161
-6
lines changed

1 file changed

+161
-6
lines changed

src/script/descriptor.cpp

Lines changed: 161 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,9 +1612,10 @@ enum class ParseScriptContext {
16121612
P2WPKH, //!< Inside wpkh() (no script, pubkey only)
16131613
P2WSH, //!< Inside wsh() (script becomes v0 witness script)
16141614
P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf)
1615+
MUSIG, //!< Inside musig() (implies P2TR, cannot have nested musig())
16151616
};
16161617

1617-
std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostrophe, std::string& error)
1618+
std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostrophe, std::string& error, bool& has_hardened)
16181619
{
16191620
bool hardened = false;
16201621
if (elem.size() > 0) {
@@ -1633,6 +1634,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
16331634
error = strprintf("Key path value %u is out of range", *p);
16341635
return std::nullopt;
16351636
}
1637+
has_hardened = has_hardened || hardened;
16361638

16371639
return std::make_optional<uint32_t>(*p | (((uint32_t)hardened) << 31));
16381640
}
@@ -1647,14 +1649,15 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
16471649
* @param[in] allow_multipath Allows the parsed path to use the multipath specifier
16481650
* @returns false if parsing failed
16491651
**/
1650-
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath)
1652+
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath, bool& has_hardened)
16511653
{
16521654
KeyPath path;
16531655
struct MultipathSubstitutes {
16541656
size_t placeholder_index;
16551657
std::vector<uint32_t> values;
16561658
};
16571659
std::optional<MultipathSubstitutes> substitutes;
1660+
has_hardened = false;
16581661

16591662
for (size_t i = 1; i < split.size(); ++i) {
16601663
const std::span<const char>& elem = split[i];
@@ -1680,7 +1683,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
16801683
substitutes.emplace();
16811684
std::unordered_set<uint32_t> seen_substitutes;
16821685
for (const auto& num : nums) {
1683-
const auto& op_num = ParseKeyPathNum(num, apostrophe, error);
1686+
const auto& op_num = ParseKeyPathNum(num, apostrophe, error, has_hardened);
16841687
if (!op_num) return false;
16851688
auto [_, inserted] = seen_substitutes.insert(*op_num);
16861689
if (!inserted) {
@@ -1693,7 +1696,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
16931696
path.emplace_back(); // Placeholder for multipath segment
16941697
substitutes->placeholder_index = path.size() - 1;
16951698
} else {
1696-
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error);
1699+
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error, has_hardened);
16971700
if (!op_num) return false;
16981701
path.emplace_back(*op_num);
16991702
}
@@ -1712,6 +1715,12 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
17121715
return true;
17131716
}
17141717

1718+
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath)
1719+
{
1720+
bool dummy;
1721+
return ParseKeyPath(split, out, apostrophe, error, allow_multipath, /*has_hardened=*/dummy);
1722+
}
1723+
17151724
static DeriveType ParseDeriveType(std::vector<std::span<const char>>& split, bool& apostrophe)
17161725
{
17171726
DeriveType type = DeriveType::NO;
@@ -1802,9 +1811,154 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_i
18021811
}
18031812

18041813
/** Parse a public key including origin information (if enabled). */
1805-
std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t key_exp_index, const std::span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
1814+
// NOLINTNEXTLINE(misc-no-recursion)
1815+
std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t& key_exp_index, const std::span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
18061816
{
18071817
std::vector<std::unique_ptr<PubkeyProvider>> ret;
1818+
1819+
using namespace script;
1820+
1821+
// musig cannot be nested inside of an origin
1822+
std::span<const char> span = sp;
1823+
if (Const("musig(", span, /*skip=*/false)) {
1824+
if (ctx != ParseScriptContext::P2TR) {
1825+
error = "musig() is only allowed in tr() and rawtr()";
1826+
return {};
1827+
}
1828+
1829+
// Split the span on the end parentheses. The end parentheses must
1830+
// be included in the resulting span so that Expr is happy.
1831+
auto split = Split(sp, ')', /*include_sep=*/true);
1832+
if (split.size() > 2) {
1833+
error = "Too many ')' in musig() expression";
1834+
return {};
1835+
}
1836+
std::span<const char> expr(split.at(0).begin(), split.at(0).end());
1837+
if (!Func("musig", expr)) {
1838+
error = "Invalid musig() expression";
1839+
return {};
1840+
}
1841+
1842+
// Parse the participant pubkeys
1843+
bool any_ranged = false;
1844+
bool all_bip32 = true;
1845+
std::vector<std::vector<std::unique_ptr<PubkeyProvider>>> providers;
1846+
bool any_key_parsed = true;
1847+
size_t max_multipath_len = 0;
1848+
while (expr.size()) {
1849+
if (!any_key_parsed && !Const(",", expr)) {
1850+
error = strprintf("musig(): expected ',', got '%c'", expr[0]);
1851+
return {};
1852+
}
1853+
any_key_parsed = false;
1854+
auto arg = Expr(expr);
1855+
auto pk = ParsePubkey(key_exp_index, arg, ParseScriptContext::MUSIG, out, error);
1856+
if (pk.empty()) {
1857+
error = strprintf("musig(): %s", error);
1858+
return {};
1859+
}
1860+
1861+
any_ranged = any_ranged || pk.at(0)->IsRange();
1862+
all_bip32 = all_bip32 && pk.at(0)->IsBIP32();
1863+
1864+
max_multipath_len = std::max(max_multipath_len, pk.size());
1865+
1866+
providers.emplace_back(std::move(pk));
1867+
key_exp_index++;
1868+
}
1869+
if (any_key_parsed) {
1870+
error = "musig(): Must contain key expressions";
1871+
return {};
1872+
}
1873+
1874+
// Parse any derivation
1875+
DeriveType deriv_type = DeriveType::NO;
1876+
std::vector<KeyPath> derivation_multipaths;
1877+
if (split.size() == 2 && Const("/", split.at(1), /*skip=*/false)) {
1878+
if (!all_bip32) {
1879+
error = "musig(): derivation requires all participants to be xpubs or xprvs";
1880+
return {};
1881+
}
1882+
if (any_ranged) {
1883+
error = "musig(): Cannot have ranged participant keys if musig() also has derivation";
1884+
return {};
1885+
}
1886+
bool dummy = false;
1887+
auto deriv_split = Split(split.at(1), '/');
1888+
deriv_type = ParseDeriveType(deriv_split, dummy);
1889+
if (deriv_type == DeriveType::HARDENED) {
1890+
error = "musig(): Cannot have hardened child derivation";
1891+
return {};
1892+
}
1893+
bool has_hardened = false;
1894+
if (!ParseKeyPath(deriv_split, derivation_multipaths, dummy, error, /*allow_multipath=*/true, has_hardened)) {
1895+
error = "musig(): " + error;
1896+
return {};
1897+
}
1898+
if (has_hardened) {
1899+
error = "musig(): cannot have hardened derivation steps";
1900+
return {};
1901+
}
1902+
} else {
1903+
derivation_multipaths.emplace_back();
1904+
}
1905+
1906+
// Makes sure that all providers vectors in providers are the given length, or exactly length 1
1907+
// Length 1 vectors have the single provider cloned until it matches the given length.
1908+
const auto& clone_providers = [&providers](size_t length) -> bool {
1909+
for (auto& multipath_providers : providers) {
1910+
if (multipath_providers.size() == 1) {
1911+
for (size_t i = 1; i < length; ++i) {
1912+
multipath_providers.emplace_back(multipath_providers.at(0)->Clone());
1913+
}
1914+
} else if (multipath_providers.size() != length) {
1915+
return false;
1916+
}
1917+
}
1918+
return true;
1919+
};
1920+
1921+
// Emplace the final MuSigPubkeyProvider into ret with the pubkey providers from the specified provider vectors index
1922+
// and the path from the specified path index
1923+
const auto& emplace_final_provider = [&ret, &key_exp_index, &deriv_type, &derivation_multipaths, &providers](size_t vec_idx, size_t path_idx) -> void {
1924+
KeyPath& path = derivation_multipaths.at(path_idx);
1925+
std::vector<std::unique_ptr<PubkeyProvider>> pubs;
1926+
pubs.reserve(providers.size());
1927+
for (auto& vec : providers) {
1928+
pubs.emplace_back(std::move(vec.at(vec_idx)));
1929+
}
1930+
ret.emplace_back(std::make_unique<MuSigPubkeyProvider>(key_exp_index, std::move(pubs), path, deriv_type));
1931+
};
1932+
1933+
if (max_multipath_len > 1 && derivation_multipaths.size() > 1) {
1934+
error = "musig(): Cannot have multipath participant keys if musig() is also multipath";
1935+
return {};
1936+
} else if (max_multipath_len > 1) {
1937+
if (!clone_providers(max_multipath_len)) {
1938+
error = strprintf("musig(): Multipath derivation paths have mismatched lengths");
1939+
return {};
1940+
}
1941+
for (size_t i = 0; i < max_multipath_len; ++i) {
1942+
// Final MuSigPubkeyProvider uses participant pubkey providers at each multipath position, and the first (and only) path
1943+
emplace_final_provider(i, 0);
1944+
}
1945+
} else if (derivation_multipaths.size() > 1) {
1946+
// All key provider vectors should be length 1. Clone them until they have the same length as paths
1947+
if (!Assume(clone_providers(derivation_multipaths.size()))) {
1948+
error = "musig(): Multipath derivation path with multipath participants is disallowed"; // This error is unreachable due to earlier check
1949+
return {};
1950+
}
1951+
for (size_t i = 0; i < derivation_multipaths.size(); ++i) {
1952+
// Final MuSigPubkeyProvider uses cloned participant pubkey providers, and the multipath derivation paths
1953+
emplace_final_provider(i, i);
1954+
}
1955+
} else {
1956+
// No multipath derivation, MuSigPubkeyProvider uses the first (and only) participant pubkey providers, and the first (and only) path
1957+
emplace_final_provider(0, 0);
1958+
}
1959+
return ret;
1960+
}
1961+
18081962
auto origin_split = Split(sp, ']');
18091963
if (origin_split.size() > 2) {
18101964
error = "Multiple ']' characters found for a single pubkey";
@@ -1915,7 +2069,8 @@ struct KeyParser {
19152069
{
19162070
assert(m_out);
19172071
Key key = m_keys.size();
1918-
auto pk = ParsePubkey(m_offset + key, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error);
2072+
uint32_t exp_index = m_offset + key;
2073+
auto pk = ParsePubkey(exp_index, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error);
19192074
if (pk.empty()) return {};
19202075
m_keys.emplace_back(std::move(pk));
19212076
return key;

0 commit comments

Comments
 (0)