mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
descriptor: Parse musig() key expressions
This commit is contained in:
parent
b70531cac9
commit
46f1a8257c
1 changed files with 157 additions and 7 deletions
|
@ -643,7 +643,7 @@ public:
|
|||
extpub.nDepth = 0;
|
||||
std::memset(extpub.vchFingerprint, 0, 4);
|
||||
extpub.nChild = 0;
|
||||
extpub.chaincode.FromHex("6589e367712c6200e367717145cb322d76576bc3248959c474f9a602ca878086");
|
||||
extpub.chaincode = uint256::FromHex("6589e367712c6200e367717145cb322d76576bc3248959c474f9a602ca878086").value();
|
||||
extpub.pubkey = m_aggregate_pubkey.value();
|
||||
|
||||
m_aggregate_provider = std::make_unique<BIP32PubkeyProvider>(m_expr_index, extpub, m_path, m_derive, /*apostrophe=*/false);
|
||||
|
@ -1630,14 +1630,19 @@ enum class ParseScriptContext {
|
|||
P2WPKH, //!< Inside wpkh() (no script, pubkey only)
|
||||
P2WSH, //!< Inside wsh() (script becomes v0 witness script)
|
||||
P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf)
|
||||
MUSIG, //!< Inside musig() (implies P2TR, cannot have nested musig())
|
||||
};
|
||||
|
||||
std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostrophe, std::string& error)
|
||||
std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostrophe, std::string& error, bool allow_hardened)
|
||||
{
|
||||
bool hardened = false;
|
||||
if (elem.size() > 0) {
|
||||
const char last = elem[elem.size() - 1];
|
||||
if (last == '\'' || last == 'h') {
|
||||
if (!allow_hardened) {
|
||||
error = "cannot have hardened derivation steps";
|
||||
return std::nullopt;
|
||||
}
|
||||
elem = elem.first(elem.size() - 1);
|
||||
hardened = true;
|
||||
apostrophe = last == '\'';
|
||||
|
@ -1665,7 +1670,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
|||
* @param[in] allow_multipath Allows the parsed path to use the multipath specifier
|
||||
* @returns false if parsing failed
|
||||
**/
|
||||
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath)
|
||||
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath, bool allow_hardened = true)
|
||||
{
|
||||
KeyPath path;
|
||||
struct MultipathSubstitutes {
|
||||
|
@ -1698,7 +1703,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
|||
substitutes.emplace();
|
||||
std::unordered_set<uint32_t> seen_substitutes;
|
||||
for (const auto& num : nums) {
|
||||
const auto& op_num = ParseKeyPathNum(num, apostrophe, error);
|
||||
const auto& op_num = ParseKeyPathNum(num, apostrophe, error, allow_hardened);
|
||||
if (!op_num) return false;
|
||||
auto [_, inserted] = seen_substitutes.insert(*op_num);
|
||||
if (!inserted) {
|
||||
|
@ -1711,7 +1716,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
|||
path.emplace_back(); // Placeholder for multipath segment
|
||||
substitutes->placeholder_index = path.size() - 1;
|
||||
} else {
|
||||
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error);
|
||||
const auto& op_num = ParseKeyPathNum(elem, apostrophe, error, allow_hardened);
|
||||
if (!op_num) return false;
|
||||
path.emplace_back(*op_num);
|
||||
}
|
||||
|
@ -1814,9 +1819,153 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_i
|
|||
}
|
||||
|
||||
/** Parse a public key including origin information (if enabled). */
|
||||
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)
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
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)
|
||||
{
|
||||
std::vector<std::unique_ptr<PubkeyProvider>> ret;
|
||||
|
||||
using namespace script;
|
||||
|
||||
// musig cannot be nested inside of an origin
|
||||
std::span<const char> span = sp;
|
||||
if (Const("musig(", span, /*skip=*/false)) {
|
||||
if (ctx != ParseScriptContext::P2TR) {
|
||||
error = "musig() is only allowed in tr()";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Split the span on the end parentheses. The end parentheses must
|
||||
// be included in the resulting span so that Expr is happy.
|
||||
auto split = Split(span, ')', /*include_sep=*/true);
|
||||
if (split.size() > 2) {
|
||||
error = "Too many ')' in musig() expression";
|
||||
return {};
|
||||
}
|
||||
std::span<const char> sp_musig(split.at(0).begin(), split.at(0).end());
|
||||
|
||||
auto expr = Expr(sp_musig);
|
||||
if (!Func("musig", expr)) {
|
||||
error = "Invalid musig() expression";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Parse the participant pubkeys
|
||||
bool any_ranged = false;
|
||||
bool all_bip32 = true;
|
||||
std::vector<std::vector<std::unique_ptr<PubkeyProvider>>> providers;
|
||||
bool first = true;
|
||||
size_t max_providers_len = 0;
|
||||
while (expr.size()) {
|
||||
if (!first && !Const(",", expr)) {
|
||||
error = strprintf("musig(): expected ',', got '%c'", expr[0]);
|
||||
return {};
|
||||
}
|
||||
first = false;
|
||||
auto arg = Expr(expr);
|
||||
auto pk = ParsePubkey(key_exp_index, arg, ParseScriptContext::MUSIG, out, error);
|
||||
if (pk.empty()) {
|
||||
error = strprintf("musig(): %s", error);
|
||||
return {};
|
||||
}
|
||||
|
||||
any_ranged |= pk.at(0)->IsRange();
|
||||
all_bip32 &= pk.at(0)->IsBIP32();
|
||||
|
||||
max_providers_len = std::max(max_providers_len, pk.size());
|
||||
|
||||
providers.emplace_back(std::move(pk));
|
||||
key_exp_index++;
|
||||
}
|
||||
if (first) {
|
||||
error = "musig(): Must contain key expressions";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Parse any derivation
|
||||
DeriveType deriv_type = DeriveType::NO;
|
||||
std::vector<KeyPath> paths;
|
||||
if (split.size() == 2 && Const("/", split.at(1), /*skip=*/false)) {
|
||||
if (!all_bip32) {
|
||||
error = "musig(): Ranged musig() requires all participants to be xpubs";
|
||||
return {};
|
||||
}
|
||||
auto deriv_split = Split(split.at(1), '/');
|
||||
if (std::ranges::equal(deriv_split.back(), std::span{"*"}.first(1))) {
|
||||
deriv_split.pop_back();
|
||||
deriv_type = DeriveType::UNHARDENED;
|
||||
if (any_ranged) {
|
||||
error = "musig(): Cannot have ranged participant keys if musig() is also ranged";
|
||||
return {};
|
||||
}
|
||||
} else if (std::ranges::equal(deriv_split.back(), std::span{"*'"}.first(2)) || std::ranges::equal(deriv_split.back(), std::span{"*h"}.first(2))) {
|
||||
error = "musig(): Cannot have hardened child derivation";
|
||||
return {};
|
||||
}
|
||||
bool dummy = false;
|
||||
if (!ParseKeyPath(deriv_split, paths, dummy, error, /*allow_multipath=*/true, /*allow_hardened=*/false)) {
|
||||
error = "musig(): " + error;
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
paths.emplace_back();
|
||||
}
|
||||
|
||||
// Makes sure that all providers vectors in providers are the given length, or exactly length 1
|
||||
// Length 1 vectors have the single provider cloned until it matches the given length.
|
||||
const auto& clone_providers = [&providers](size_t length) -> bool {
|
||||
for (auto& vec : providers) {
|
||||
if (vec.size() == 1) {
|
||||
for (size_t i = 1; i < length; ++i) {
|
||||
vec.emplace_back(vec.at(0)->Clone());
|
||||
}
|
||||
} else if (vec.size() != length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Emplace the final MuSigPubkeyProvider into ret with the pubkey providers from the specified provider vectors index
|
||||
// and the path from the specified path index
|
||||
const auto& emplace_final_provider = [&ret, &key_exp_index, &deriv_type, &paths, &providers](size_t vec_idx, size_t path_idx) -> void {
|
||||
KeyPath& path = paths.at(path_idx);
|
||||
std::vector<std::unique_ptr<PubkeyProvider>> pubs;
|
||||
pubs.reserve(providers.size());
|
||||
for (auto& vec : providers) {
|
||||
pubs.emplace_back(std::move(vec.at(vec_idx)));
|
||||
}
|
||||
ret.emplace_back(std::make_unique<MuSigPubkeyProvider>(key_exp_index, std::move(pubs), path, deriv_type));
|
||||
};
|
||||
|
||||
if (max_providers_len > 1 && paths.size() > 1) {
|
||||
error = "musig(): Cannot have multipath participant keys if musig() is also multipath";
|
||||
return {};
|
||||
} else if (max_providers_len > 1) {
|
||||
if (!clone_providers(max_providers_len)) {
|
||||
error = strprintf("musig(): Multipath derivation paths have mismatched lengths");
|
||||
return {};
|
||||
}
|
||||
for (size_t i = 0; i < max_providers_len; ++i) {
|
||||
// Final MuSigPubkeyProvider use participant pubkey providers at each multipath position, and the first (and only) path
|
||||
emplace_final_provider(i, 0);
|
||||
}
|
||||
} else if (paths.size() > 1) {
|
||||
// All key provider vectors should be length 1. Clone them until they have the same length as paths
|
||||
if (!clone_providers(paths.size())) {
|
||||
error = "musig(): Multipath derivation path with multipath participants is disallowed"; // This error is unreachable due to earlier check
|
||||
return {};
|
||||
}
|
||||
for (size_t i = 0; i < paths.size(); ++i) {
|
||||
// Final MuSigPubkeyProvider uses cloned participant pubkey providers, and the multipath derivation paths
|
||||
emplace_final_provider(i, i);
|
||||
}
|
||||
} else {
|
||||
// No multipath derivation MuSigPubkeyProvider uses the first (and only) participant pubkey providers, and the first (and only) path
|
||||
emplace_final_provider(0, 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto origin_split = Split(sp, ']');
|
||||
if (origin_split.size() > 2) {
|
||||
error = "Multiple ']' characters found for a single pubkey";
|
||||
|
@ -1927,7 +2076,8 @@ 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);
|
||||
uint32_t exp_index = m_offset + key;
|
||||
auto pk = ParsePubkey(exp_index, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error);
|
||||
if (pk.empty()) return {};
|
||||
m_keys.emplace_back(std::move(pk));
|
||||
return key;
|
||||
|
|
Loading…
Add table
Reference in a new issue