Skip to content

Commit 48aa0e9

Browse files
committed
Merge bitcoin/bitcoin#29675: wallet: Be able to receive and spend inputs involving MuSig2 aggregate keys
ac599c4 test: Test MuSig2 in the wallet (Ava Chow) 68ef954 wallet: Keep secnonces in DescriptorScriptPubKeyMan (Ava Chow) 4a273ed sign: Create MuSig2 signatures for known MuSig2 aggregate keys (Ava Chow) 258db93 sign: Add CreateMuSig2AggregateSig (Ava Chow) bf69442 sign: Add CreateMuSig2PartialSig (Ava Chow) 512b17f sign: Add CreateMuSig2Nonce (Ava Chow) 82ea67c musig: Add MuSig2AggregatePubkeys variant that validates the aggregate (Ava Chow) d99a081 psbt: MuSig2 data in Fill/FromSignatureData (Ava Chow) 4d8b4f5 signingprovider: Add musig2 secnonces (Ava Chow) c06a1dc Add MuSig2SecNonce class for secure allocation of musig nonces (Ava Chow) 9baff05 sign: Include taproot output key's KeyOriginInfo in sigdata (Ava Chow) 4b24bfe pubkey: Return tweaks from BIP32 derivation (Ava Chow) f148762 musig: Move synthetic xpub construction to its own function (Ava Chow) fb8720f sign: Refactor Schnorr sighash computation out of CreateSchnorrSig (Ava Chow) a4cfddd tests: Clarify why musig derivation adds a pubkey and xpub (Ava Chow) 39a63bf descriptors: Add a doxygen comment for has_hardened output_parameter (Ava Chow) 2320184 descriptors: 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: tACK ac599c4 rkrux: lgtm tACK ac599c4 theStack: Code-review ACK ac599c4 🗝️ Tree-SHA512: 626b9adc42ed2403e2f4405321eb9ce009a829c07d968e95ab288fe4940b195b0af35ca279a4a7fa51af76e55382bad6f63a23bca14a84140559b3c667e7041e
2 parents db4bde0 + ac599c4 commit 48aa0e9

File tree

18 files changed

+976
-47
lines changed

18 files changed

+976
-47
lines changed

src/key.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <secp256k1.h>
1414
#include <secp256k1_ellswift.h>
1515
#include <secp256k1_extrakeys.h>
16+
#include <secp256k1_musig.h>
1617
#include <secp256k1_recovery.h>
1718
#include <secp256k1_schnorrsig.h>
1819

@@ -349,6 +350,128 @@ KeyPair CKey::ComputeKeyPair(const uint256* merkle_root) const
349350
return KeyPair(*this, merkle_root);
350351
}
351352

