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

libbitcoin / libbitcoin-system / 15089886131

17 May 2025 11:00PM UTC coverage: 81.285% (-0.05%) from 81.33%
15089886131

push

github

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

Integrate secp256k1::verify_commitment into tapscript extract.

0 of 73 new or added lines in 2 files covered. (0.0%)

7 existing lines in 2 files now uncovered.

10385 of 12776 relevant lines covered (81.29%)

3746331.8 hits per line

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

71.43
/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_signature(ec_signature& out, const data_slice& der_signature,
50✔
378
    bool strict) NOEXCEPT
379
{
380
    // BIP66: strict parse is not called for when signature is empty.
381
    if (der_signature.empty())
50✔
382
        return false;
383

384
    // BIP66: requires strict and minimal DER signature encoding.
385
    if (strict && !is_valid_signature_encoding(der_signature))
50✔
386
        return false;
387

388
    const auto context = ec_context_verify::context();
50✔
389
    auto parsed = pointer_cast<secp256k1_ecdsa_signature>(out.data());
50✔
390

391
    // ************************************************************************
392
    // CONSENSUS: This function parses DER with various errors as allowed by
393
    // Bitcoin prior to activation of BIP66. This attempts to codify the lax
394
    // rules applied by version(s) of OpenSSL in use up to that time.
395
    // ************************************************************************
396
    return ecdsa_signature_parse_der_lax(context, parsed, der_signature.data(),
50✔
397
        der_signature.size());
50✔
398
}
399

400
bool encode_signature(der_signature& out,
3✔
401
    const ec_signature& signature) NOEXCEPT
402
{
403
    const auto sign = pointer_cast<const secp256k1_ecdsa_signature>(
3✔
404
        signature.data());
405

406
    const auto context = ec_context_sign::context();
3✔
407
    auto size = max_der_signature_size;
3✔
408
    out.resize(size);
3✔
409

410
    if (secp256k1_ecdsa_signature_serialize_der(context, out.data(), &size,
3✔
411
        sign) != ec_success)
412
        return false;
413

414
    out.resize(size);
3✔
415
    return true;
3✔
416
}
417

418
// serialize?
419
bool sign(ec_signature& out, const ec_secret& secret,
5✔
420
    const hash_digest& hash) NOEXCEPT
421
{
422
    const auto context = ec_context_sign::context();
5✔
423
    const auto signature = pointer_cast<secp256k1_ecdsa_signature>(out.data());
5✔
424

425
    return secp256k1_ecdsa_sign(context, signature, hash.data(), secret.data(),
5✔
426
        secp256k1_nonce_function_rfc6979, nullptr) == ec_success;
5✔
427
}
428

429
bool verify_signature(const data_slice& point, const hash_digest& hash,
52✔
430
    const ec_signature& signature) NOEXCEPT
431
{
432
    secp256k1_pubkey pubkey;
52✔
433
    const auto context = ec_context_verify::context();
52✔
434

435
    return system::parse(context, pubkey, point) &&
104✔
436
        system::verify_signature(context, pubkey, hash, signature);
52✔
437
}
438

439
// ECDSA recoverable sign/recover
440
// ----------------------------------------------------------------------------
441
// It is recommended to verify a signature after signing.
442

443
bool sign_recoverable(recoverable_signature& out, const ec_secret& secret,
5✔
444
    const hash_digest& hash) NOEXCEPT
445
{
446
    int recovery_id{};
5✔
447
    const auto context = ec_context_sign::context();
5✔
448
    secp256k1_ecdsa_recoverable_signature signature;
5✔
449

450
    const auto result =
5✔
451
        secp256k1_ecdsa_sign_recoverable(context, &signature, hash.data(),
5✔
452
            secret.data(), secp256k1_nonce_function_rfc6979, nullptr) ==
453
            ec_success &&
10✔
454
        secp256k1_ecdsa_recoverable_signature_serialize_compact(context,
5✔
455
            out.signature.data(), &recovery_id, &signature) == ec_success;
456

457
    if (is_negative(recovery_id) || recovery_id > maximum_recovery_id)
5✔
458
        return false;
459

460
    out.recovery_id = narrow_sign_cast<uint8_t>(recovery_id);
5✔
461
    return result;
5✔
462
}
463

