Bitcoin Core Fuzz Coverage Report

Coverage Report

Created: 2026-06-01 16:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/zip/work/bitcoin/src/musig.cpp
Line
Count
Source
1
// Copyright (c) 2024-present The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <musig.h>
6
#include <key.h>
7
#include <random.h>
8
#include <support/allocators/secure.h>
9
10
#include <secp256k1_musig.h>
11
12
//! MuSig2 chaincode as defined by BIP 328
13
using namespace util::hex_literals;
14
constexpr uint256 MUSIG_CHAINCODE{
15
    // Use immediate lambda to work around GCC-14 bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117966
16
    []() consteval { return uint256{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8}; }(),
17
};
18
19
static bool GetMuSig2KeyAggCache(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache)
20
0
{
21
0
    if (pubkeys.empty()) {
22
0
        return false;
23
0
    }
24
25
    // Parse the pubkeys
26
0
    std::vector<secp256k1_pubkey> secp_pubkeys;
27
0
    std::vector<const secp256k1_pubkey*> pubkey_ptrs;
28
0
    for (const CPubKey& pubkey : pubkeys) {
29
0
        if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pubkeys.emplace_back(), pubkey.data(), pubkey.size())) {
30
0
            return false;
31
0
        }
32
0
    }
33
0
    pubkey_ptrs.reserve(secp_pubkeys.size());
34
0
    for (const secp256k1_pubkey& p : secp_pubkeys) {
35
0
        pubkey_ptrs.push_back(&p);
36
0
    }
37
38
    // Aggregate the pubkey
39
0
    if (!secp256k1_musig_pubkey_agg(secp256k1_context_static, nullptr, &keyagg_cache, pubkey_ptrs.data(), pubkey_ptrs.size())) {
40
0
        return false;
41
0
    }
42
0
    return true;
