From 4a273edda0ec10f0c5ae5d94b9925fa334d1c6e6 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 not be created, add our own public nonces for the private keys that we know, if they do not yet exist. --- src/script/sign.cpp | 123 ++++++++++++++++++++++++++++++--- src/script/signingprovider.cpp | 10 +++ src/script/signingprovider.h | 3 + 3 files changed, 128 insertions(+), 8 deletions(-) diff --git a/src/script/sign.cpp b/src/script/sign.cpp index bc40c05a195..8103e318c51 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -265,6 +265,100 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat return false; } +static bool SignMuSig2(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector& sig_out, const XOnlyPubKey& script_pubkey, const uint256* merkle_root, const uint256* leaf_hash, SigVersion sigversion) +{ + Assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT); + + // Lookup derivation paths for the script pubkey + KeyOriginInfo agg_info; + auto misc_pk_it = sigdata.taproot_misc_pubkeys.find(script_pubkey); + if (misc_pk_it != sigdata.taproot_misc_pubkeys.end()) { + agg_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()) { + it = sigdata.taproot_misc_pubkeys.emplace(xonly_part, std::make_pair(std::set(), part_info)).first; + } + if (leaf_hash) it->second.first.insert(*leaf_hash); + } + } + + // The pubkey in the script may not be the actual aggregate of the participants, but derived from it. + // Check the derivation, and compute the BIP 32 derivation tweaks + std::vector> tweaks; + CPubKey plain_pub = agg_pub; + if (XOnlyPubKey(agg_pub) != script_pubkey) { + if (agg_info.path.empty()) continue; + // Compute and compare fingerprint + CKeyID keyid = agg_pub.GetID(); + if (!std::equal(agg_info.fingerprint, agg_info.fingerprint + sizeof(agg_info.fingerprint), keyid.data())) { + continue; + } + // Get the BIP32 derivation tweaks + CExtPubKey extpub = CreateMuSig2SyntheticXpub(agg_pub); + for (const int i : agg_info.path) { + auto& [t, xonly] = tweaks.emplace_back(); + xonly = false; + if (!extpub.Derive(extpub, i, &t)) { + return false; + } + } + Assert(XOnlyPubKey(extpub.pubkey) == script_pubkey); + plain_pub = extpub.pubkey; + } + + // Add the merkle root tweak + if (sigversion == SigVersion::TAPROOT && merkle_root) { + tweaks.emplace_back(script_pubkey.ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root), true); + std::optional> tweaked = script_pubkey.CreateTapTweak(merkle_root->IsNull() ? nullptr : merkle_root); + if (!Assume(tweaked)) return false; + plain_pub = tweaked->first.GetCPubKeys().at(tweaked->second ? 1 : 0); + } + + // First try to aggregate + if (creator.CreateMuSig2AggregateSig(part_pks, sig_out, agg_pub, plain_pub, leaf_hash, tweaks, sigversion, sigdata)) { + if (sigversion == SigVersion::TAPROOT) { + sigdata.taproot_key_path_sig = sig_out; + } else { + auto lookup_key = std::make_pair(script_pubkey, leaf_hash ? *leaf_hash : uint256()); + sigdata.taproot_script_sigs[lookup_key] = sig_out; + } + continue; + } + // Cannot aggregate, try making partial sigs for every participant + auto pub_key_leaf_hash = std::make_pair(plain_pub, leaf_hash ? *leaf_hash : uint256()); + 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()) { + continue; + } + // 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, merkle_root, sigversion, sigdata); + if (pubnonce.empty()) continue; + pubnonces[part_pk] = std::move(pubnonce); + } + } + return true; +} + static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion) { KeyOriginInfo info; @@ -283,11 +377,14 @@ 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 if (!SignMuSig2(creator, sigdata, provider, sig_out, pubkey, /*merkle_root=*/nullptr, &leaf_hash, sigversion)) { + return false; } - return false; + + return sigdata.taproot_script_sigs.contains(lookup_key); } template @@ -456,6 +553,10 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea if (provider.GetTaprootBuilder(output, builder)) { sigdata.tr_builder = builder; } + if (auto agg_keys = provider.GetAllMuSig2ParticipantPubkeys(); !agg_keys.empty()) { + sigdata.musig2_pubkeys.insert(agg_keys.begin(), agg_keys.end()); + } + // Try key path spending. { @@ -475,16 +576,22 @@ 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 { + SignMuSig2(creator, sigdata, provider, sig, pk, merkle_root, /*leaf_hash=*/nullptr, SigVersion::TAPROOT); } + }; + + // 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/signingprovider.cpp b/src/script/signingprovider.cpp index 846f0c98da2..8557eb772c8 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -58,6 +58,11 @@ std::vector HidingSigningProvider::GetMuSig2ParticipantPubkeys(const CP return m_provider->GetMuSig2ParticipantPubkeys(pubkey); } +std::map> HidingSigningProvider::GetAllMuSig2ParticipantPubkeys() const +{ + return m_provider->GetAllMuSig2ParticipantPubkeys(); +} + void HidingSigningProvider::SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const { m_provider->SetMuSig2SecNonce(id, std::move(nonce)); @@ -109,6 +114,11 @@ std::vector FlatSigningProvider::GetMuSig2ParticipantPubkeys(const CPub return participant_pubkeys; } +std::map> FlatSigningProvider::GetAllMuSig2ParticipantPubkeys() const +{ + return aggregate_pubkeys; +} + void FlatSigningProvider::SetMuSig2SecNonce(const uint256& session_id, MuSig2SecNonce&& nonce) const { if (!Assume(musig2_secnonces)) return; diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index cc917cc6c63..31422bb4b6a 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -166,6 +166,7 @@ public: 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 GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const { return {}; } + virtual std::map> GetAllMuSig2ParticipantPubkeys() 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 {} @@ -213,6 +214,7 @@ public: bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; std::vector GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override; + std::map> GetAllMuSig2ParticipantPubkeys() 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; @@ -236,6 +238,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 GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override; + std::map> GetAllMuSig2ParticipantPubkeys() 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;