Skip to content

Commit 4af0dca

Browse files
committed
descriptor: Add MuSigPubkeyProvider
1 parent d00d954 commit 4af0dca

File tree

2 files changed

+213
-0
lines changed

2 files changed

+213
-0
lines changed

src/musig.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
struct secp256k1_musig_keyagg_cache;
1414

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

src/script/descriptor.cpp

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <hash.h>
88
#include <key_io.h>
99
#include <pubkey.h>
10+
#include <musig.h>
1011
#include <script/miniscript.h>
1112
#include <script/parsing.h>
1213
#include <script/script.h>
@@ -580,6 +581,214 @@ class BIP32PubkeyProvider final : public PubkeyProvider
580581
}
581582
};
582583

584+
/** PubkeyProvider for a musig() expression */
585+
class MuSigPubkeyProvider final : public PubkeyProvider
586+
{
587+
private:
588+
//! PubkeyProvider for the participants
589+
const std::vector<std::unique_ptr<PubkeyProvider>> m_participants;
590+
//! Derivation path
591+
const KeyPath m_path;
592+
//! PubkeyProvider for the aggregate pubkey if it can be cached (i.e. participants are not ranged)
593+
mutable std::unique_ptr<PubkeyProvider> m_aggregate_provider;
594+
mutable std::optional<CPubKey> m_aggregate_pubkey;
595+
const DeriveType m_derive;
596+
const bool m_ranged_participants;
597+
598+
bool IsRangedDerivation() const { return m_derive != DeriveType::NO; }
599+
600+
public:
601+
MuSigPubkeyProvider(
602+
uint32_t exp_index,
603+
std::vector<std::unique_ptr<PubkeyProvider>> providers,
604+
KeyPath path,
605+
DeriveType derive
606+
)
607+
: PubkeyProvider(exp_index),
608+
m_participants(std::move(providers)),
609+
m_path(std::move(path)),
610+
m_derive(derive),
611+
m_ranged_participants(std::any_of(m_participants.begin(), m_participants.end(), [](const auto& pubkey) { return pubkey->IsRange(); }))
612+
{
613+
if (!Assume(!(m_ranged_participants && IsRangedDerivation()))) {
614+
throw std::runtime_error("musig(): Cannot have both ranged participants and ranged derivation");
615+
}
616+
if (!Assume(m_derive != DeriveType::HARDENED)) {
617+
throw std::runtime_error("musig(): Cannot have hardened derivation");
618+
}
619+
}
620+
621+
std::optional<CPubKey> GetPubKey(int pos, const SigningProvider& arg, FlatSigningProvider& out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
622+
{
623+
FlatSigningProvider dummy;
624+
// If the participants are not ranged, we can compute and cache the aggregate pubkey by creating a PubkeyProvider for it
625+
if (!m_aggregate_provider && !m_ranged_participants) {
626+
// Retrieve the pubkeys from the providers
627+
std::vector<CPubKey> pubkeys;
628+
for (const auto& prov : m_participants) {
629+
std::optional<CPubKey> pubkey = prov->GetPubKey(0, arg, dummy, read_cache, write_cache);
630+
if (!pubkey.has_value()) {
631+
return std::nullopt;
632+
}
633+
pubkeys.push_back(pubkey.value());
634+
}
635+
std::sort(pubkeys.begin(), pubkeys.end());
636+
637+
// Aggregate the pubkey
638+
m_aggregate_pubkey = MuSig2AggregatePubkeys(pubkeys);
639+
if (!Assume(m_aggregate_pubkey.has_value())) return std::nullopt;
640+
641+
// Make our pubkey provider
642+
if (IsRangedDerivation() || !m_path.empty()) {
643+
// Make the synthetic xpub and construct the BIP32PubkeyProvider
644+
CExtPubKey extpub;
645+
extpub.nDepth = 0;
646+
std::memset(extpub.vchFingerprint, 0, 4);
647+
extpub.nChild = 0;
648+
extpub.chaincode = MUSIG_CHAINCODE;
649+
extpub.pubkey = m_aggregate_pubkey.value();
650+
651+
m_aggregate_provider = std::make_unique<BIP32PubkeyProvider>(m_expr_index, extpub, m_path, m_derive, /*apostrophe=*/false);
652+
} else {
653+
m_aggregate_provider = std::make_unique<ConstPubkeyProvider>(m_expr_index, m_aggregate_pubkey.value(), /*xonly=*/false);
654+
}
655+
}
656+
657+
// Retrieve all participant pubkeys
658+
std::vector<CPubKey> pubkeys;
659+
for (const auto& prov : m_participants) {
660+
std::optional<CPubKey> pub = prov->GetPubKey(pos, arg, out, read_cache, write_cache);
661+
if (!pub) return std::nullopt;
662+
pubkeys.emplace_back(*pub);
663+
}
664+
std::sort(pubkeys.begin(), pubkeys.end());
665+
666+
CPubKey pubout;
667+
if (m_aggregate_provider) {
668+
// When we have a cached aggregate key, we are either returning it or deriving from it
669+
// Either way, we can passthrough to its GetPubKey
670+
// Use a dummy signing provider as private keys do not exist for the aggregate pubkey
671+
std::optional<CPubKey> pub = m_aggregate_provider->GetPubKey(pos, dummy, out, read_cache, write_cache);
672+
if (!pub) return std::nullopt;
673+
pubout = *pub;
674+
out.aggregate_pubkeys.emplace(m_aggregate_pubkey.value(), pubkeys);
675+
} else {
676+
if (!Assume(m_ranged_participants) || !Assume(m_path.empty())) return std::nullopt;
677+
// Compute aggregate key from derived participants
678+
std::optional<CPubKey> aggregate_pubkey = MuSig2AggregatePubkeys(pubkeys);
679+
if (!aggregate_pubkey) return std::nullopt;
680+
pubout = *aggregate_pubkey;
681+
682+
std::unique_ptr<ConstPubkeyProvider> this_agg_provider = std::make_unique<ConstPubkeyProvider>(m_expr_index, aggregate_pubkey.value(), /*xonly=*/false);
683+
this_agg_provider->GetPubKey(0, dummy, out, read_cache, write_cache);
684+
out.aggregate_pubkeys.emplace(pubout, pubkeys);
685+
}
686+
687+
if (!Assume(pubout.IsValid())) return std::nullopt;
688+
return pubout;
689+
}
690+
bool IsRange() const override { return IsRangedDerivation() || m_ranged_participants; }
691+
// musig() expressions can only be used in tr() contexts which have 32 byte xonly pubkeys
692+
size_t GetSize() const override { return 32; }
693+
694+
std::string ToString(StringType type=StringType::PUBLIC) const override
695+
{
696+
std::string out = "musig(";
697+
for (size_t i = 0; i < m_participants.size(); ++i) {
698+
const auto& pubkey = m_participants.at(i);
699+
if (i) out += ",";
700+
out += pubkey->ToString(type);
701+
}
702+
out += ")";
703+
out += FormatHDKeypath(m_path);
704+
if (IsRangedDerivation()) {
705+
out += "/*";
706+
}
707+
return out;
708+
}
709+
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
710+
{
711+
bool any_privkeys = false;
712+
out = "musig(";
713+
for (size_t i = 0; i < m_participants.size(); ++i) {
714+
const auto& pubkey = m_participants.at(i);
715+
if (i) out += ",";
716+
std::string tmp;
717+
if (pubkey->ToPrivateString(arg, tmp)) {
718+
any_privkeys = true;
719+
out += tmp;
720+
} else {
721+
out += pubkey->ToString();
722+
}
723+
}
724+
out += ")";
725+
out += FormatHDKeypath(m_path);
726+
if (IsRangedDerivation()) {
727+
out += "/*";
728+
}
729+
if (!any_privkeys) out.clear();
730+
return any_privkeys;
731+
}
732+
bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const override
733+
{
734+
out = "musig(";
735+
for (size_t i = 0; i < m_participants.size(); ++i) {
736+
const auto& pubkey = m_participants.at(i);
737+
if (i) out += ",";
738+
std::string tmp;
739+
if (!pubkey->ToNormalizedString(arg, tmp, cache)) {
740+
return false;
741+
}
742+
out += tmp;
743+
}
744+
out += ")";
745+
out += FormatHDKeypath(m_path);
746+
if (IsRangedDerivation()) {
747+
out += "/*";
748+
}
749+
return true;
750+
}
751+
752+
void GetPrivKey(int pos, const SigningProvider& arg, FlatSigningProvider& out) const override
753+
{
754+
// Get the private keys for any participants that we have
755+
// If there is participant derivation, it will be done.
756+
// If there is not, then the participant privkeys will be included directly
757+
for (const auto& prov : m_participants) {
758+
prov->GetPrivKey(pos, arg, out);
759+
}
760+
}
761+
762+
// Get RootPubKey and GetRootExtPubKey are used to return the single pubkey underlying the pubkey provider
763+
// to be presented to the user in gethdkeys. As this is a multisig construction, there is no single underlying
764+
// pubkey hence nothing should be returned.
765+
// While the aggregate pubkey could be returned as the root (ext)pubkey, it is not a pubkey that anyone should
766+
// be using by itself in a descriptor as it is unspendable without knowing its participants.
767+
std::optional<CPubKey> GetRootPubKey() const override
768+
{
769+
return std::nullopt;
770+
}
771+
std::optional<CExtPubKey> GetRootExtPubKey() const override
772+
{
773+
return std::nullopt;
774+
}
775+
776+
std::unique_ptr<PubkeyProvider> Clone() const override
777+
{
778+
std::vector<std::unique_ptr<PubkeyProvider>> providers;
779+
providers.reserve(m_participants.size());
780+
for (const std::unique_ptr<PubkeyProvider>& p : m_participants) {
781+
providers.emplace_back(p->Clone());
782+
}
783+
return std::make_unique<MuSigPubkeyProvider>(m_expr_index, std::move(providers), m_path, m_derive);
784+
}
785+
bool IsBIP32() const override
786+
{
787+
// musig() can only be a BIP 32 key if all participants are bip32 too
788+
return std::all_of(m_participants.begin(), m_participants.end(), [](const auto& pubkey) { return pubkey->IsBIP32(); });
789+
}
790+
};
791+
583792
/** Base class for all Descriptor implementations. */
584793
class DescriptorImpl : public Descriptor
585794
{

0 commit comments

Comments
 (0)