353+
std::vector<uint8_t> CKey::CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys)
354+
{
355+
// Get the keyagg cache and aggregate pubkey
356+
secp256k1_musig_keyagg_cache keyagg_cache;
357+
if (!MuSig2AggregatePubkeys(pubkeys, keyagg_cache, aggregate_pubkey)) return {};
358+
359+
// Parse participant pubkey
360+
CPubKey our_pubkey = GetPubKey();
361+
secp256k1_pubkey pubkey;
362+
if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, our_pubkey.data(), our_pubkey.size())) {
363+
return {};
364+
}
365+
366+
// Generate randomness for nonce
367+
uint256 rand;
368+
GetStrongRandBytes(rand);
369+
370+
// Generate nonce
371+
secp256k1_musig_pubnonce pubnonce;
372+
if (!secp256k1_musig_nonce_gen(secp256k1_context_sign, secnonce.Get(), &pubnonce, rand.data(), UCharCast(begin()), &pubkey, sighash.data(), &keyagg_cache, nullptr)) {
373+
return {};
374+
}
375+
376+
// Serialize pubnonce
377+
std::vector<uint8_t> out;
378+
out.resize(MUSIG2_PUBNONCE_SIZE);
379+
if (!secp256k1_musig_pubnonce_serialize(secp256k1_context_static, out.data(), &pubnonce)) {
380+
return {};
381+
}
382+
383+
return out;
384+
}
385+
386+
std::optional<uint256> CKey::CreateMuSig2PartialSig(const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys, const std::map<CPubKey, std::vector<uint8_t>>& pubnonces, MuSig2SecNonce& secnonce, const std::vector<std::pair<uint256, bool>>& tweaks)
387+
{
388+
secp256k1_keypair keypair;
389+
if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, UCharCast(begin()))) return std::nullopt;
390+
391+
// Get the keyagg cache and aggregate pubkey
392+
secp256k1_musig_keyagg_cache keyagg_cache;
393+
if (!MuSig2AggregatePubkeys(pubkeys, keyagg_cache, aggregate_pubkey)) return std::nullopt;
394+
395+
// Check that there are enough pubnonces
396+
if (pubnonces.size() != pubkeys.size()) return std::nullopt;
397+
398+
// Parse the pubnonces
399+
std::vector<std::pair<secp256k1_pubkey, secp256k1_musig_pubnonce>> signers_data;
400+
std::vector<const secp256k1_musig_pubnonce*> pubnonce_ptrs;
401+
std::optional<size_t> our_pubkey_idx;
402+
CPubKey our_pubkey = GetPubKey();
403+
for (const CPubKey& part_pk : pubkeys) {
404+
const auto& pn_it = pubnonces.find(part_pk);
405+
if (pn_it == pubnonces.end()) return std::nullopt;
406+
const std::vector<uint8_t> pubnonce = pn_it->second;
407+
if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) return std::nullopt;
408+
if (part_pk == our_pubkey) {
409+
our_pubkey_idx = signers_data.size();
410+
}
411+
412+
auto& [secp_pk, secp_pn] = signers_data.emplace_back();
413+
414+
if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pk, part_pk.data(), part_pk.size())) {
415+
return std::nullopt;
416+
}
417+
418+
if (!secp256k1_musig_pubnonce_parse(secp256k1_context_static, &secp_pn, pubnonce.data())) {
419+
return std::nullopt;
420+
}
421+
}
422+
if (our_pubkey_idx == std::nullopt) {
423+
return std::nullopt;
424+
}
425+
pubnonce_ptrs.reserve(signers_data.size());
426+
for (auto& [_, pn] : signers_data) {
427+
pubnonce_ptrs.push_back(&pn);
428+
}
429+
430+
// Aggregate nonces
431+
secp256k1_musig_aggnonce aggnonce;
432+
if (!secp256k1_musig_nonce_agg(secp256k1_context_static, &aggnonce, pubnonce_ptrs.data(), pubnonce_ptrs.size())) {
433+
return std::nullopt;
434+
}
435+
436+
// Apply tweaks
437+
for (const auto& [tweak, xonly] : tweaks) {
438+
if (xonly) {
439+
if (!secp256k1_musig_pubkey_xonly_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) {
440+
return std::nullopt;
441+
}
442+
} else if (!secp256k1_musig_pubkey_ec_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) {
443+
return std::nullopt;
444+
}
445+
}
446+
447+
// Create musig_session
448+
secp256k1_musig_session session;
449+
if (!secp256k1_musig_nonce_process(secp256k1_context_static, &session, &aggnonce, sighash.data(), &keyagg_cache)) {
450+
return std::nullopt;
451+
}
452+
453+
// Create partial signature
454+
secp256k1_musig_partial_sig psig;
455+
if (!secp256k1_musig_partial_sign(secp256k1_context_static, &psig, secnonce.Get(), &keypair, &keyagg_cache, &session)) {
456+
return std::nullopt;
457+
}
458+
// The secnonce must be deleted after signing to prevent nonce reuse.
459+
secnonce.Invalidate();
460+
461+
// Verify partial signature
462+
if (!secp256k1_musig_partial_sig_verify(secp256k1_context_static, &psig, &(signers_data.at(*our_pubkey_idx).second), &(signers_data.at(*our_pubkey_idx).first), &keyagg_cache, &session)) {
463+
return std::nullopt;
464+
}
465+
466+
// Serialize
467+
uint256 sig;
468+
if (!secp256k1_musig_partial_sig_serialize(secp256k1_context_static, sig.data(), &psig)) {
469+
return std::nullopt;
470+
}
471+
472+
return sig;
473+
}
474+
352475
CKey GenerateRandomKey(bool compressed) noexcept
353476
{
354477
CKey key;

src/key.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#ifndef BITCOIN_KEY_H
88
#define BITCOIN_KEY_H
99

10+
#include <musig.h>
1011
#include <pubkey.h>
1112
#include <serialize.h>
1213
#include <support/allocators/secure.h>
@@ -220,6 +221,9 @@ class CKey
220221
* Merkle root of the script tree).
221222
*/
222223
KeyPair ComputeKeyPair(const uint256* merkle_root) const;
224+
225+
std::vector<uint8_t> CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys);
226+
std::optional<uint256> CreateMuSig2PartialSig(const uint256& hash, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys, const std::map<CPubKey, std::vector<uint8_t>>& pubnonces, MuSig2SecNonce& secnonce, const std::vector<std::pair<uint256, bool>>& tweaks);
223227
};
224228