43
0
}
44
45
static std::optional<CPubKey> GetCPubKeyFromMuSig2KeyAggCache(secp256k1_musig_keyagg_cache& keyagg_cache)
46
0
{
47
    // Get the plain aggregated pubkey
48
0
    secp256k1_pubkey agg_pubkey;
49
0
    if (!secp256k1_musig_pubkey_get(secp256k1_context_static, &agg_pubkey, &keyagg_cache)) {
50
0
        return std::nullopt;
51
0
    }
52
53
    // Turn into CPubKey
54
0
    unsigned char ser_agg_pubkey[CPubKey::COMPRESSED_SIZE];
55
0
    size_t ser_agg_pubkey_len = CPubKey::COMPRESSED_SIZE;
56
0
    secp256k1_ec_pubkey_serialize(secp256k1_context_static, ser_agg_pubkey, &ser_agg_pubkey_len, &agg_pubkey, SECP256K1_EC_COMPRESSED);
Line
Count
Source
225
0
#define SECP256K1_EC_COMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
Line
Count
Source
206
0
#define SECP256K1_FLAGS_TYPE_COMPRESSION (1 << 1)
#define SECP256K1_EC_COMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
Line
Count
Source
211
0
#define SECP256K1_FLAGS_BIT_COMPRESSION (1 << 8)
57
0
    return CPubKey(ser_agg_pubkey, ser_agg_pubkey + ser_agg_pubkey_len);
58
0
}
59
60
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache, const std::optional<CPubKey>& expected_aggregate)
61
0
{
62
0
    if (!GetMuSig2KeyAggCache(pubkeys, keyagg_cache)) {
63
0
        return std::nullopt;
64
0
    }
65
0
    std::optional<CPubKey> agg_key = GetCPubKeyFromMuSig2KeyAggCache(keyagg_cache);
66
0
    if (!agg_key.has_value()) return std::nullopt;
67
0
    if (expected_aggregate.has_value() && expected_aggregate != agg_key) return std::nullopt;
68
0
    return agg_key;
69
0
}
70
71
std::optional<CPubKey> MuSig2AggregatePubkeys(const std::vector<CPubKey>& pubkeys)
72
0
{
73
0
    secp256k1_musig_keyagg_cache keyagg_cache;
74
0
    return MuSig2AggregatePubkeys(pubkeys, keyagg_cache, std::nullopt);
75
0
}
76
77
CExtPubKey CreateMuSig2SyntheticXpub(const CPubKey& pubkey)
78
0
{
79
0
    CExtPubKey extpub;
80
0
    extpub.nDepth = 0;
81
0
    std::memset(extpub.vchFingerprint, 0, 4);
82
0
    extpub.nChild = 0;
83
0
    extpub.chaincode = MUSIG_CHAINCODE;
84
0
    extpub.pubkey = pubkey;
85
0
    return extpub;
86
0
}
87
88
class MuSig2SecNonceImpl
89
{
90
private:
91
    //! The actual secnonce itself
92
    secure_unique_ptr<secp256k1_musig_secnonce> m_nonce;
93
94
public:
95
0
    MuSig2SecNonceImpl() : m_nonce{make_secure_unique<secp256k1_musig_secnonce>()} {}
96
97
    // Delete copy constructors
98
    MuSig2SecNonceImpl(const MuSig2SecNonceImpl&) = delete;
99
    MuSig2SecNonceImpl& operator=(const MuSig2SecNonceImpl&) = delete;
100
101
0
    secp256k1_musig_secnonce* Get() const { return m_nonce.get(); }
102
0
    void Invalidate() { m_nonce.reset(); }
103
0
    bool IsValid() { return m_nonce != nullptr; }
104
};
105
106
0
MuSig2SecNonce::MuSig2SecNonce() : m_impl{std::make_unique<MuSig2SecNonceImpl>()} {}
107
108
0
MuSig2SecNonce::MuSig2SecNonce(MuSig2SecNonce&&) noexcept = default;
109
0
MuSig2SecNonce& MuSig2SecNonce::operator=(MuSig2SecNonce&&) noexcept = default;
110
111
0
MuSig2SecNonce::~MuSig2SecNonce() = default;
112
113
secp256k1_musig_secnonce* MuSig2SecNonce::Get() const
114
0
{
115
0
    return m_impl->Get();
116
0
}
117
118
void MuSig2SecNonce::Invalidate()
119
0
{
120
0
    return m_impl->Invalidate();
121
0
}
122
123
bool MuSig2SecNonce::IsValid()
124
0
{
125
0
    return m_impl->IsValid();
126
0
}
127
128
uint256 MuSig2SessionID(const CPubKey& script_pubkey, const CPubKey& part_pubkey, const uint256& sighash)
129
0
{
130
0
    HashWriter hasher;
131
0
    hasher << script_pubkey << part_pubkey << sighash;
132
0
    return hasher.GetSHA256();
133
0
}
134
135
std::vector<uint8_t> CreateMuSig2Nonce(MuSig2SecNonce& secnonce, const uint256& sighash, const CKey& our_seckey, const CPubKey& aggregate_pubkey, const std::vector<CPubKey>& pubkeys)
136
0
{
137
    // Get the keyagg cache and aggregate pubkey
138
0
    secp256k1_musig_keyagg_cache keyagg_cache;
139
0
    if (!MuSig2AggregatePubkeys(pubkeys, keyagg_cache, aggregate_pubkey)) return {};
140
141
    // Parse participant pubkey
142
0
    CPubKey our_pubkey = our_seckey.GetPubKey();
143
0
    secp256k1_pubkey pubkey;
144
0
    if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, our_pubkey.data(), our_pubkey.size())) {
145
0
        return {};
146
0
    }
147
148
    // Generate randomness for nonce
149
0
    uint256 rand;
150
0
    GetStrongRandBytes(rand);
151
152
    // Generate nonce
153
0
    secp256k1_musig_pubnonce pubnonce;
154
0
    if (!secp256k1_musig_nonce_gen(GetSecp256k1SignContext(), secnonce.Get(), &pubnonce, rand.data(), UCharCast(our_seckey.begin()), &pubkey, sighash.data(), &keyagg_cache, nullptr)) {
155
0
        return {};
156
0
    }
157
158
    // Serialize pubnonce
159
0
    std::vector<uint8_t> out;
160
0
    out.resize(MUSIG2_PUBNONCE_SIZE);
161
0
    if (!secp256k1_musig_pubnonce_serialize(secp256k1_context_static, out.data(), &pubnonce)) {
162
0
        return {};
163
0
    }
164
165
0
    return out;