464
bool recover_public(ec_compressed& out,
4✔
465
    const recoverable_signature& recoverable, const hash_digest& hash) NOEXCEPT
466
{
467
    const auto context = ec_context_verify::context();
4✔
468
    return recover_public(context, out, recoverable, hash);
4✔
469
}
470

471
bool recover_public(ec_uncompressed& out,
2✔
472
    const recoverable_signature& recoverable, const hash_digest& hash) NOEXCEPT
473
{
474
    const auto context = ec_context_verify::context();
2✔
475
    return recover_public(context, out, recoverable, hash);
2✔
476
}
477

478
} // namespace ecdsa
479

480
namespace schnorr {
481

482
// Schnorr parse/sign/verify
483
// ----------------------------------------------------------------------------
484

485
// It is recommended to verify a signature after signing.
486
bool sign(ec_signature& out, const ec_secret& secret,
×
487
    const hash_digest& hash, const hash_digest& auxiliary) NOEXCEPT
488
{
489
    secp256k1_keypair keypair;
×
490
    const auto context = ec_context_sign::context();
×
491

492
    return 
×
493
        secp256k1_keypair_create(context, &keypair, secret.data()) ==
×
494
            ec_success &&
×
495
        secp256k1_schnorrsig_sign32(context, out.data(), hash.data(), &keypair,
×
496
            auxiliary.data()) == ec_success;
×
497
}
498

499
// BIP341: A Taproot signature is a 64-byte Schnorr sig, as defined in BIP340.
NEW
500
bool verify_signature(const data_chunk& x_point, const hash_digest& hash,
×
501
    const ec_signature& signature) NOEXCEPT
502
{
NEW
503
    constexpr auto size = public_key_size;
×
NEW
504
    if (x_point.size() != size)
×
505
        return false;
506

NEW
507
    const auto& pubkey = unsafe_array_cast<uint8_t, size>(x_point.data());
×
NEW
508
    return verify_signature(pubkey, hash, signature);
×
509
}
510

511
// BIP341: A Taproot signature is a 64-byte Schnorr sig, as defined in BIP340.
NEW
512
bool verify_signature(const ec_xonly& x_point, const hash_digest& hash,
×
513
    const ec_signature& signature) NOEXCEPT
514
{
515
    secp256k1_xonly_pubkey pubkey;
×
516
    const auto context = ec_context_verify::context();
×
517

518
    return
×
519
        secp256k1_xonly_pubkey_parse(context, &pubkey, x_point.data()) ==
×
520
            ec_success && 
×
NEW
521
        secp256k1_schnorrsig_verify(context, signature.data(), hash.data(),
×
UNCOV
522
            hash_size, &pubkey) == ec_success;
×
523
}
524

525
// BIP341: If q != x(Q) or c[0] & 1 != y(Q) mod 2, fail.
NEW
526
bool verify_commitment(const ec_xonly& internal_key, const hash_digest& tweak,
×
527
    const ec_xonly& tweaked_key, bool tweaked_key_parity) NOEXCEPT
528
{
NEW
529
    secp256k1_xonly_pubkey pubkey;
×
NEW
530
    const auto context = ec_context_verify::context();
×
NEW
531
    const auto parity = to_int(tweaked_key_parity);
×
532

NEW
533
    return
×
NEW
534
        secp256k1_xonly_pubkey_parse(context, &pubkey, internal_key.data()) ==
×
NEW
535
            ec_success &&
×
NEW
536
        secp256k1_xonly_pubkey_tweak_add_check(context, tweaked_key.data(),
×
NEW
537
            parity, &pubkey, tweak.data()) == ec_success;
×
538
}
539

540
} // namespace schnorr
541
} // namespace system
542
} // 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