225229
CKey GenerateRandomKey(bool compressed = true) noexcept;

src/musig.cpp

Lines changed: 157 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <musig.h>
6+
#include <support/allocators/secure.h>
67

78
#include <secp256k1_musig.h>
89

9-
bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
10+
static bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
1011
{
1112
// Parse the pubkeys
1213
std::vector<secp256k1_pubkey> secp_pubkeys;
@@ -28,7 +29,7 @@ bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_k
2829
return true;
2930
}
3031

31-
std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache)
32+
static std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache)
3233
{
3334
// Get the plain aggregated pubkey
3435
secp256k1_pubkey agg_pubkey;
@@ -43,11 +44,163 @@ std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_ca
4344
return CPubKey(ser_agg_pubkey, ser_agg_pubkey + ser_agg_pubkey_len);
4445
}
4546

47+
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional<CPubKey>& expected_aggregate)
48+
{
49+
if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) {
50+
return std::nullopt;
51+
}
52+
std::optional<CPubKey> agg_key = GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache);
53+
if (!agg_key.has_value()) return std::nullopt;
54+
if (expected_aggregate.has_value() && expected_aggregate != agg_key) return std::nullopt;
55+
return agg_key;
56+
}
57+
4658
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys)
4759
{
4860
secp256k1_musig_keyagg_cache keyagg_cache;
49-
if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) {
61+
return MuSig2AggregatePubkeys(pubkeys, keyagg_cache, std::nullopt);
62+
}
63+
64+
CExtPubKey CreateMuSig2SyntheticXpub(const CPubKey& pubkey)
65+
{
66+
CExtPubKey extpub;
67+
extpub.nDepth = 0;
68+
std::memset(extpub.vchFingerprint, 0, 4);
69+
extpub.nChild = 0;
70+
extpub.chaincode = MUSIG_CHAINCODE;
71+
extpub.pubkey = pubkey;
72+
return extpub;
73+
}
74+
75+
class MuSig2SecNonceImpl
76+
{
77+
private:
78+
//! The actual secnonce itself
79+
secure_unique_ptr<secp256k1_musig_secnonce> m_nonce;
80+
81+
public:
82+
MuSig2SecNonceImpl() : m_nonce{make_secure_unique<secp256k1_musig_secnonce>()} {}
83+
84+
// Delete copy constructors
85+
MuSig2SecNonceImpl(const MuSig2SecNonceImpl&) = delete;
86+
MuSig2SecNonceImpl& operator=(const MuSig2SecNonceImpl&) = delete;
87+
88+
secp256k1_musig_secnonce* Get() const { return m_nonce.get(); }
89+
void Invalidate() { m_nonce.reset(); }
90+
bool IsValid() { return m_nonce != nullptr; }
91+
};
92+
93+
MuSig2SecNonce::MuSig2SecNonce() : m_impl{std::make_unique<MuSig2SecNonceImpl>()} {}
94+
95+
MuSig2SecNonce::MuSig2SecNonce(MuSig2SecNonce&&) noexcept = default;
96+
MuSig2SecNonce& MuSig2SecNonce::operator=(MuSig2SecNonce&&) noexcept = default;
97+
98+
MuSig2SecNonce::~MuSig2SecNonce() = default;
99+
100+
secp256k1_musig_secnonce* MuSig2SecNonce::Get() const
101+
{
102+
return m_impl->Get();
103+
}
104+
105+
void MuSig2SecNonce::Invalidate()
106+
{
107+
return m_impl->Invalidate();
108+
}
109+
110+
bool MuSig2SecNonce::IsValid()
111+
{
112+
return m_impl->IsValid();
113+
}
114+
115+
uint256 MuSig2SessionID(const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256& sighash)
116+
{
117+
HashWriter hasher;
118+
hasher << script_pubkey << part_pubkey << sighash;
119+
return hasher.GetSHA256();
120+
}
121+
122+
std::optional<std::vector<uint8_t>> CreateMuSig2AggregateSig(const std::vector<CPubKey>& part_pubkeys, const CPubKey& aggregate_pubkey, const std::vector<std::pair<uint256, bool>>& tweaks, const uint256& sighash, const std::map<CPubKey, std::vector<uint8_t>>& pubnonces, const std::map<CPubKey, uint256>& partial_sigs)
123+
{
124+
if (!part_pubkeys.size()) return std::nullopt;
125+
126+
// Get the keyagg cache and aggregate pubkey
127+
secp256k1_musig_keyagg_cache keyagg_cache;
128+
if (!MuSig2AggregatePubkeys(part_pubkeys, keyagg_cache, aggregate_pubkey)) return std::nullopt;
129+
130+
// Check if enough pubnonces and partial sigs
131+
if (pubnonces.size() != part_pubkeys.size()) return std::nullopt;
132+
if (partial_sigs.size() != part_pubkeys.size()) return std::nullopt;
133+
134+
// Parse the pubnonces and partial sigs
135+
std::vector<std::tuple<secp256k1_pubkey, secp256k1_musig_pubnonce, secp256k1_musig_partial_sig>> signers_data;
136+
std::vector<const secp256k1_musig_pubnonce*> pubnonce_ptrs;
137+
std::vector<const secp256k1_musig_partial_sig*> partial_sig_ptrs;
138+
for (const CPubKey& part_pk : part_pubkeys) {
139+
const auto& pn_it = pubnonces.find(part_pk);
140+
if (pn_it == pubnonces.end()) return std::nullopt;
141+
const std::vector<uint8_t> pubnonce = pn_it->second;
142+
if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) return std::nullopt;
143+
const auto& it = partial_sigs.find(part_pk);
144+
if (it == partial_sigs.end()) return std::nullopt;
145+
const uint256& partial_sig = it->second;
146+
147+
auto& [secp_pk, secp_pn, secp_ps] = signers_data.emplace_back();
148+
149+
if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pk, part_pk.data(), part_pk.size())) {
150+
return std::nullopt;
151+
}
152+
153+
if (!secp256k1_musig_pubnonce_parse(secp256k1_context_static, &secp_pn, pubnonce.data())) {
154+
return std::nullopt;
155+
}
156+
157+
if (!secp256k1_musig_partial_sig_parse(secp256k1_context_static, &secp_ps, partial_sig.data())) {
158+
return std::nullopt;
159+
}
160+
}
161+
pubnonce_ptrs.reserve(signers_data.size());
162+
partial_sig_ptrs.reserve(signers_data.size());
163+
for (auto& [_, pn, ps] : signers_data) {
164+
pubnonce_ptrs.push_back(&pn);
165+
partial_sig_ptrs.push_back(&ps);
166+
}
167+
168+
// Aggregate nonces
169+
secp256k1_musig_aggnonce aggnonce;
170+
if (!secp256k1_musig_nonce_agg(secp256k1_context_static, &aggnonce, pubnonce_ptrs.data(), pubnonce_ptrs.size())) {
171+
return std::nullopt;
172+
}
173+
174+
// Apply tweaks
175+
for (const auto& [tweak, xonly] : tweaks) {
176+
if (xonly) {
177+
if (!secp256k1_musig_pubkey_xonly_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) {
178+
return std::nullopt;
179+
}
180+
} else if (!secp256k1_musig_pubkey_ec_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) {
181+
return std::nullopt;
182+
}
183+
}
184+
185+
// Create musig_session
186+
secp256k1_musig_session session;
187+
if (!secp256k1_musig_nonce_process(secp256k1_context_static, &session, &aggnonce, sighash.data(), &keyagg_cache)) {
50188
return std::nullopt;
51189
}
52-
return GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache);
190+
191+
// Verify partial sigs
192+
for (const auto& [pk, pb, ps] : signers_data) {
193+
if (!secp256k1_musig_partial_sig_verify(secp256k1_context_static, &ps, &pb, &pk, &keyagg_cache, &session)) {
194+
return std::nullopt;
195+
}
196+
}
197+
198+
// Aggregate partial sigs
199+
std::vector<uint8_t> sig;
200+
sig.resize(64);
201+
if (!secp256k1_musig_partial_sig_agg(secp256k1_context_static, sig.data(), &session, partial_sig_ptrs.data(), partial_sig_ptrs.size())) {
202+
return std::nullopt;
203+
}
204+
205+
return sig;
53206
}

0 commit comments

Comments
 (0)