From 71d507f5c18b9092cc6212ef133d711cfd2142a3 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Mon, 5 Feb 2024 15:57:01 -0500 Subject: [PATCH] sign: Create MuSig2 signatures for known MuSig2 aggregate keys When creating Taproot signatures, if the key being signed for is known to be a MuSig2 aggregate key, do the MuSig2 signing algorithms. First try to create the aggregate signature. This will fail if there are not enough partial signatures or public nonces. If it does fail, try to create a partial signature with all participant keys. This will fail for those keys that we do not have the private keys for, and if there are not enough public nonces. Lastly, if the partial signatures could be created, add our own public nonces for the private keys that we know, if they do not yet exist. --- src/musig.h | 3 +- src/script/sign.cpp | 196 +++++++++++++++++++++++++++++++-- src/script/sign.h | 2 + src/script/signingprovider.cpp | 10 ++ src/script/signingprovider.h | 3 + 5 files changed, 204 insertions(+), 10 deletions(-) diff --git a/src/musig.h b/src/musig.h index c38477dec950ee..d5dd492998316c 100644 --- a/src/musig.h +++ b/src/musig.h @@ -32,8 +32,7 @@ std::optional MuSig2AggregatePubkeys(const std::vector& pubkey class MuSig2SecNonce { private: - //! The actual secnonce itself - secure_unique_ptr m_nonce; + std::unique_ptr m_impl; public: MuSig2SecNonce(); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 78e76d8aa2dfea..ac893c70c775d9 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -282,11 +282,93 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur sig_out = it->second; return true; } + if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) { sigdata.taproot_script_sigs[lookup_key] = sig_out; - return true; + } else { + auto misc_pk_it = sigdata.taproot_misc_pubkeys.find(pubkey); + if (misc_pk_it != sigdata.taproot_misc_pubkeys.end()) { + info = misc_pk_it->second.second; + } + + for (const auto& [agg_pub, part_pks] : sigdata.musig2_pubkeys) { + if (part_pks.empty()) continue; + + // Fill participant derivation path info + for (const auto& part_pk : part_pks) { + KeyOriginInfo part_info; + if (provider.GetKeyOrigin(part_pk.GetID(), part_info)) { + XOnlyPubKey xonly_part(part_pk); + auto it = sigdata.taproot_misc_pubkeys.find(xonly_part); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(xonly_part, std::make_pair(std::set({leaf_hash}), part_info)); + } else { + it->second.first.insert(leaf_hash); + } + } + } + + std::vector> tweaks; + CPubKey plain_pub = agg_pub; + if (XOnlyPubKey(agg_pub) != pubkey) { + if (info.path.size() > 0) { + // Compute and compare fingerprint + CKeyID keyid = agg_pub.GetID(); + if (std::memcmp(keyid.data(), info.fingerprint, sizeof(info.fingerprint)) != 0) { + continue; + } + // Get the BIP32 derivation tweaks + CExtPubKey extpub; + extpub.nDepth = 0; + std::memset(extpub.vchFingerprint, 0, 4); + extpub.nChild = 0; + extpub.chaincode = uint256::FromHex("6589e367712c6200e367717145cb322d76576bc3248959c474f9a602ca878086").value(); + extpub.pubkey = agg_pub; + for (const int i : info.path) { + auto& [tweak, xonly] = tweaks.emplace_back(); + xonly = false; + if (!extpub.Derive(extpub, i, tweak)) { + return false; + } + } + Assert(XOnlyPubKey(extpub.pubkey) == pubkey); + plain_pub = extpub.pubkey; + } else { + continue; + } + } + + // We know this is musig, try musig signing + // First try to aggregate + if (creator.CreateMuSig2AggregateSig(part_pks, sig_out, agg_pub, plain_pub, &leaf_hash, tweaks, sigversion, sigdata)) { + sigdata.taproot_script_sigs[lookup_key] = sig_out; + return true; + } + // Cannot aggregate, try making partial sigs for every participant + auto pub_key_leaf_hash = std::make_pair(plain_pub, leaf_hash); + for (const CPubKey& part_pk : part_pks) { + uint256 partial_sig; + if (creator.CreateMuSig2PartialSig(provider, partial_sig, agg_pub, plain_pub, part_pk, &leaf_hash, tweaks, sigversion, sigdata) && Assume(!partial_sig.IsNull())) { + sigdata.musig2_partial_sigs[pub_key_leaf_hash].emplace(part_pk, partial_sig); + } + } + // If there are any partial signatures, exit early + auto partial_sigs_it = sigdata.musig2_partial_sigs.find(pub_key_leaf_hash); + if (partial_sigs_it != sigdata.musig2_partial_sigs.end() && !partial_sigs_it->second.empty()) { + return false; + } + // No partial sigs, try to make pubnonces + std::map>& pubnonces = sigdata.musig2_pubnonces[pub_key_leaf_hash]; + for (const CPubKey& part_pk : part_pks) { + if (pubnonces.contains(part_pk)) continue; + std::vector pubnonce = creator.CreateMuSig2Nonce(provider, agg_pub, plain_pub, part_pk, &leaf_hash, nullptr, sigversion, sigdata); + if (pubnonce.empty()) continue; + pubnonces[part_pk] = std::move(pubnonce); + } + } } - return false; + + return sigdata.taproot_script_sigs.contains(lookup_key); } template @@ -455,6 +537,10 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea if (provider.GetTaprootBuilder(output, builder)) { sigdata.tr_builder = builder; } + if (auto agg_keys = provider.GetAllAggregateParticipantPubkeys(); !agg_keys.empty()) { + sigdata.musig2_pubkeys.merge(agg_keys); + } + // Try key path spending. { @@ -474,16 +560,110 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea } } - std::vector sig; - if (sigdata.taproot_key_path_sig.size() == 0) { - if (creator.CreateSchnorrSig(provider, sig, sigdata.tr_spenddata.internal_key, nullptr, &sigdata.tr_spenddata.merkle_root, SigVersion::TAPROOT)) { + auto make_keypath_sig = [&](const XOnlyPubKey& pk, const uint256* merkle_root) { + std::vector sig; + if (creator.CreateSchnorrSig(provider, sig, pk, nullptr, merkle_root, SigVersion::TAPROOT)) { sigdata.taproot_key_path_sig = sig; + } else { + // Lookup derivation paths for this key + KeyOriginInfo info; + auto misc_pk_it = sigdata.taproot_misc_pubkeys.find(pk); + if (misc_pk_it != sigdata.taproot_misc_pubkeys.end()) { + info = misc_pk_it->second.second; + } + + for (const auto& [agg_pub, part_pks] : sigdata.musig2_pubkeys) { + if (part_pks.empty()) continue; + + // Fill participant derivation path info + for (const auto& part_pk : part_pks) { + KeyOriginInfo info; + if (provider.GetKeyOrigin(part_pk.GetID(), info)) { + XOnlyPubKey xonly_part(part_pk); + auto it = sigdata.taproot_misc_pubkeys.find(xonly_part); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(xonly_part, std::make_pair(std::set(), info)); + } + } + } + + std::vector> tweaks; + CPubKey plain_pub = agg_pub; + if (XOnlyPubKey(agg_pub) != pk) { + if (info.path.size() > 0) { + // Compute and compare fingerprint + CKeyID keyid = agg_pub.GetID(); + if (!std::equal(info.fingerprint, info.fingerprint + sizeof(info.fingerprint), keyid.data())) { + continue; + } + // Get the BIP32 derivation tweaks + CExtPubKey extpub; + extpub.nDepth = 0; + std::memset(extpub.vchFingerprint, 0, 4); + extpub.nChild = 0; + extpub.chaincode = uint256::FromHex("6589e367712c6200e367717145cb322d76576bc3248959c474f9a602ca878086").value(); + extpub.pubkey = agg_pub; + for (const int i : info.path) { + auto& [t, xonly] = tweaks.emplace_back(); + xonly = false; + if (!extpub.Derive(extpub, i, t)) { + return; + } + } + Assert(XOnlyPubKey(extpub.pubkey) == pk); + plain_pub = extpub.pubkey; + } else { + continue; + } + } + + // Add the merkle root tweak + if (merkle_root) { + tweaks.emplace_back(pk.ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root), true); + std::optional> tweaked = pk.CreateTapTweak(merkle_root->IsNull() ? nullptr : merkle_root); + if (!Assume(tweaked)) return; + plain_pub = tweaked->first.GetCPubKeys().at(tweaked->second ? 1 : 0); + } + + // We know this is musig, try musig signing + // First try to aggregate + if (creator.CreateMuSig2AggregateSig(part_pks, sig, agg_pub, plain_pub, nullptr, tweaks, SigVersion::TAPROOT, sigdata)) { + sigdata.taproot_key_path_sig = sig; + return; + } + // Cannot aggregate, try making partial sigs for every participant + auto pub_key_leaf_hash = std::make_pair(plain_pub, uint256()); + for (const CPubKey& part_pk : part_pks) { + uint256 partial_sig; + if (creator.CreateMuSig2PartialSig(provider, partial_sig, agg_pub, plain_pub, part_pk, nullptr, tweaks, SigVersion::TAPROOT, sigdata) && Assume(!partial_sig.IsNull())) { + sigdata.musig2_partial_sigs[pub_key_leaf_hash].emplace(part_pk, partial_sig); + } + } + // If there are any partial signatures, exit early + auto partial_sigs_it = sigdata.musig2_partial_sigs.find(pub_key_leaf_hash); + if (partial_sigs_it != sigdata.musig2_partial_sigs.end() && !partial_sigs_it->second.empty()) { + return; + } + // No partial sigs, try to make pubnonces + std::map>& pubnonces = sigdata.musig2_pubnonces[pub_key_leaf_hash]; + for (const CPubKey& part_pk : part_pks) { + if (pubnonces.contains(part_pk)) continue; + std::vector pubnonce = creator.CreateMuSig2Nonce(provider, agg_pub, plain_pub, part_pk, nullptr, merkle_root, SigVersion::TAPROOT, sigdata); + if (pubnonce.empty()) continue; + pubnonces[part_pk] = std::move(pubnonce); + } + break; + } } + }; + + // First try signing with internal key + if (sigdata.taproot_key_path_sig.size() == 0) { + make_keypath_sig(sigdata.tr_spenddata.internal_key, &sigdata.tr_spenddata.merkle_root); } + // Try signing with output key if still no signature if (sigdata.taproot_key_path_sig.size() == 0) { - if (creator.CreateSchnorrSig(provider, sig, output, nullptr, nullptr, SigVersion::TAPROOT)) { - sigdata.taproot_key_path_sig = sig; - } + make_keypath_sig(output, nullptr); } if (sigdata.taproot_key_path_sig.size()) { result = Vector(sigdata.taproot_key_path_sig); diff --git a/src/script/sign.h b/src/script/sign.h index 177ee8f78d1f22..8378953a3430ea 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -97,6 +97,8 @@ struct SignatureData { std::map, std::vector> hash256_preimages; ///< Mapping from a HASH256 hash to its preimage provided to solve a Script std::map, std::vector> ripemd160_preimages; ///< Mapping from a RIPEMD160 hash to its preimage provided to solve a Script std::map, std::vector> hash160_preimages; ///< Mapping from a HASH160 hash to its preimage provided to solve a Script + //! Map MuSig2 aggregate pubkeys to its participants + std::map> musig2_pubkeys; //! Mapping from pair of MuSig2 aggregate pubkey, and tapleaf hash to map of MuSig2 participant pubkeys to MuSig2 public nonce std::map, std::map>> musig2_pubnonces; //! Mapping from pair of MuSig2 aggregate pubkey, and tapleaf hash to map of MuSig2 participant pubkeys to MuSig2 partial signature diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index ba5151a74bbc0c..284be5843aa941 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -58,6 +58,11 @@ std::vector HidingSigningProvider::GetAggregateParticipantPubkeys(const return m_provider->GetAggregateParticipantPubkeys(pubkey); } +std::map> HidingSigningProvider::GetAllAggregateParticipantPubkeys() const +{ + return m_provider->GetAllAggregateParticipantPubkeys(); +} + void HidingSigningProvider::SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const { m_provider->SetMuSig2SecNonce(id, std::move(nonce)); @@ -109,6 +114,11 @@ std::vector FlatSigningProvider::GetAggregateParticipantPubkeys(const C return it->second; } +std::map> FlatSigningProvider::GetAllAggregateParticipantPubkeys() const +{ + return aggregate_pubkeys; +} + void FlatSigningProvider::SetMuSig2SecNonce(const uint256& session_id, MuSig2SecNonce&& nonce) const { if (!musig2_secnonces) return; diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index 703c882f64d186..4026add1e12a5d 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -164,6 +164,7 @@ class SigningProvider virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; } virtual bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const { return false; } virtual std::vector GetAggregateParticipantPubkeys(const CPubKey& pubkey) const { return {}; } + virtual std::map> GetAllAggregateParticipantPubkeys() const {return {}; } virtual void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const {} virtual std::optional> GetMuSig2SecNonce(const uint256& session_id) const { return std::nullopt; } virtual void DeleteMuSig2Session(const uint256& session_id) const {} @@ -211,6 +212,7 @@ class HidingSigningProvider : public SigningProvider bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; std::vector GetAggregateParticipantPubkeys(const CPubKey& pubkey) const override; + std::map> GetAllAggregateParticipantPubkeys() const override; void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const override; std::optional> GetMuSig2SecNonce(const uint256& session_id) const override; void DeleteMuSig2Session(const uint256& session_id) const override; @@ -234,6 +236,7 @@ struct FlatSigningProvider final : public SigningProvider bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; std::vector GetAggregateParticipantPubkeys(const CPubKey& pubkey) const override; + std::map> GetAllAggregateParticipantPubkeys() const override; void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const override; std::optional> GetMuSig2SecNonce(const uint256& session_id) const override; void DeleteMuSig2Session(const uint256& session_id) const override;