Skip to content

Commit

Permalink
sign: Create MuSig2 signatures for known MuSig2 aggregate keys
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
achow101 committed Nov 7, 2024
1 parent 56b10a8 commit 9c43df1
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 10 deletions.
3 changes: 1 addition & 2 deletions src/musig.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkey
class MuSig2SecNonce
{
private:
//! The actual secnonce itself
secure_unique_ptr<secp256k1_musig_secnonce> m_nonce;
std::unique_ptr<MuSig2SecNonceImpl> m_impl;

public:
MuSig2SecNonce();
Expand Down
196 changes: 188 additions & 8 deletions src/script/sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint256>({leaf_hash}), part_info));
} else {
it->second.first.insert(leaf_hash);
}
}
}

std::vector<std::pair<uint256, bool>> 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<CPubKey, std::vector<uint8_t>>& pubnonces = sigdata.musig2_pubnonces[pub_key_leaf_hash];
for (const CPubKey& part_pk : part_pks) {
if (pubnonces.contains(part_pk)) continue;
std::vector<uint8_t> 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<typename M, typename K, typename V>
Expand Down Expand Up @@ -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.
{
Expand All @@ -474,16 +560,110 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea
}
}

std::vector<unsigned char> 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<unsigned char> 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<uint256>(), info));
}
}
}

std::vector<std::pair<uint256, bool>> 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<std::pair<XOnlyPubKey, bool>> 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<CPubKey, std::vector<uint8_t>>& pubnonces = sigdata.musig2_pubnonces[pub_key_leaf_hash];
for (const CPubKey& part_pk : part_pks) {
if (pubnonces.contains(part_pk)) continue;
std::vector<uint8_t> 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);
Expand Down
2 changes: 2 additions & 0 deletions src/script/sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ struct SignatureData {
std::map<std::vector<uint8_t>, std::vector<uint8_t>> hash256_preimages; ///< Mapping from a HASH256 hash to its preimage provided to solve a Script
std::map<std::vector<uint8_t>, std::vector<uint8_t>> ripemd160_preimages; ///< Mapping from a RIPEMD160 hash to its preimage provided to solve a Script
std::map<std::vector<uint8_t>, std::vector<uint8_t>> hash160_preimages; ///< Mapping from a HASH160 hash to its preimage provided to solve a Script
//! Map MuSig2 aggregate pubkeys to its participants
std::map<CPubKey, std::vector<CPubKey>> 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::pair<CPubKey, uint256>, std::map<CPubKey, std::vector<uint8_t>>> musig2_pubnonces;
//! Mapping from pair of MuSig2 aggregate pubkey, and tapleaf hash to map of MuSig2 participant pubkeys to MuSig2 partial signature
Expand Down
10 changes: 10 additions & 0 deletions src/script/signingprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ std::vector<CPubKey> HidingSigningProvider::GetAggregateParticipantPubkeys(const
return m_provider->GetAggregateParticipantPubkeys(pubkey);
}

std::map<CPubKey, std::vector<CPubKey>> HidingSigningProvider::GetAllAggregateParticipantPubkeys() const
{
return m_provider->GetAllAggregateParticipantPubkeys();
}

void HidingSigningProvider::SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const
{
m_provider->SetMuSig2SecNonce(id, std::move(nonce));
Expand Down Expand Up @@ -109,6 +114,11 @@ std::vector<CPubKey> FlatSigningProvider::GetAggregateParticipantPubkeys(const C
return it->second;
}

std::map<CPubKey, std::vector<CPubKey>> FlatSigningProvider::GetAllAggregateParticipantPubkeys() const
{
return aggregate_pubkeys;
}

void FlatSigningProvider::SetMuSig2SecNonce(const uint256& session_id, MuSig2SecNonce&& nonce) const
{
if (!musig2_secnonces) return;
Expand Down
3 changes: 3 additions & 0 deletions src/script/signingprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const { return {}; }
virtual std::map<CPubKey, std::vector<CPubKey>> GetAllAggregateParticipantPubkeys() const {return {}; }
virtual void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const {}
virtual std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const { return std::nullopt; }
virtual void DeleteMuSig2Session(const uint256& session_id) const {}
Expand Down Expand Up @@ -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<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const override;
std::map<CPubKey, std::vector<CPubKey>> GetAllAggregateParticipantPubkeys() const override;
void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const override;
std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const override;
void DeleteMuSig2Session(const uint256& session_id) const override;
Expand All @@ -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<CPubKey> GetAggregateParticipantPubkeys(const CPubKey& pubkey) const override;
std::map<CPubKey, std::vector<CPubKey>> GetAllAggregateParticipantPubkeys() const override;
void SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const override;
std::optional<std::reference_wrapper<MuSig2SecNonce>> GetMuSig2SecNonce(const uint256& session_id) const override;
void DeleteMuSig2Session(const uint256& session_id) const override;
Expand Down

0 comments on commit 9c43df1

Please sign in to comment.