descriptor: Add MuSigPubkeyProvider

This commit is contained in:
Ava Chow
2024-01-15 17:10:08 -05:00
parent d00d95437d
commit 4af0dca096
2 changed files with 213 additions and 0 deletions

View File

@@ -12,6 +12,10 @@
struct secp256k1_musig_keyagg_cache;
//! MuSig2 chaincode as defined by BIP 328
using namespace util::hex_literals;
constexpr uint256 MUSIG_CHAINCODE{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8};
//! Create a secp256k1_musig_keyagg_cache from the pubkeys in their current order. This is necessary for most MuSig2 operations
bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache);
//! Retrieve the full aggregate pubkey from the secp256k1_musig_keyagg_cache

View File

@@ -7,6 +7,7 @@
#include <hash.h>
#include <key_io.h>
#include <pubkey.h>
#include <musig.h>
#include <script/miniscript.h>
#include <script/parsing.h>
#include <script/script.h>
@@ -580,6 +581,214 @@ public:
}
};
/** PubkeyProvider for a musig() expression */
class MuSigPubkeyProvider final : public PubkeyProvider
{
private:
//! PubkeyProvider for the participants
const std::vector<std::unique_ptr<PubkeyProvider>> m_participants;
//! Derivation path
const KeyPath m_path;
//! PubkeyProvider for the aggregate pubkey if it can be cached (i.e. participants are not ranged)
mutable std::unique_ptr<PubkeyProvider> m_aggregate_provider;
mutable std::optional<CPubKey> m_aggregate_pubkey;
const DeriveType m_derive;
const bool m_ranged_participants;
bool IsRangedDerivation() const { return m_derive != DeriveType::NO; }
public:
MuSigPubkeyProvider(
uint32_t exp_index,
std::vector<std::unique_ptr<PubkeyProvider>> providers,
KeyPath path,
DeriveType derive
)
: PubkeyProvider(exp_index),
m_participants(std::move(providers)),
m_path(std::move(path)),
m_derive(derive),
m_ranged_participants(std::any_of(m_participants.begin(), m_participants.end(), [](const auto& pubkey) { return pubkey->IsRange(); }))
{
if (!Assume(!(m_ranged_participants && IsRangedDerivation()))) {
throw std::runtime_error("musig(): Cannot have both ranged participants and ranged derivation");
}
if (!Assume(m_derive != DeriveType::HARDENED)) {
throw std::runtime_error("musig(): Cannot have hardened derivation");
}
}
std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
{
FlatSigningProvider dummy;
// If the participants are not ranged, we can compute and cache the aggregate pubkey by creating a PubkeyProvider for it
if (!m_aggregate_provider && !m_ranged_participants) {
// Retrieve the pubkeys from the providers
std::vector<CPubKey> pubkeys;
for (const auto& prov : m_participants) {
std::optional<CPubKey> pubkey = prov->GetPubKey(0, arg, dummy, read_cache, write_cache);
if (!pubkey.has_value()) {
return std::nullopt;
}
pubkeys.push_back(pubkey.value());
}
std::sort(pubkeys.begin(), pubkeys.end());
// Aggregate the pubkey
m_aggregate_pubkey = MuSig2AggregatePubkeys(pubkeys);
if (!Assume(m_aggregate_pubkey.has_value())) return std::nullopt;
// Make our pubkey provider
if (IsRangedDerivation() || !m_path.empty()) {
// Make the synthetic xpub and construct the BIP32PubkeyProvider
CExtPubKey extpub;
extpub.nDepth = 0;
std::memset(extpub.vchFingerprint, 0, 4);
extpub.nChild = 0;
extpub.chaincode = MUSIG_CHAINCODE;
extpub.pubkey = m_aggregate_pubkey.value();
m_aggregate_provider = std::make_unique<BIP32PubkeyProvider>(m_expr_index, extpub, m_path, m_derive, /*apostrophe=*/false);
} else {
m_aggregate_provider = std::make_unique<ConstPubkeyProvider>(m_expr_index, m_aggregate_pubkey.value(), /*xonly=*/false);
}
}
// Retrieve all participant pubkeys
std::vector<CPubKey> pubkeys;
for (const auto& prov : m_participants) {
std::optional<CPubKey> pub = prov->GetPubKey(pos, arg, out, read_cache, write_cache);
if (!pub) return std::nullopt;
pubkeys.emplace_back(*pub);
}
std::sort(pubkeys.begin(), pubkeys.end());
CPubKey pubout;
if (m_aggregate_provider) {
// When we have a cached aggregate key, we are either returning it or deriving from it
// Either way, we can passthrough to its GetPubKey
// Use a dummy signing provider as private keys do not exist for the aggregate pubkey
std::optional<CPubKey> pub = m_aggregate_provider->GetPubKey(pos, dummy, out, read_cache, write_cache);
if (!pub) return std::nullopt;
pubout = *pub;
out.aggregate_pubkeys.emplace(m_aggregate_pubkey.value(), pubkeys);
} else {
if (!Assume(m_ranged_participants) || !Assume(m_path.empty())) return std::nullopt;
// Compute aggregate key from derived participants
std::optional<CPubKey> aggregate_pubkey = MuSig2AggregatePubkeys(pubkeys);
if (!aggregate_pubkey) return std::nullopt;
pubout = *aggregate_pubkey;
std::unique_ptr<ConstPubkeyProvider> this_agg_provider = std::make_unique<ConstPubkeyProvider>(m_expr_index, aggregate_pubkey.value(), /*xonly=*/false);
this_agg_provider->GetPubKey(0, dummy, out, read_cache, write_cache);
out.aggregate_pubkeys.emplace(pubout, pubkeys);
}
if (!Assume(pubout.IsValid())) return std::nullopt;
return pubout;
}
bool IsRange() const override { return IsRangedDerivation() || m_ranged_participants; }
// musig() expressions can only be used in tr() contexts which have 32 byte xonly pubkeys
size_t GetSize() const override { return 32; }
std::string ToString(StringType type=StringType::PUBLIC) const override
{
std::string out = "musig(";
for (size_t i = 0; i < m_participants.size(); ++i) {
const auto& pubkey = m_participants.at(i);
if (i) out += ",";
out += pubkey->ToString(type);
}
out += ")";
out += FormatHDKeypath(m_path);
if (IsRangedDerivation()) {
out += "/*";
}
return out;
}
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
{
bool any_privkeys = false;
out = "musig(";
for (size_t i = 0; i < m_participants.size(); ++i) {
const auto& pubkey = m_participants.at(i);
if (i) out += ",";
std::string tmp;
if (pubkey->ToPrivateString(arg, tmp)) {
any_privkeys = true;
out += tmp;
} else {
out += pubkey->ToString();
}
}
out += ")";
out += FormatHDKeypath(m_path);
if (IsRangedDerivation()) {
out += "/*";
}
if (!any_privkeys) out.clear();
return any_privkeys;
}
bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const override
{
out = "musig(";
for (size_t i = 0; i < m_participants.size(); ++i) {
const auto& pubkey = m_participants.at(i);
if (i) out += ",";
std::string tmp;
if (!pubkey->ToNormalizedString(arg, tmp, cache)) {
return false;
}
out += tmp;
}
out += ")";
out += FormatHDKeypath(m_path);
if (IsRangedDerivation()) {
out += "/*";
}
return true;
}
void GetPrivKey(int pos, const SigningProvider& arg, FlatSigningProvider& out) const override
{
// Get the private keys for any participants that we have
// If there is participant derivation, it will be done.
// If there is not, then the participant privkeys will be included directly
for (const auto& prov : m_participants) {
prov->GetPrivKey(pos, arg, out);
}
}
// Get RootPubKey and GetRootExtPubKey are used to return the single pubkey underlying the pubkey provider
// to be presented to the user in gethdkeys. As this is a multisig construction, there is no single underlying
// pubkey hence nothing should be returned.
// While the aggregate pubkey could be returned as the root (ext)pubkey, it is not a pubkey that anyone should
// be using by itself in a descriptor as it is unspendable without knowing its participants.
std::optional<CPubKey> GetRootPubKey() const override
{
return std::nullopt;
}
std::optional<CExtPubKey> GetRootExtPubKey() const override
{
return std::nullopt;
}
std::unique_ptr<PubkeyProvider> Clone() const override
{
std::vector<std::unique_ptr<PubkeyProvider>> providers;
providers.reserve(m_participants.size());
for (const std::unique_ptr<PubkeyProvider>& p : m_participants) {
providers.emplace_back(p->Clone());
}
return std::make_unique<MuSigPubkeyProvider>(m_expr_index, std::move(providers), m_path, m_derive);
}
bool IsBIP32() const override
{
// musig() can only be a BIP 32 key if all participants are bip32 too
return std::all_of(m_participants.begin(), m_participants.end(), [](const auto& pubkey) { return pubkey->IsBIP32(); });
}
};
/** Base class for all Descriptor implementations. */
class DescriptorImpl : public Descriptor
{