• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

libbitcoin / libbitcoin-system / 14945433772

10 May 2025 12:40PM UTC coverage: 82.466% (-0.01%) from 82.479%
14945433772

push

github

web-flow
Merge pull request #1657 from evoskuil/master

Refactoring to support tapscript signature hash generation.

48 of 63 new or added lines in 6 files covered. (76.19%)

10 existing lines in 6 files now uncovered.

10286 of 12473 relevant lines covered (82.47%)

3862936.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.53
/src/crypto/secp256k1.cpp
1
/**
2
 * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
3
 *
4
 * This file is part of libbitcoin.
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
#include <bitcoin/system/crypto/secp256k1.hpp>
20

21
#include <algorithm>
22
#include <utility>
23
#include <secp256k1.h>
24
#include <secp256k1_recovery.h>
25
#include <secp256k1_schnorrsig.h>
26
#include <bitcoin/system/crypto/der_parser.hpp>
27
#include <bitcoin/system/data/data.hpp>
28
#include <bitcoin/system/hash/hash.hpp>
29
#include <bitcoin/system/math/math.hpp>
30
#include "ec_context.hpp"
31

32
namespace libbitcoin {
33
namespace system {
34

35
static constexpr auto ec_success = 1;
36
static constexpr auto maximum_recovery_id = 3;
37
static constexpr auto compressed_even = 0x02_u8;
38
static constexpr auto compressed_odd = 0x03_u8;
39
static constexpr auto uncompressed = 0x04_u8;
40
static constexpr auto hybrid_even = 0x06_u8;
41
static constexpr auto hybrid_odd = 0x07_u8;
42

43
// Local functions.
44
// ----------------------------------------------------------------------------
45
// The templates allow strong typing of private keys without redundant code.
46

47
constexpr int to_flags(bool compressed) NOEXCEPT
48
{
49
    return compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED;
50
}
51

52
static bool parse(const secp256k1_context* context, secp256k1_pubkey& out,
488✔
53
    const data_slice& point) NOEXCEPT
54
{
55
    if (point.empty())
488✔
56
        return false;
57

58
    // secp256k1_ec_pubkey_parse supports compressed (33 bytes, header byte 0x02
59
    // or 0x03), uncompressed (65 bytes, header byte 0x04), or hybrid (65 bytes,
60
    // header byte 0x06 or 0x07) format public keys.
61
    return secp256k1_ec_pubkey_parse(context, &out, point.data(), point.size())
488✔
62
        == ec_success;
488✔
63
}
64

65
static bool parse(const secp256k1_context* context,
95✔
66
    std::vector<secp256k1_pubkey>& out, const compressed_list& points) NOEXCEPT
67
{
68
    out.resize(points.size());
95✔
69
    auto key = out.begin();
95✔
70

71
    for (const auto& point: points)
287✔
72
        if (!parse(context, *key++, point))
192✔
73
            return false;
74

75
    return true;
76
}
77

78
// Create an array of secp256k1_pubkey pointers for secp256k1 call.
79
static std::vector<const secp256k1_pubkey*> to_pointers(
95✔
80
    const std::vector<secp256k1_pubkey>& keys) NOEXCEPT
81
{
82
    std::vector<const secp256k1_pubkey*> pointers(keys.size());
95✔
83

84
    std::transform(keys.begin(), keys.end(), pointers.begin(),
95✔
85
        [](const secp256k1_pubkey& point) NOEXCEPT
86
        {
87
            return &point;
88
        });
89

90
    return pointers;
95✔
91
}
92

93
template <size_t Size>
94
static bool serialize(const secp256k1_context* context, data_array<Size>& out,
734✔
95
    const secp256k1_pubkey point) NOEXCEPT
96
{
97
    auto size = Size;
734✔
98
    constexpr auto flags = to_flags(Size == ec_compressed_size);
99
    secp256k1_ec_pubkey_serialize(context, out.data(), &size, &point, flags);
734✔
100
    return size == Size;
734✔
101
}
102

103
// parse, add, serialize
104
template <size_t Size>
105
static bool ec_add(const secp256k1_context* context, data_array<Size>& in_out,
14✔
106
    const ec_secret& secret) NOEXCEPT
107
{
108
    secp256k1_pubkey pubkey;
109
    return parse(context, pubkey, in_out) &&
28✔
110
        secp256k1_ec_pubkey_tweak_add(context, &pubkey, secret.data()) ==
14✔
111
            ec_success && serialize(context, in_out, pubkey);
27✔
112
}
113

114
// parse, multiply, serialize
115
template <size_t Size>
116
static bool ec_multiply(const secp256k1_context* context,
200✔
117
    data_array<Size>& in_out, const ec_secret& secret) NOEXCEPT
118
{
119
    secp256k1_pubkey pubkey;
120
    return parse(context, pubkey, in_out) &&
400✔
121
        secp256k1_ec_pubkey_tweak_mul(context, &pubkey, secret.data()) ==
200✔
122
            ec_success && serialize(context, in_out, pubkey);
400✔
123
}
124

125
// parse, negate, serialize
126
template <size_t Size>
127
static bool ec_negate(const secp256k1_context* context,
×
128
    data_array<Size>& in_out) NOEXCEPT
129
{
130
    secp256k1_pubkey pubkey;
131
    return parse(context, pubkey, in_out) &&
×
132
        secp256k1_ec_pubkey_negate(context, &pubkey) == ec_success &&
×
133
        serialize(context, in_out, pubkey);
×
134
}
135

136
// create, serialize (secrets are normal)
137
template <size_t Size>
138
static bool secret_to_public(const secp256k1_context* context,
393✔
139
    data_array<Size>& out, const ec_secret& secret) NOEXCEPT
140
{
141
    secp256k1_pubkey pubkey;
142
    return secp256k1_ec_pubkey_create(context, &pubkey, secret.data()) ==
393✔
143
        ec_success && serialize(context, out, pubkey);
393✔
144
}
145

146
// parse, recover, serialize
147
template <size_t Size>
148
static bool recover_public(const secp256k1_context* context,
6✔
149
    data_array<Size>& out, const recoverable_signature& recoverable,
150
    const hash_digest& hash) NOEXCEPT
151
{
152
    secp256k1_pubkey pubkey;
153
    secp256k1_ecdsa_recoverable_signature sign;
154
    const auto recovery_id = static_cast<int>(recoverable.recovery_id);
6✔
155
    return
156
        secp256k1_ecdsa_recoverable_signature_parse_compact(context,
6✔
157
            &sign, recoverable.signature.data(), recovery_id) == ec_success &&
6✔
158
        secp256k1_ecdsa_recover(context, &pubkey, &sign, hash.data()) ==
6✔
159
            ec_success && serialize(context, out, pubkey);
12✔
160
}
161

162
// parsed - normalize, verify
163
static bool verify_signature(const secp256k1_context* context,
52✔
164
    const secp256k1_pubkey& point, const hash_digest& hash,
165
    const ec_signature& signature) NOEXCEPT
166
{
167
    const auto parsed = pointer_cast<const secp256k1_ecdsa_signature>(
52✔
168
        signature.data());
169

170
    // BIP62 required low-s signatures, but that is not active.
171
    // secp256k1_ecdsa_verify rejects non-normalized (low-s) signatures, but
172
    // bitcoin does not have such a limitation, so we always normalize.
173
    secp256k1_ecdsa_signature normal;
52✔
174
    secp256k1_ecdsa_signature_normalize(context, &normal, parsed);
52✔
175

176
    return secp256k1_ecdsa_verify(context, &normal, hash.data(), &point) ==
52✔
177
        ec_success;
52✔
178
}
179

180
// Add EC values
181
// ----------------------------------------------------------------------------
182

183
bool ec_add(ec_compressed& point, const ec_secret& scalar) NOEXCEPT
14✔
184
{
185
    const auto context = ec_context_verify::context();
14✔
186
    return ec_add(context, point, scalar);
14✔
187
}
188

189
bool ec_add(ec_uncompressed& point, const ec_secret& scalar) NOEXCEPT
×
190
{
191
    const auto context = ec_context_verify::context();
×
192
    return ec_add(context, point, scalar);
×
193
}
194

195
// secrets are normal
196
bool ec_add(ec_secret& left, const ec_secret& right) NOEXCEPT
81✔
197
{
198
    const auto context = ec_context_verify::context();
81✔
199
    return secp256k1_ec_seckey_tweak_add(context, left.data(), right.data())
81✔
200
        == ec_success;
81✔
201
}
202

203
bool ec_add(ec_compressed& left, const ec_compressed& right) NOEXCEPT
94✔
204
{
205
    return ec_sum(left, { left, right });
94✔
206
}
207

208
bool ec_add(ec_compressed& left, const ec_uncompressed& right) NOEXCEPT
×
209
{
210
    ec_compressed out;
×
211
    return compress(out, right) && ec_add(left, out);
×
212
}
213

214
// parse, combine, serialize
215
bool ec_sum(ec_compressed& out, const compressed_list& points) NOEXCEPT
95✔
216
{
217
    if (points.empty())
95✔
218
        return false;
219

220
    const auto context = ec_context_verify::context();
95✔
221

222
    std::vector<secp256k1_pubkey> keys;
95✔
223
    if (!parse(context, keys, points))
95✔
224
        return false;
225

226
    secp256k1_pubkey pubkey;
95✔
227
    return secp256k1_ec_pubkey_combine(context, &pubkey,
95✔
228
        to_pointers(keys).data(), points.size()) == ec_success &&
380✔
229
        serialize(context, out, pubkey);
95✔
230
}
95✔
231

232
// Multiply EC values
233
// ----------------------------------------------------------------------------
234

235
bool ec_multiply(ec_compressed& point, const ec_secret& scalar) NOEXCEPT
200✔
236
{
237
    const auto context = ec_context_verify::context();
200✔
238
    return ec_multiply(context, point, scalar);
200✔
239
}
240

241
bool ec_multiply(ec_uncompressed& point, const ec_secret& scalar) NOEXCEPT
×
242
{
243
    const auto context = ec_context_verify::context();
×
244
    return ec_multiply(context, point, scalar);
×
245
}
246

247
// secrets are normal
248
bool ec_multiply(ec_secret& left, const ec_secret& right) NOEXCEPT
17✔
249
{
250
    const auto context = ec_context_verify::context();
17✔
251
    return secp256k1_ec_seckey_tweak_mul(context, left.data(), right.data()) ==
17✔
252
        ec_success;
17✔
253
}
254

255
// Negate EC values
256
// ----------------------------------------------------------------------------
257

258
// secrets are normal
259
bool ec_negate(ec_secret& scalar) NOEXCEPT
39✔
260
{
261
    const auto context = ec_context_verify::context();
39✔
262
    return secp256k1_ec_seckey_negate(context, scalar.data()) == ec_success;
39✔
263
}
264

265
bool ec_negate(ec_compressed& point) NOEXCEPT
×
266
{
267
    const auto context = ec_context_verify::context();
×
268
    return ec_negate(context, point);
×
269
}
270

271
bool ec_negate(ec_uncompressed& point) NOEXCEPT
×
272
{
273
    const auto context = ec_context_verify::context();
×
274
    return ec_negate(context, point);
×
275
}
276

277
// Convert keys
278
// ----------------------------------------------------------------------------
279

280
bool compress(ec_compressed& out, const ec_uncompressed& point) NOEXCEPT
19✔
281
{
282
    secp256k1_pubkey pubkey;
19✔
283
    const auto context = ec_context_verify::context();
19✔
284
    return parse(context, pubkey, point) && serialize(context, out, pubkey);
19✔
285
}
286

287
bool decompress(ec_uncompressed& out, const ec_compressed& point) NOEXCEPT
11✔
288
{
289
    secp256k1_pubkey pubkey;
11✔
290
    const auto context = ec_context_verify::context();
11✔
291
    return parse(context, pubkey, point) && serialize(context, out, pubkey);
11✔
292
}
293

294
bool secret_to_public(ec_compressed& out, const ec_secret& secret) NOEXCEPT
392✔
295
{
296
    const auto context = ec_context_sign::context();
392✔
297
    return secret_to_public(context, out, secret);
392✔
298
}
299

300
bool secret_to_public(ec_uncompressed& out, const ec_secret& secret) NOEXCEPT
1✔
301
{
302
    const auto context = ec_context_sign::context();
1✔
303
    return secret_to_public(context, out, secret);
1✔
304
}
305

306
// Verify keys
307
// ----------------------------------------------------------------------------
308

309
bool verify(const ec_secret& secret) NOEXCEPT
157✔
310
{
311
    const auto context = ec_context_verify::context();
157✔
312
    return secp256k1_ec_seckey_verify(context, secret.data()) == ec_success;
157✔
313
}
314

315
bool verify(const data_slice& point) NOEXCEPT
×
316
{
317
    secp256k1_pubkey pubkey;
×
318
    const auto context = ec_context_verify::context();
×
319
    return parse(context, pubkey, point);
×
320
}
321

322
// Detect public keys
323
// ----------------------------------------------------------------------------
324

325
bool is_even_key(const ec_compressed& point) NOEXCEPT
1✔
326
{
327
    return point.front() == ec_even_sign;
1✔
328
}
329

330
bool is_compressed_key(const data_slice& point) NOEXCEPT
83✔
331
{
332
    const auto size = point.size();
83✔
333
    if (size != ec_compressed_size)
83✔
334
        return false;
335

336
    const auto first = point.front();
61✔
337
    return first == compressed_even || first == compressed_odd;
61✔
338
}
339

340
bool is_uncompressed_key(const data_slice& point) NOEXCEPT
20✔
341
{
342
    const auto size = point.size();
20✔
343
    if (size != ec_uncompressed_size)
20✔
344
        return false;
345

346
    const auto first = point.front();
18✔
347
    return first == uncompressed;
18✔
348
}
349

350
bool is_hybrid_key(const data_slice& point) NOEXCEPT
×
351
{
352
    const auto size = point.size();
×
353
    if (size != ec_uncompressed_size)
×
354
        return false;
355

356
    const auto first = point.front();
×
357
    return first == hybrid_even || first == hybrid_odd;
×
358
}
359

360
bool is_public_key(const data_slice& point) NOEXCEPT
76✔
361
{
362
    return is_compressed_key(point) || is_uncompressed_key(point);
76✔
363
}
364

365
bool is_endorsement(const endorsement& endorsement) NOEXCEPT
14✔
366
{
367
    const auto size = endorsement.size();
14✔
368
    return size >= min_endorsement_size && size <= max_endorsement_size;
14✔
369
}
370

371
namespace ecdsa {
372

373
// ECDSA parse/encode/sign/verify signature
374
// ----------------------------------------------------------------------------
375
// It is recommended to verify a signature after signing.
376

377
bool parse_endorsement(uint8_t& sighash_flags, data_slice& der_signature,
46✔
378
    const endorsement& endorsement) NOEXCEPT
379
{
380
    if (endorsement.empty())
46✔
381
        return false;
382

383
    sighash_flags = endorsement.back();
46✔
384
    der_signature = { endorsement.begin(), std::prev(endorsement.end()) };
46✔
385
    return true;
46✔
386
}
387

388
bool parse_signature(ec_signature& out, const data_slice& der_signature,
50✔
389
    bool strict) NOEXCEPT
390
{
391
    // BIP66: strict parse is not called for when signature is empty.
392
    if (der_signature.empty())
50✔
393
        return false;
394

395
    // BIP66: requires strict and minimal DER signature encoding.
396
    if (strict && !is_valid_signature_encoding(der_signature))
50✔
397
        return false;
398

399
    const auto context = ec_context_verify::context();
50✔
400
    auto parsed = pointer_cast<secp256k1_ecdsa_signature>(out.data());
50✔
401

402
    // ************************************************************************
403
    // CONSENSUS: This function parses DER with various errors as allowed by
404
    // Bitcoin prior to activation of BIP66. This attempts to codify the lax
405
    // rules applied by version(s) of OpenSSL in use up to that time.
406
    // ************************************************************************
407
    return ecdsa_signature_parse_der_lax(context, parsed, der_signature.data(),
50✔
408
        der_signature.size());
50✔
409
}
410

411
bool encode_signature(der_signature& out,
3✔
412
    const ec_signature& signature) NOEXCEPT
413
{
414
    const auto sign = pointer_cast<const secp256k1_ecdsa_signature>(
3✔
415
        signature.data());
416

417
    const auto context = ec_context_sign::context();
3✔
418
    auto size = max_der_signature_size;
3✔
419
    out.resize(size);
3✔
420

421
    if (secp256k1_ecdsa_signature_serialize_der(context, out.data(), &size,
3✔
422
        sign) != ec_success)
423
        return false;
424

425
    out.resize(size);
3✔
426
    return true;
3✔
427
}
428

429
// serialize?
430
bool sign(ec_signature& out, const ec_secret& secret,
5✔
431
    const hash_digest& hash) NOEXCEPT
432
{
433
    const auto context = ec_context_sign::context();
5✔
434
    const auto signature = pointer_cast<secp256k1_ecdsa_signature>(out.data());
5✔
435

436
    return secp256k1_ecdsa_sign(context, signature, hash.data(), secret.data(),
5✔
437
        secp256k1_nonce_function_rfc6979, nullptr) == ec_success;
5✔
438
}
439

440
bool verify_signature(const data_slice& point, const hash_digest& hash,
52✔
441
    const ec_signature& signature) NOEXCEPT
442
{
443
    secp256k1_pubkey pubkey;
52✔
444
    const auto context = ec_context_verify::context();
52✔
445

446
    return system::parse(context, pubkey, point) &&
104✔
447
        system::verify_signature(context, pubkey, hash, signature);
52✔
448
}
449

450
// ECDSA recoverable sign/recover
451
// ----------------------------------------------------------------------------
452
// It is recommended to verify a signature after signing.
453

454
bool sign_recoverable(recoverable_signature& out, const ec_secret& secret,
5✔
455
    const hash_digest& hash) NOEXCEPT
456
{
457
    int recovery_id{};
5✔
458
    const auto context = ec_context_sign::context();
5✔
459
    secp256k1_ecdsa_recoverable_signature signature;
5✔
460

461
    const auto result =
5✔
462
        secp256k1_ecdsa_sign_recoverable(context, &signature, hash.data(),
5✔
463
            secret.data(), secp256k1_nonce_function_rfc6979, nullptr) ==
464
            ec_success &&
10✔
465
        secp256k1_ecdsa_recoverable_signature_serialize_compact(context,
5✔
466
            out.signature.data(), &recovery_id, &signature) == ec_success;
467

468
    if (is_negative(recovery_id) || recovery_id > maximum_recovery_id)
5✔
469
        return false;
470

471
    out.recovery_id = narrow_sign_cast<uint8_t>(recovery_id);
5✔
472
    return result;
5✔
473
}
474

475
bool recover_public(ec_compressed& out,
4✔
476
    const recoverable_signature& recoverable, const hash_digest& hash) NOEXCEPT
477
{
478
    const auto context = ec_context_verify::context();
4✔
479
    return recover_public(context, out, recoverable, hash);
4✔
480
}
481

482
bool recover_public(ec_uncompressed& out,
2✔
483
    const recoverable_signature& recoverable, const hash_digest& hash) NOEXCEPT
484
{
485
    const auto context = ec_context_verify::context();
2✔
486
    return recover_public(context, out, recoverable, hash);
2✔
487
}
488

489
} // namespace ecdsa
490

491
namespace schnorr {
492

493
// Schnorr parse/sign/verify
494
// ----------------------------------------------------------------------------
495

UNCOV
496
bool parse(uint8_t& sighash_flags, ec_signature& signature,
×
497
    const endorsement& endorsement) NOEXCEPT
498
{
499
    switch (endorsement.size())
×
500
    {
501
        // BIP341: if [sighash byte] is omitted the resulting signatures are 64
502
        // bytes, and [default == 0] mode is implied (implies SIGHASH_ALL).
503
        case signature_size:
×
504
            sighash_flags = 0;
×
505
            break;
×
506

507
        // BIP341: signature has sighash byte appended in the usual fashion.
508
        // BIP341: zero is invalid sighash, must be explicit to prevent mally.
509
        case add1(signature_size):
×
510
            sighash_flags = endorsement.back();
×
511
            if (is_zero(sighash_flags)) return false;
×
512
            break;
513

514
        // BIP341: A Taproot signature is a 64-byte Schnorr signature.
515
        default:
516
            return false;
517
    }
518

519
    // TODO: optimize unnecessary copy (e.g. could return reference).
520
    signature = unsafe_array_cast<uint8_t, signature_size>(endorsement.data());
×
521
    return true;
×
522
}
523

524
// It is recommended to verify a signature after signing.
525
bool sign(ec_signature& out, const ec_secret& secret,
×
526
    const hash_digest& hash, const hash_digest& auxiliary) NOEXCEPT
527
{
528
    secp256k1_keypair keypair;
×
529
    const auto context = ec_context_sign::context();
×
530

531
    return 
×
532
        secp256k1_keypair_create(context, &keypair, secret.data()) ==
×
533
            ec_success &&
×
534
        secp256k1_schnorrsig_sign32(context, out.data(), hash.data(), &keypair,
×
535
            auxiliary.data()) == ec_success;
×
536
}
537

538
// BIP341: A Taproot signature is a 64-byte Schnorr sig, as defined in BIP340.
539
bool verify_signature(const data_slice& x_point, const hash_digest& hash,
×
540
    const ec_signature& signature) NOEXCEPT
541
{
542
    if (x_point.size() != hash_size)
×
543
        return false;
544

545
    secp256k1_xonly_pubkey pubkey;
×
546
    const auto context = ec_context_verify::context();
×
547

548
    return
×
549
        secp256k1_xonly_pubkey_parse(context, &pubkey, x_point.data()) ==
×
550
            ec_success && 
×
551
        secp256k1_schnorrsig_verify(context, signature.data(),  hash.data(),
×
552
            hash_size, &pubkey) == ec_success;
553
}
554

555
} // namespace schnorr
556
} // namespace system
557
} // namespace libbitcoin
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc