|
7 | 7 | #include <hash.h>
|
8 | 8 | #include <key_io.h>
|
9 | 9 | #include <pubkey.h>
|
| 10 | +#include <musig.h> |
10 | 11 | #include <script/miniscript.h>
|
11 | 12 | #include <script/parsing.h>
|
12 | 13 | #include <script/script.h>
|
@@ -580,6 +581,214 @@ class BIP32PubkeyProvider final : public PubkeyProvider
|
580 | 581 | }
|
581 | 582 | };
|
582 | 583 |
|
| 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 | + |
583 | 792 | /** Base class for all Descriptor implementations. */
|
584 | 793 | class DescriptorImpl : public Descriptor
|
585 | 794 | {
|
|
0 commit comments