mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-10 07:37:59 +02:00
Merge bitcoin/bitcoin#29675: wallet: Be able to receive and spend inputs involving MuSig2 aggregate keys
ac599c4a9ctest: Test MuSig2 in the wallet (Ava Chow)68ef954c4cwallet: Keep secnonces in DescriptorScriptPubKeyMan (Ava Chow)4a273edda0sign: Create MuSig2 signatures for known MuSig2 aggregate keys (Ava Chow)258db93889sign: Add CreateMuSig2AggregateSig (Ava Chow)bf69442b3fsign: Add CreateMuSig2PartialSig (Ava Chow)512b17fc56sign: Add CreateMuSig2Nonce (Ava Chow)82ea67c607musig: Add MuSig2AggregatePubkeys variant that validates the aggregate (Ava Chow)d99a081679psbt: MuSig2 data in Fill/FromSignatureData (Ava Chow)4d8b4f5336signingprovider: Add musig2 secnonces (Ava Chow)c06a1dc86fAdd MuSig2SecNonce class for secure allocation of musig nonces (Ava Chow)9baff05e49sign: Include taproot output key's KeyOriginInfo in sigdata (Ava Chow)4b24bfeab9pubkey: Return tweaks from BIP32 derivation (Ava Chow)f14876213amusig: Move synthetic xpub construction to its own function (Ava Chow)fb8720f1e0sign: Refactor Schnorr sighash computation out of CreateSchnorrSig (Ava Chow)a4cfddda64tests: Clarify why musig derivation adds a pubkey and xpub (Ava Chow)39a63bf2e7descriptors: Add a doxygen comment for has_hardened output_parameter (Ava Chow)2320184d0edescriptors: Fix meaning of any_key_parsed (Ava Chow) Pull request description: This PR implements MuSig2 signing so that the wallet can receive and spend from imported `musig(0` descriptors. The libsecp musig module is enabled so that it can be used for all of the MuSig2 cryptography. Secnonces are handled in a separate class which holds the libsecp secnonce object in a `secure_unique_ptr`. Since secnonces must not be used, this class has no serialization and will only live in memory. A restart of the software will require a restart of the MuSig2 signing process. ACKs for top commit: fjahr: tACKac599c4a9crkrux: lgtm tACKac599c4a9ctheStack: Code-review ACKac599c4a9c🗝️ Tree-SHA512: 626b9adc42ed2403e2f4405321eb9ce009a829c07d968e95ab288fe4940b195b0af35ca279a4a7fa51af76e55382bad6f63a23bca14a84140559b3c667e7041e
This commit is contained in:
@@ -641,13 +641,7 @@ public:
|
||||
// Make our pubkey provider
|
||||
if (IsRangedDerivation() || !m_path.empty()) {
|
||||
// Make the synthetic xpub and construct the BIP32PubkeyProvider
|
||||
CExtPubKey extpub;
|
||||
extpub.nDepth = 0;
|
||||
std::memset(extpub.vchFingerprint, 0, 4);
|
||||
extpub.nChild = 0;
|
||||
extpub.chaincode = MUSIG_CHAINCODE;
|
||||
extpub.pubkey = m_aggregate_pubkey.value();
|
||||
|
||||
CExtPubKey extpub = CreateMuSig2SyntheticXpub(m_aggregate_pubkey.value());
|
||||
m_aggregate_provider = std::make_unique<BIP32PubkeyProvider>(m_expr_index, extpub, m_path, m_derive, /*apostrophe=*/false);
|
||||
} else {
|
||||
m_aggregate_provider = std::make_unique<ConstPubkeyProvider>(m_expr_index, m_aggregate_pubkey.value(), /*xonly=*/false);
|
||||
@@ -1647,6 +1641,7 @@ std::optional<uint32_t> ParseKeyPathNum(std::span<const char> elem, bool& apostr
|
||||
* @param[out] apostrophe only updated if hardened derivation is found
|
||||
* @param[out] error parsing error message
|
||||
* @param[in] allow_multipath Allows the parsed path to use the multipath specifier
|
||||
* @param[out] has_hardened Records whether the path contains any hardened derivation
|
||||
* @returns false if parsing failed
|
||||
**/
|
||||
[[nodiscard]] bool ParseKeyPath(const std::vector<std::span<const char>>& split, std::vector<KeyPath>& out, bool& apostrophe, std::string& error, bool allow_multipath, bool& has_hardened)
|
||||
@@ -1843,20 +1838,20 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t& key_exp_index
|
||||
bool any_ranged = false;
|
||||
bool all_bip32 = true;
|
||||
std::vector<std::vector<std::unique_ptr<PubkeyProvider>>> providers;
|
||||
bool any_key_parsed = true;
|
||||
bool any_key_parsed = false;
|
||||
size_t max_multipath_len = 0;
|
||||
while (expr.size()) {
|
||||
if (!any_key_parsed && !Const(",", expr)) {
|
||||
if (any_key_parsed && !Const(",", expr)) {
|
||||
error = strprintf("musig(): expected ',', got '%c'", expr[0]);
|
||||
return {};
|
||||
}
|
||||
any_key_parsed = false;
|
||||
auto arg = Expr(expr);
|
||||
auto pk = ParsePubkey(key_exp_index, arg, ParseScriptContext::MUSIG, out, error);
|
||||
if (pk.empty()) {
|
||||
error = strprintf("musig(): %s", error);
|
||||
return {};
|
||||
}
|
||||
any_key_parsed = true;
|
||||
|
||||
any_ranged = any_ranged || pk.at(0)->IsRange();
|
||||
all_bip32 = all_bip32 && pk.at(0)->IsBIP32();
|
||||
@@ -1866,7 +1861,7 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkey(uint32_t& key_exp_index
|
||||
providers.emplace_back(std::move(pk));
|
||||
key_exp_index++;
|
||||
}
|
||||
if (any_key_parsed) {
|
||||
if (!any_key_parsed) {
|
||||
error = "musig(): Must contain key expressions";
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
#include <consensus/amount.h>
|
||||
#include <key.h>
|
||||
#include <musig.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <random.h>
|
||||
#include <script/keyorigin.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/script.h>
|
||||
@@ -59,17 +61,14 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const
|
||||
std::optional<uint256> MutableTransactionSignatureCreator::ComputeSchnorrSignatureHash(const uint256* leaf_hash, SigVersion sigversion) const
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
|
||||
|
||||
CKey key;
|
||||
if (!provider.GetKeyByXOnly(pubkey, key)) return false;
|
||||
|
||||
// BIP341/BIP342 signing needs lots of precomputed transaction data. While some
|
||||
// (non-SIGHASH_DEFAULT) sighash modes exist that can work with just some subset
|
||||
// of data present, for now, only support signing when everything is provided.
|
||||
if (!m_txdata || !m_txdata->m_bip341_taproot_ready || !m_txdata->m_spent_outputs_ready) return false;
|
||||
if (!m_txdata || !m_txdata->m_bip341_taproot_ready || !m_txdata->m_spent_outputs_ready) return std::nullopt;
|
||||
|
||||
ScriptExecutionData execdata;
|
||||
execdata.m_annex_init = true;
|
||||
@@ -77,19 +76,134 @@ bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider&
|
||||
if (sigversion == SigVersion::TAPSCRIPT) {
|
||||
execdata.m_codeseparator_pos_init = true;
|
||||
execdata.m_codeseparator_pos = 0xFFFFFFFF; // Only support non-OP_CODESEPARATOR BIP342 signing for now.
|
||||
if (!leaf_hash) return false; // BIP342 signing needs leaf hash.
|
||||
if (!leaf_hash) return std::nullopt; // BIP342 signing needs leaf hash.
|
||||
execdata.m_tapleaf_hash_init = true;
|
||||
execdata.m_tapleaf_hash = *leaf_hash;
|
||||
}
|
||||
uint256 hash;
|
||||
if (!SignatureHashSchnorr(hash, execdata, m_txto, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false;
|
||||
if (!SignatureHashSchnorr(hash, execdata, m_txto, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return std::nullopt;
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
|
||||
|
||||
CKey key;
|
||||
if (!provider.GetKeyByXOnly(pubkey, key)) return false;
|
||||
|
||||
std::optional<uint256> hash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
|
||||
if (!hash.has_value()) return false;
|
||||
|
||||
sig.resize(64);
|
||||
// Use uint256{} as aux_rnd for now.
|
||||
if (!key.SignSchnorr(hash, sig, merkle_root, {})) return false;
|
||||
if (!key.SignSchnorr(*hash, sig, merkle_root, {})) return false;
|
||||
if (nHashType) sig.push_back(nHashType);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> MutableTransactionSignatureCreator::CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
|
||||
|
||||
// Retrieve the private key
|
||||
CKey key;
|
||||
if (!provider.GetKey(part_pubkey.GetID(), key)) return {};
|
||||
|
||||
// Retrieve participant pubkeys
|
||||
auto it = sigdata.musig2_pubkeys.find(aggregate_pubkey);
|
||||
if (it == sigdata.musig2_pubkeys.end()) return {};
|
||||
const std::vector<CPubKey>& pubkeys = it->second;
|
||||
if (std::find(pubkeys.begin(), pubkeys.end(), part_pubkey) == pubkeys.end()) return {};
|
||||
|
||||
// Compute sighash
|
||||
std::optional<uint256> sighash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
|
||||
if (!sighash.has_value()) return {};
|
||||
|
||||
MuSig2SecNonce secnonce;
|
||||
std::vector<uint8_t> out = key.CreateMuSig2Nonce(secnonce, *sighash, aggregate_pubkey, pubkeys);
|
||||
if (out.empty()) return {};
|
||||
|
||||
// Store the secnonce in the SigningProvider
|
||||
provider.SetMuSig2SecNonce(MuSig2SessionID(script_pubkey, part_pubkey, *sighash), std::move(secnonce));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool MutableTransactionSignatureCreator::CreateMuSig2PartialSig(const SigningProvider& provider, uint256& partial_sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
|
||||
|
||||
// Retrieve private key
|
||||
CKey key;
|
||||
if (!provider.GetKey(part_pubkey.GetID(), key)) return false;
|
||||
|
||||
// Retrieve participant pubkeys
|
||||
auto it = sigdata.musig2_pubkeys.find(aggregate_pubkey);
|
||||
if (it == sigdata.musig2_pubkeys.end()) return false;
|
||||
const std::vector<CPubKey>& pubkeys = it->second;
|
||||
if (std::find(pubkeys.begin(), pubkeys.end(), part_pubkey) == pubkeys.end()) return {};
|
||||
|
||||
// Retrieve pubnonces
|
||||
auto this_leaf_aggkey = std::make_pair(script_pubkey, leaf_hash ? *leaf_hash : uint256());
|
||||
auto pubnonce_it = sigdata.musig2_pubnonces.find(this_leaf_aggkey);
|
||||
if (pubnonce_it == sigdata.musig2_pubnonces.end()) return false;
|
||||
const std::map<CPubKey, std::vector<uint8_t>>& pubnonces = pubnonce_it->second;
|
||||
|
||||
// Check if enough pubnonces
|
||||
if (pubnonces.size() != pubkeys.size()) return false;
|
||||
|
||||
// Compute sighash
|
||||
std::optional<uint256> sighash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
|
||||
if (!sighash.has_value()) return false;
|
||||
|
||||
// Retrieve the secnonce
|
||||
uint256 session_id = MuSig2SessionID(script_pubkey, part_pubkey, *sighash);
|
||||
std::optional<std::reference_wrapper<MuSig2SecNonce>> secnonce = provider.GetMuSig2SecNonce(session_id);
|
||||
if (!secnonce || !secnonce->get().IsValid()) return false;
|
||||
|
||||
// Compute the sig
|
||||
std::optional<uint256> sig = key.CreateMuSig2PartialSig(*sighash, aggregate_pubkey, pubkeys, pubnonces, *secnonce, tweaks);
|
||||
if (!sig) return false;
|
||||
partial_sig = std::move(*sig);
|
||||
|
||||
// Delete the secnonce now that we're done with it
|
||||
assert(!secnonce->get().IsValid());
|
||||
provider.DeleteMuSig2Session(session_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MutableTransactionSignatureCreator::CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const
|
||||
{
|
||||
assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
|
||||
if (!participants.size()) return false;
|
||||
|
||||
// Retrieve pubnonces and partial sigs
|
||||
auto this_leaf_aggkey = std::make_pair(script_pubkey, leaf_hash ? *leaf_hash : uint256());
|
||||
auto pubnonce_it = sigdata.musig2_pubnonces.find(this_leaf_aggkey);
|
||||
if (pubnonce_it == sigdata.musig2_pubnonces.end()) return false;
|
||||
const std::map<CPubKey, std::vector<uint8_t>>& pubnonces = pubnonce_it->second;
|
||||
auto partial_sigs_it = sigdata.musig2_partial_sigs.find(this_leaf_aggkey);
|
||||
if (partial_sigs_it == sigdata.musig2_partial_sigs.end()) return false;
|
||||
const std::map<CPubKey, uint256>& partial_sigs = partial_sigs_it->second;
|
||||
|
||||
// Check if enough pubnonces and partial sigs
|
||||
if (pubnonces.size() != participants.size()) return false;
|
||||
if (partial_sigs.size() != participants.size()) return false;
|
||||
|
||||
// Compute sighash
|
||||
std::optional<uint256> sighash = ComputeSchnorrSignatureHash(leaf_hash, sigversion);
|
||||
if (!sighash.has_value()) return false;
|
||||
|
||||
std::optional<std::vector<uint8_t>> res = ::CreateMuSig2AggregateSig(participants, aggregate_pubkey, tweaks, *sighash, pubnonces, partial_sigs);
|
||||
if (!res) return false;
|
||||
sig = res.value();
|
||||
if (nHashType) sig.push_back(nHashType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool GetCScript(const SigningProvider& provider, const SignatureData& sigdata, const CScriptID& scriptid, CScript& script)
|
||||
{
|
||||
if (provider.GetCScript(scriptid, script)) {
|
||||
@@ -151,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<unsigned char>& 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<uint256>(), 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<std::pair<uint256, bool>> 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<std::pair<XOnlyPubKey, bool>> 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<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, 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<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion)
|
||||
{
|
||||
KeyOriginInfo info;
|
||||
@@ -169,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<typename M, typename K, typename V>
|
||||
@@ -342,27 +553,45 @@ 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.
|
||||
{
|
||||
KeyOriginInfo info;
|
||||
if (provider.GetKeyOriginByXOnly(sigdata.tr_spenddata.internal_key, info)) {
|
||||
KeyOriginInfo internal_key_info;
|
||||
if (provider.GetKeyOriginByXOnly(sigdata.tr_spenddata.internal_key, internal_key_info)) {
|
||||
auto it = sigdata.taproot_misc_pubkeys.find(sigdata.tr_spenddata.internal_key);
|
||||
if (it == sigdata.taproot_misc_pubkeys.end()) {
|
||||
sigdata.taproot_misc_pubkeys.emplace(sigdata.tr_spenddata.internal_key, std::make_pair(std::set<uint256>(), info));
|
||||
sigdata.taproot_misc_pubkeys.emplace(sigdata.tr_spenddata.internal_key, std::make_pair(std::set<uint256>(), internal_key_info));
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
sigdata.taproot_key_path_sig = sig;
|
||||
KeyOriginInfo output_key_info;
|
||||
if (provider.GetKeyOriginByXOnly(output, output_key_info)) {
|
||||
auto it = sigdata.taproot_misc_pubkeys.find(output);
|
||||
if (it == sigdata.taproot_misc_pubkeys.end()) {
|
||||
sigdata.taproot_misc_pubkeys.emplace(output, std::make_pair(std::set<uint256>(), output_key_info));
|
||||
}
|
||||
}
|
||||
if (sigdata.taproot_key_path_sig.size() == 0) {
|
||||
if (creator.CreateSchnorrSig(provider, sig, output, nullptr, nullptr, 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 {
|
||||
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) {
|
||||
make_keypath_sig(output, nullptr);
|
||||
}
|
||||
if (sigdata.taproot_key_path_sig.size()) {
|
||||
result = Vector(sigdata.taproot_key_path_sig);
|
||||
@@ -737,6 +966,22 @@ public:
|
||||
sig.assign(64, '\000');
|
||||
return true;
|
||||
}
|
||||
std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const override
|
||||
{
|
||||
std::vector<uint8_t> out;
|
||||
out.assign(MUSIG2_PUBNONCE_SIZE, '\000');
|
||||
return out;
|
||||
}
|
||||
bool CreateMuSig2PartialSig(const SigningProvider& provider, uint256& partial_sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const override
|
||||
{
|
||||
partial_sig = uint256::ONE;
|
||||
return true;
|
||||
}
|
||||
bool CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const override
|
||||
{
|
||||
sig.assign(64, '\000');
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ class SigningProvider;
|
||||
|
||||
struct bilingual_str;
|
||||
struct CMutableTransaction;
|
||||
struct SignatureData;
|
||||
|
||||
/** Interface for signature creators. */
|
||||
class BaseSignatureCreator {
|
||||
@@ -33,6 +34,9 @@ public:
|
||||
/** Create a singular (non-script) signature. */
|
||||
virtual bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const =0;
|
||||
virtual bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const =0;
|
||||
virtual std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const =0;
|
||||
virtual bool CreateMuSig2PartialSig(const SigningProvider& provider, uint256& partial_sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const =0;
|
||||
virtual bool CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const =0;
|
||||
};
|
||||
|
||||
/** A signature creator for transactions. */
|
||||
@@ -45,12 +49,17 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator
|
||||
const MutableTransactionSignatureChecker checker;
|
||||
const PrecomputedTransactionData* m_txdata;
|
||||
|
||||
std::optional<uint256> ComputeSchnorrSignatureHash(const uint256* leaf_hash, SigVersion sigversion) const;
|
||||
|
||||
public:
|
||||
MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, int hash_type);
|
||||
MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type);
|
||||
const BaseSignatureChecker& Checker() const override { return checker; }
|
||||
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override;
|
||||
bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override;
|
||||
std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider& provider, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion, const SignatureData& sigdata) const override;
|
||||
bool CreateMuSig2PartialSig(const SigningProvider& provider, uint256& partial_sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const override;
|
||||
bool CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate_pubkey, const CPubKey& script_pubkey, const uint256* leaf_hash, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion sigversion, const SignatureData& sigdata) const override;
|
||||
};
|
||||
|
||||
/** A signature checker that accepts all signatures */
|
||||
@@ -78,7 +87,7 @@ struct SignatureData {
|
||||
std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys;
|
||||
std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending
|
||||
std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash.
|
||||
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal key (may have no leaf script hashes).
|
||||
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal and output keys (may have no leaf script hashes).
|
||||
std::map<CKeyID, XOnlyPubKey> tap_pubkeys; ///< Misc Taproot pubkeys involved in this input, by hash. (Equivalent of misc_pubkeys but for Taproot.)
|
||||
std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found
|
||||
std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found
|
||||
@@ -88,6 +97,12 @@ 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
|
||||
std::map<std::pair<CPubKey, uint256>, std::map<CPubKey, uint256>> musig2_partial_sigs;
|
||||
|
||||
SignatureData() = default;
|
||||
explicit SignatureData(const CScript& script) : scriptSig(script) {}
|
||||
|
||||
@@ -58,6 +58,26 @@ std::vector<CPubKey> HidingSigningProvider::GetMuSig2ParticipantPubkeys(const CP
|
||||
return m_provider->GetMuSig2ParticipantPubkeys(pubkey);
|
||||
}
|
||||
|
||||
std::map<CPubKey, std::vector<CPubKey>> HidingSigningProvider::GetAllMuSig2ParticipantPubkeys() const
|
||||
{
|
||||
return m_provider->GetAllMuSig2ParticipantPubkeys();
|
||||
}
|
||||
|
||||
void HidingSigningProvider::SetMuSig2SecNonce(const uint256& id, MuSig2SecNonce&& nonce) const
|
||||
{
|
||||
m_provider->SetMuSig2SecNonce(id, std::move(nonce));
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<MuSig2SecNonce>> HidingSigningProvider::GetMuSig2SecNonce(const uint256& session_id) const
|
||||
{
|
||||
return m_provider->GetMuSig2SecNonce(session_id);
|
||||
}
|
||||
|
||||
void HidingSigningProvider::DeleteMuSig2Session(const uint256& session_id) const
|
||||
{
|
||||
m_provider->DeleteMuSig2Session(session_id);
|
||||
}
|
||||
|
||||
bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); }
|
||||
bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); }
|
||||
bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const
|
||||
@@ -94,6 +114,31 @@ std::vector<CPubKey> FlatSigningProvider::GetMuSig2ParticipantPubkeys(const CPub
|
||||
return participant_pubkeys;
|
||||
}
|
||||
|
||||
std::map<CPubKey, std::vector<CPubKey>> FlatSigningProvider::GetAllMuSig2ParticipantPubkeys() const
|
||||
{
|
||||
return aggregate_pubkeys;
|
||||
}
|
||||
|
||||
void FlatSigningProvider::SetMuSig2SecNonce(const uint256& session_id, MuSig2SecNonce&& nonce) const
|
||||
{
|
||||
if (!Assume(musig2_secnonces)) return;
|
||||
musig2_secnonces->emplace(session_id, std::move(nonce));
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<MuSig2SecNonce>> FlatSigningProvider::GetMuSig2SecNonce(const uint256& session_id) const
|
||||
{
|
||||
if (!Assume(musig2_secnonces)) return std::nullopt;
|
||||
const auto& it = musig2_secnonces->find(session_id);
|
||||
if (it == musig2_secnonces->end()) return std::nullopt;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void FlatSigningProvider::DeleteMuSig2Session(const uint256& session_id) const
|
||||
{
|
||||
if (!Assume(musig2_secnonces)) return;
|
||||
musig2_secnonces->erase(session_id);
|
||||
}
|
||||
|
||||
FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
|
||||
{
|
||||
scripts.merge(b.scripts);
|
||||
@@ -102,6 +147,8 @@ FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b)
|
||||
origins.merge(b.origins);
|
||||
tr_trees.merge(b.tr_trees);
|
||||
aggregate_pubkeys.merge(b.aggregate_pubkeys);
|
||||
// We shouldn't be merging 2 different sessions, just overwrite with b's sessions.
|
||||
if (!musig2_secnonces) musig2_secnonces = b.musig2_secnonces;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,15 @@
|
||||
#include <addresstype.h>
|
||||
#include <attributes.h>
|
||||
#include <key.h>
|
||||
#include <musig.h>
|
||||
#include <pubkey.h>
|
||||
#include <script/keyorigin.h>
|
||||
#include <script/script.h>
|
||||
#include <sync.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
struct ShortestVectorFirstComparator
|
||||
{
|
||||
bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const
|
||||
@@ -162,6 +166,10 @@ 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<CPubKey> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const { return {}; }
|
||||
virtual std::map<CPubKey, std::vector<CPubKey>> GetAllMuSig2ParticipantPubkeys() 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 {}
|
||||
|
||||
bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const
|
||||
{
|
||||
@@ -206,6 +214,10 @@ public:
|
||||
bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override;
|
||||
bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override;
|
||||
std::vector<CPubKey> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override;
|
||||
std::map<CPubKey, std::vector<CPubKey>> GetAllMuSig2ParticipantPubkeys() 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;
|
||||
};
|
||||
|
||||
struct FlatSigningProvider final : public SigningProvider
|
||||
@@ -216,6 +228,7 @@ struct FlatSigningProvider final : public SigningProvider
|
||||
std::map<CKeyID, CKey> keys;
|
||||
std::map<XOnlyPubKey, TaprootBuilder> tr_trees; /** Map from output key to Taproot tree (which can then make the TaprootSpendData */
|
||||
std::map<CPubKey, std::vector<CPubKey>> aggregate_pubkeys; /** MuSig2 aggregate pubkeys */
|
||||
std::map<uint256, MuSig2SecNonce>* musig2_secnonces{nullptr};
|
||||
|
||||
bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
|
||||
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
|
||||
@@ -225,6 +238,10 @@ 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> GetMuSig2ParticipantPubkeys(const CPubKey& pubkey) const override;
|
||||
std::map<CPubKey, std::vector<CPubKey>> GetAllMuSig2ParticipantPubkeys() 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;
|
||||
|
||||
FlatSigningProvider& Merge(FlatSigningProvider&& b) LIFETIMEBOUND;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user