166
0
}
167
168
std::optional<uint256> CreateMuSig2PartialSig(const uint256& sighash, const CKey& our_seckey, 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)
169
0
{
170
0
    secp256k1_keypair keypair;
171
0
    if (!secp256k1_keypair_create(GetSecp256k1SignContext(), &keypair, UCharCast(our_seckey.begin()))) return std::nullopt;
172
173
    // Get the keyagg cache and aggregate pubkey
174
0
    secp256k1_musig_keyagg_cache keyagg_cache;
175
0
    if (!MuSig2AggregatePubkeys(pubkeys, keyagg_cache, aggregate_pubkey)) return std::nullopt;
176
177
    // Check that there are enough pubnonces
178
0
    if (pubnonces.size() != pubkeys.size()) return std::nullopt;
179
180
    // Parse the pubnonces
181
0
    std::vector<std::pair<secp256k1_pubkey, secp256k1_musig_pubnonce>> signers_data;
182
0
    std::vector<const secp256k1_musig_pubnonce*> pubnonce_ptrs;
183
0
    std::optional<size_t> our_pubkey_idx;
184
0
    CPubKey our_pubkey = our_seckey.GetPubKey();
185
0
    for (const CPubKey& part_pk : pubkeys) {
186
0
        const auto& pn_it = pubnonces.find(part_pk);
187
0
        if (pn_it == pubnonces.end()) return std::nullopt;
188
0
        const std::vector<uint8_t> pubnonce = pn_it->second;
189
0
        if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) return std::nullopt;
190
0
        if (part_pk == our_pubkey) {
191
0
            our_pubkey_idx = signers_data.size();
192
0
        }
193
194
0
        auto& [secp_pk, secp_pn] = signers_data.emplace_back();
195
196
0
        if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pk, part_pk.data(), part_pk.size())) {
197
0
            return std::nullopt;
198
0
        }
199
200
0
        if (!secp256k1_musig_pubnonce_parse(secp256k1_context_static, &secp_pn, pubnonce.data())) {
201
0
            return std::nullopt;
202
0
        }
203
0
    }
204
0
    if (our_pubkey_idx == std::nullopt) {
205
0
        return std::nullopt;
206
0
    }
207
0
    pubnonce_ptrs.reserve(signers_data.size());
208
0
    for (auto& [_, pn] : signers_data) {
209
0
        pubnonce_ptrs.push_back(&pn);
210
0
    }
211
212
    // Aggregate nonces
213
0
    secp256k1_musig_aggnonce aggnonce;
214
0
    if (!secp256k1_musig_nonce_agg(secp256k1_context_static, &aggnonce, pubnonce_ptrs.data(), pubnonce_ptrs.size())) {
215
0
        return std::nullopt;
216
0
    }
217
218
    // Apply tweaks
219
0
    for (const auto& [tweak, xonly] : tweaks) {
220
0
        if (xonly) {
221
0
            if (!secp256k1_musig_pubkey_xonly_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) {
222
0
                return std::nullopt;
223
0
            }
224
0
        } else if (!secp256k1_musig_pubkey_ec_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) {
225
0
            return std::nullopt;
226
0
        }
227
0
    }
228
229
    // Create musig_session
230
0
    secp256k1_musig_session session;
231
0
    if (!secp256k1_musig_nonce_process(secp256k1_context_static, &session, &aggnonce, sighash.data(), &keyagg_cache)) {
232
0
        return std::nullopt;
233
0
    }
234
235
    // Create partial signature
236
0
    secp256k1_musig_partial_sig psig;
237
0
    if (!secp256k1_musig_partial_sign(secp256k1_context_static, &psig, secnonce.Get(), &keypair, &keyagg_cache, &session)) {
238
0
        return std::nullopt;
239
0
    }
240
    // The secnonce must be deleted after signing to prevent nonce reuse.
241
0
    secnonce.Invalidate();
242
243
    // Verify partial signature
244
0
    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)) {
245
0
        return std::nullopt;
246
0
    }
247
248
    // Serialize
249
0
    uint256 sig;
250
0
    if (!secp256k1_musig_partial_sig_serialize(secp256k1_context_static, sig.data(), &psig)) {
251
0
        return std::nullopt;
252
0
    }
253
254
0
    return sig;
