mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-19 14:53:43 +01:00
descriptor: Add MuSigPubkeyProvider
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user