255
0
}
256
257
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)
258
0
{
259
0
    if (!part_pubkeys.size()) return std::nullopt;
260
261
    // Get the keyagg cache and aggregate pubkey
262
0
    secp256k1_musig_keyagg_cache keyagg_cache;
263
0
    if (!MuSig2AggregatePubkeys(part_pubkeys, keyagg_cache, aggregate_pubkey)) return std::nullopt;
264
265
    // Check if enough pubnonces and partial sigs
266
0
    if (pubnonces.size() != part_pubkeys.size()) return std::nullopt;
267
0
    if (partial_sigs.size() != part_pubkeys.size()) return std::nullopt;
268
269
    // Parse the pubnonces and partial sigs
270
0
    std::vector<std::tuple<secp256k1_pubkey, secp256k1_musig_pubnonce, secp256k1_musig_partial_sig>> signers_data;
271
0
    std::vector<const secp256k1_musig_pubnonce*> pubnonce_ptrs;
272
0
    std::vector<const secp256k1_musig_partial_sig*> partial_sig_ptrs;
273
0
    for (const CPubKey& part_pk : part_pubkeys) {
274
0
        const auto& pn_it = pubnonces.find(part_pk);
275
0
        if (pn_it == pubnonces.end()) return std::nullopt;
276
0
        const std::vector<uint8_t> pubnonce = pn_it->second;
277
0
        if (pubnonce.size() != MUSIG2_PUBNONCE_SIZE) return std::nullopt;
278
0
        const auto& it = partial_sigs.find(part_pk);
279
0
        if (it == partial_sigs.end()) return std::nullopt;
280
0
        const uint256& partial_sig = it->second;
281
282
0
        auto& [secp_pk, secp_pn, secp_ps] = signers_data.emplace_back();
283
284
0
        if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &secp_pk, part_pk.data(), part_pk.size())) {
285
0
            return std::nullopt;
286
0
        }
287
288
0
        if (!secp256k1_musig_pubnonce_parse(secp256k1_context_static, &secp_pn, pubnonce.data())) {
289
0
            return std::nullopt;
290
0
        }
291
292
0
        if (!secp256k1_musig_partial_sig_parse(secp256k1_context_static, &secp_ps, partial_sig.data())) {
293
0
            return std::nullopt;
294
0
        }
295
0
    }
296
0
    pubnonce_ptrs.reserve(signers_data.size());
297
0
    partial_sig_ptrs.reserve(signers_data.size());
298
0
    for (auto& [_, pn, ps] : signers_data) {
299
0
        pubnonce_ptrs.push_back(&pn);
300
0
        partial_sig_ptrs.push_back(&ps);
301
0
    }
302
303
    // Aggregate nonces
304
0
    secp256k1_musig_aggnonce aggnonce;
305
0
    if (!secp256k1_musig_nonce_agg(secp256k1_context_static, &aggnonce, pubnonce_ptrs.data(), pubnonce_ptrs.size())) {
306
0
        return std::nullopt;
307
0
    }
308
309
    // Apply tweaks
310
0
    for (const auto& [tweak, xonly] : tweaks) {
311
0
        if (xonly) {
312
0
            if (!secp256k1_musig_pubkey_xonly_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) {
313
0
                return std::nullopt;
314
0
            }
315
0
        } else if (!secp256k1_musig_pubkey_ec_tweak_add(secp256k1_context_static, nullptr, &keyagg_cache, tweak.data())) {
316
0
            return std::nullopt;
317
0
        }
318
0
    }
319
320
    // Create musig_session
321
0
    secp256k1_musig_session session;
322
0
    if (!secp256k1_musig_nonce_process(secp256k1_context_static, &session, &aggnonce, sighash.data(), &keyagg_cache)) {
323
0
        return std::nullopt;
324
0
    }
325
326
    // Verify partial sigs
327
0
    for (const auto& [pk, pb, ps] : signers_data) {
328
0
        if (!secp256k1_musig_partial_sig_verify(secp256k1_context_static, &ps, &pb, &pk, &keyagg_cache, &session)) {
329
0
            return std::nullopt;
330
0
        }
331
0
    }
332
333
    // Aggregate partial sigs
334
0
    std::vector<uint8_t> sig;
335
0
    sig.resize(64);
336
0
    if (!secp256k1_musig_partial_sig_agg(secp256k1_context_static, sig.data(), &session, partial_sig_ptrs.data(), partial_sig_ptrs.size())) {
337
0
        return std::nullopt;
338
0
    }
339
340
0
    return sig;
341
0
}