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

libbitcoin / libbitcoin-system / 14957620207

11 May 2025 04:23PM UTC coverage: 82.287% (-0.2%) from 82.469%
14957620207

push

github

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

Refactor sig operations, reintroduce multisighash caching.

54 of 124 new or added lines in 8 files covered. (43.55%)

9 existing lines in 6 files now uncovered.

10299 of 12516 relevant lines covered (82.29%)

3849629.69 hits per line

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

82.93
/src/chain/script.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/chain/script.hpp>
20

21
#include <algorithm>
22
#include <iterator>
23
#include <numeric>
24
#include <sstream>
25
#include <utility>
26
#include <bitcoin/system/chain/enums/coverage.hpp>
27
#include <bitcoin/system/chain/enums/flags.hpp>
28
#include <bitcoin/system/chain/enums/script_pattern.hpp>
29
#include <bitcoin/system/chain/enums/script_version.hpp>
30
#include <bitcoin/system/chain/enums/magic_numbers.hpp>
31
#include <bitcoin/system/chain/enums/opcode.hpp>
32
#include <bitcoin/system/chain/operation.hpp>
33
#include <bitcoin/system/chain/transaction.hpp>
34
#include <bitcoin/system/chain/witness.hpp>
35
#include <bitcoin/system/data/data.hpp>
36
#include <bitcoin/system/define.hpp>
37
#include <bitcoin/system/error/error.hpp>
38
#include <bitcoin/system/hash/hash.hpp>
39
#include <bitcoin/system/machine/machine.hpp>
40
#include <bitcoin/system/radix/radix.hpp>
41
#include <bitcoin/system/stream/stream.hpp>
42

43
namespace libbitcoin {
44
namespace system {
45
namespace chain {
46

47
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
48
BC_PUSH_WARNING(NO_ARRAY_INDEXING)
49

50
// static
51
// TODO: would be inlined but machine is a circular include.
52
//*****************************************************************************
53
// CONSENSUS: BIP34 requires coinbase input script to begin with one byte
54
// that indicates height size. This is inconsistent with an extreme future
55
// where the size byte overflows. However satoshi actually requires nominal
56
// encoding.
57
//*************************************************************************
58
bool script::is_coinbase_pattern(const operations& ops, size_t height) NOEXCEPT
×
59
{
60
    using namespace machine::number;
×
61
    return !ops.empty()
×
62
        && ops[0].is_nominal_push()
×
63
        && ops[0].data() == chunk::from_integer(to_unsigned(height));
×
64
}
65

66
// Constructors.
67
// ----------------------------------------------------------------------------
68

69
script::script() NOEXCEPT
130✔
70
  : script(operations{}, false, false, false, false, zero)
260✔
71
{
72
}
130✔
73

74
script::~script() NOEXCEPT
4,393✔
75
{
76
}
4,393✔
77

78
script::script(script&& other) NOEXCEPT
370✔
79
  : script(
80
      std::move(other.ops_),
370✔
81
      other.valid_,
370✔
82
      other.easier_,
370✔
83
      other.failer_,
370✔
84
      other.roller_,
370✔
85
      other.size_)
370✔
86
{
87
}
370✔
88

89
script::script(const script& other) NOEXCEPT
1,474✔
90
  : script(
91
      other.ops_,
1,474✔
92
      other.valid_,
1,474✔
93
      other.easier_,
1,474✔
94
      other.failer_,
1,474✔
95
      other.roller_,
1,474✔
96
      other.size_)
1,474✔
97
{
98
}
1,474✔
99

100
script::script(operations&& ops) NOEXCEPT
224✔
101
  : script(from_operations(std::move(ops)))
224✔
102
{
103
}
224✔
104

105
script::script(const operations& ops) NOEXCEPT
1✔
106
  : script(from_operations(ops))
1✔
107
{
108
}
1✔
109

110
script::script(stream::in::fast&& stream, bool prefix) NOEXCEPT
133✔
111
  : script(read::bytes::fast(stream), prefix)
133✔
112
{
113
}
133✔
114

115
script::script(stream::in::fast& stream, bool prefix) NOEXCEPT
1✔
116
  : script(read::bytes::fast(stream), prefix)
1✔
117
{
118
}
1✔
119

120
script::script(std::istream&& stream, bool prefix) NOEXCEPT
×
121
  : script(read::bytes::istream(stream), prefix)
×
122
{
123
}
×
124

125
script::script(std::istream& stream, bool prefix) NOEXCEPT
1✔
126
  : script(read::bytes::istream(stream), prefix)
1✔
127
{
128
}
1✔
129

130
script::script(reader&& source, bool prefix) NOEXCEPT
135✔
131
  : script(source, prefix)
135✔
132
{
133
}
×
134

135
script::script(reader& source, bool prefix) NOEXCEPT
696✔
136
  : ops_(source.get_arena())
696✔
137
{
138
    assign_data(source, prefix);
696✔
139
}
696✔
140

141
script::script(const std::string& mnemonic) NOEXCEPT
1,502✔
142
  : script(from_string(mnemonic))
1,502✔
143
{
144
}
1,502✔
145

146
// protected
147
script::script(const operations& ops, bool valid, bool easier, bool failer,
3,697✔
148
    bool roller, size_t size) NOEXCEPT
1,974✔
149
  : ops_(ops),
3,697✔
150
    valid_(valid),
3,697✔
151
    easier_(easier),
3,697✔
152
    failer_(failer),
3,697✔
153
    roller_(roller),
3,697✔
154
    size_(size),
1,628✔
155
    offset(ops_.begin())
1,974✔
156
{
157
}
1,498✔
158

159
// Operators.
160
// ----------------------------------------------------------------------------
161

162
script& script::operator=(script&& other) NOEXCEPT
4✔
163
{
164
    ops_ = std::move(other.ops_);
4✔
165
    valid_ = other.valid_;
4✔
166
    easier_ = other.easier_;
4✔
167
    failer_ = other.failer_;
4✔
168
    roller_ = other.roller_;
4✔
169
    size_ = other.size_;
4✔
170
    offset = ops_.begin();
4✔
171
    return *this;
4✔
172
}
173

174
script& script::operator=(const script& other) NOEXCEPT
×
175
{
176
    ops_ = other.ops_;
×
177
    valid_ = other.valid_;
×
178
    easier_ = other.easier_;
×
179
    failer_ = other.failer_;
×
180
    roller_ = other.roller_;
×
181
    size_ = other.size_;
×
182
    offset = ops_.begin();
×
183
    return *this;
×
184
}
185

186
bool script::operator==(const script& other) const NOEXCEPT
87✔
187
{
188
    return size_ == other.size_
87✔
189
        && ops_ == other.ops_;
87✔
190
}
191

192
bool script::operator!=(const script& other) const NOEXCEPT
×
193
{
194
    return !(*this == other);
×
195
}
196

197
// Deserialization.
198
// ----------------------------------------------------------------------------
199

200
// static/private
201
size_t script::op_count(reader& source) NOEXCEPT
696✔
202
{
203
    // Stream errors reset by set_position so trap here.
204
    if (!source)
696✔
205
        return zero;
206

207
    const auto start = source.get_read_position();
696✔
208
    auto count = zero;
696✔
209

210
    // This is expensive (1.1%) but far less than vector reallocs (11.6%).
211
    while (operation::count_op(source))
84,083✔
212
        ++count;
82,691✔
213

214
    source.set_position(start);
696✔
215
    return count;
696✔
216
}
217

218
// static/private
219
script script::from_operations(operations&& ops) NOEXCEPT
224✔
220
{
221
    constexpr auto valid = true;
224✔
222
    auto easier = false;
224✔
223
    auto failer = false;
224✔
224
    auto roller = false;
224✔
225

226
    for (const auto& op: ops)
709✔
227
    {
228
        easier |= op.is_success();
485✔
229
        failer |= op.is_invalid();
485✔
230
        roller |= op.is_roller();
485✔
231
    }
232

233
    const auto size = serialized_size(ops);
224✔
234
    return { std::move(ops), valid, easier, failer, roller, size };
224✔
235
}
236

237
// static/private
238
script script::from_operations(const operations& ops) NOEXCEPT
1✔
239
{
240
    constexpr auto valid = true;
1✔
241
    auto easier = false;
1✔
242
    auto failer = false;
1✔
243
    auto roller = false;
1✔
244

245
    for (const auto& op : ops)
3✔
246
    {
247
        easier |= op.is_success();
2✔
248
        failer |= op.is_invalid();
2✔
249
        roller |= op.is_roller();
2✔
250
    }
251

252
    const auto size = serialized_size(ops);
1✔
253
    return { ops, valid, easier, failer, roller, size };
1✔
254
}
255

256
// private
257
void script::assign_data(reader& source, bool prefix) NOEXCEPT
696✔
258
{
259
    easier_ = false;
696✔
260
    failer_ = false;
696✔
261
    roller_ = false;
696✔
262
    size_t expected{};
696✔
263

264
    if (prefix)
696✔
265
    {
266
        expected = source.read_size();
567✔
267
        source.set_limit(expected);
567✔
268
    }
269

270
    ops_.reserve(op_count(source));
696✔
271
    const auto start = source.get_read_position();
696✔
272

273
    while (!source.is_exhausted())
84,083✔
274
    {
275
        ops_.emplace_back(source);
82,691✔
276
        const auto& op = ops_.back();
82,691✔
277
        easier_ |= op.is_success();
82,691✔
278
        failer_ |= op.is_invalid();
82,691✔
279
        roller_ |= op.is_roller();
82,691✔
280
    }
281

282
    size_ = source.get_read_position() - start;
696✔
283

284
    if (prefix)
696✔
285
    {
286
        source.set_limit();
567✔
287
        if (size_ != expected)
567✔
288
            source.invalidate();
2✔
289
    }
290

291
    valid_ = source;
696✔
292
    offset = ops_.begin();
696✔
293
}
696✔
294

295
// static/private
296
script script::from_string(const std::string& mnemonic) NOEXCEPT
1,502✔
297
{
298
    constexpr auto valid = true;
1,502✔
299
    auto easier = false;
1,502✔
300
    auto failer = false;
1,502✔
301
    auto roller = false;
1,502✔
302

303
    // There is always one operation per non-empty string token.
304
    auto tokens = split(mnemonic);
1,502✔
305

306
    // Split always returns at least one token, and when trimming it will be
307
    // empty only if there was nothing but whitespace in the mnemonic.
308
    if (tokens.front().empty())
1,502✔
309
        tokens.clear();
66✔
310

311
    operations ops{};
1,502✔
312
    ops.reserve(tokens.size());
1,502✔
313

314
    // Create an op list from the split tokens.
315
    for (const auto& token: tokens)
12,200✔
316
    {
317
        ops.emplace_back(token);
10,702✔
318
        const auto& op = ops.back();
10,702✔
319
        easier |= op.is_success();
10,702✔
320
        failer |= op.is_invalid();
10,702✔
321
        roller |= op.is_roller();
10,702✔
322

323
        // This is a deserialization failure, not just an invalid code.
324
        if (!ops.back().is_valid())
10,702✔
325
            return {};
4✔
326
    }
327

328
    const auto size = serialized_size(ops);
1,498✔
329
    return { std::move(ops), valid, easier, failer, roller, size };
1,498✔
330
}
1,502✔
331

332
// Serialization.
333
// ----------------------------------------------------------------------------
334

335
data_chunk script::to_data(bool prefix) const NOEXCEPT
165✔
336
{
337
    data_chunk data(serialized_size(prefix));
165✔
338
    stream::out::fast ostream(data);
165✔
339
    write::bytes::fast out(ostream);
165✔
340
    to_data(out, prefix);
165✔
341
    return data;
330✔
342
}
165✔
343

344
void script::to_data(std::ostream& stream, bool prefix) const NOEXCEPT
×
345
{
346
    write::bytes::ostream out(stream);
×
347
    to_data(out, prefix);
×
348
}
×
349

350
// see also: subscript.to_data().
351
void script::to_data(writer& sink, bool prefix) const NOEXCEPT
3,327✔
352
{
353
    if (prefix)
3,327✔
354
        sink.write_variable(serialized_size(false));
3,145✔
355

356
    // Data serialization is affected by offset metadata.
357
    for (iterator op{ offset }; op != ops().end(); ++op)
1,494,744✔
358
        op->to_data(sink);
1,491,417✔
359
}
3,327✔
360

361
std::string script::to_string(uint32_t active_flags) const NOEXCEPT
28✔
362
{
363
    auto first = true;
28✔
364
    std::ostringstream text;
28✔
365

366
    // Throwing stream aborts.
367
    for (const auto& op: ops())
82✔
368
    {
369
        text << (first ? "" : " ") << op.to_string(active_flags);
80✔
370
        first = false;
54✔
371
    }
372

373
    // An invalid operation has a specialized serialization.
374
    return text.str();
28✔
375
}
28✔
376

377
// Properties.
378
// ----------------------------------------------------------------------------
379

380
bool script::is_valid() const NOEXCEPT
1,513✔
381
{
382
    // Any byte vector is a valid script.
383
    // This is false only if the byte count did not match the size prefix.
384
    return valid_;
1,513✔
385
}
386

387
bool script::is_roller() const NOEXCEPT
8✔
388
{
389
    return roller_;
8✔
390
};
391

392
bool script::is_prefail() const NOEXCEPT
6,186✔
393
{
394
    // Script contains an invalid opcode and will fail evaluation.
395
    return failer_;
6,186✔
396
}
397

398
bool script::is_prevalid() const NOEXCEPT
×
399
{
400
    // Script contains a success opcode and will pass evaluation (tapscript).
401
    return easier_;
×
402
}
403

404
bool script::is_underflow() const NOEXCEPT
3,093✔
405
{
406
    // Prefail implies an invalid code and a non-empty op stack.
407
    return is_prefail() && ops_.back().is_underflow();
3,093✔
408
}
409

410
const operations& script::ops() const NOEXCEPT
31,324✔
411
{
412
    return ops_;
31,324✔
413
}
414

415
// Consensus (witness::extract_script) and Electrum server payments key.
416
hash_digest script::hash() const NOEXCEPT
18✔
417
{
418
    hash_digest sha256{};
18✔
419
    hash::sha256::copy sink(sha256);
18✔
420
    to_data(sink, false);
18✔
421
    sink.flush();
18✔
422
    return sha256;
36✔
423
}
18✔
424

425
// static/private
426
size_t script::serialized_size(const operations& ops) NOEXCEPT
1,723✔
427
{
428
    return std::accumulate(ops.begin(), ops.end(), zero, op_size);
1,723✔
429
}
430

431
size_t script::serialized_size(bool prefix) const NOEXCEPT
8,849✔
432
{
433
    // Recompute it serialization has been affected by offset metadata.
434
    const auto size = (offset == ops_.begin()) ? size_ :
8,849✔
435
        std::accumulate(offset, ops_.end(), zero, op_size);
7✔
436

437
    return prefix ? ceilinged_add(size, variable_size(size)) : size;
8,849✔
438
}
439

440
// Utilities.
441
// ----------------------------------------------------------------------------
442

443
const data_chunk& script::witness_program() const NOEXCEPT
24✔
444
{
445
    static const data_chunk empty{};
24✔
446
    return is_witness_program_pattern(ops()) ? ops()[1].data() : empty;
24✔
447
}
448

449
script_version script::version() const NOEXCEPT
48✔
450
{
451
    if (!is_witness_program_pattern(ops()))
48✔
452
        return script_version::unversioned;
453

454
    switch (ops_.front().code())
48✔
455
    {
456
        case opcode::push_size_0:
457
            return script_version::segwit;
458
        case opcode::push_size_1:
×
459
            return script_version::taproot;
×
460
        default:
×
461
            return script_version::reserved;
×
462
    }
463
}
464

465
// Caller should test for is_sign_script_hash_pattern when sign_key_hash result
466
// as it is possible for an input script to match both patterns.
467
script_pattern script::pattern() const NOEXCEPT
11✔
468
{
469
    const auto input = output_pattern();
11✔
470
    return input == script_pattern::non_standard ? input_pattern() : input;
11✔
471
}
472

473
// Output patterns are mutually and input unambiguous.
474
// The bip141 coinbase pattern is not tested here, must test independently.
475
script_pattern script::output_pattern() const NOEXCEPT
22✔
476
{
477
    if (is_pay_key_hash_pattern(ops()))
22✔
478
        return script_pattern::pay_key_hash;
479

480
    if (is_pay_script_hash_pattern(ops()))
22✔
481
        return script_pattern::pay_script_hash;
482

483
    if (is_pay_null_data_pattern(ops()))
22✔
484
        return script_pattern::pay_null_data;
485

486
    if (is_pay_public_key_pattern(ops()))
18✔
487
        return script_pattern::pay_public_key;
488

489
    // Limited to 16 signatures though op_check_multisig allows 20.
490
    if (is_pay_multisig_pattern(ops()))
18✔
491
        return script_pattern::pay_multisig;
8✔
492

493
    return script_pattern::non_standard;
494
}
495

496
// A sign_key_hash result always implies sign_script_hash as well.
497
// The bip34 coinbase pattern is not tested here, must test independently.
498
script_pattern script::input_pattern() const NOEXCEPT
16✔
499
{
500
    if (is_sign_key_hash_pattern(ops()))
16✔
501
        return script_pattern::sign_key_hash;
502

503
    // This must follow is_sign_key_hash_pattern for ambiguity comment to hold.
504
    if (is_sign_script_hash_pattern(ops()))
16✔
505
        return script_pattern::sign_script_hash;
506

507
    if (is_sign_public_key_pattern(ops()))
16✔
508
        return script_pattern::sign_public_key;
509

510
    if (is_sign_multisig_pattern(ops()))
16✔
511
        return script_pattern::sign_multisig;
×
512

513
    return script_pattern::non_standard;
514
}
515

516
bool script::is_pay_to_witness(uint32_t active_flags) const NOEXCEPT
961✔
517
{
518
    // This is an optimization over using script::pattern.
519
    return is_enabled(active_flags, flags::bip141_rule) &&
1,419✔
520
        is_witness_program_pattern(ops());
458✔
521
}
522

523
bool script::is_pay_to_script_hash(uint32_t active_flags) const NOEXCEPT
969✔
524
{
525
    // This is an optimization over using script::pattern.
526
    return is_enabled(active_flags, flags::bip16_rule) &&
1,424✔
527
        is_pay_script_hash_pattern(ops());
455✔
528
}
529

530
// prevout_script is only used to determine is_pay_script_hash_pattern.
NEW
531
bool script::extract_sigop_script(script& embedded,
×
532
    const chain::script& prevout_script) const NOEXCEPT
533
{
534
    // There are no embedded sigops when the prevout script is not p2sh.
NEW
535
    if (!is_pay_script_hash_pattern(prevout_script.ops()))
×
536
        return false;
537

538
    // There are no embedded sigops when the input script is not push only.
NEW
539
    if (ops().empty() || !is_relaxed_push_pattern(ops()))
×
NEW
540
        return false;
×
541

542
    // Parse the embedded script from the last input script item (data).
543
    // This cannot fail because there is no prefix to invalidate the length.
NEW
544
    embedded = { ops().back().data(), false };
×
NEW
545
    return true;
×
546
}
547

548
// Count 1..16 multisig accurately for embedded [bip16] and witness [bip141].
UNCOV
549
constexpr size_t multisig_sigops(bool accurate, opcode code) NOEXCEPT
×
550
{
551
    return accurate && operation::is_positive(code) ?
×
552
        operation::opcode_to_positive(code) : multisig_default_sigops;
×
553
}
554

555
constexpr bool is_single_sigop(opcode code) NOEXCEPT
24✔
556
{
557
    return code == opcode::checksig || code == opcode::checksigverify;
24✔
558
}
559

560
constexpr bool is_multiple_sigop(opcode code) NOEXCEPT
×
561
{
562
    return code == opcode::checkmultisig || code == opcode::checkmultisigverify;
×
563
}
564

565
// TODO: compute in or at script evaluation and add coinbase input scripts?
566
// TODO: this would avoid second deserialization of script for sigop counting.
567
size_t script::signature_operations(bool accurate) const NOEXCEPT
12✔
568
{
569
    size_t total{};
12✔
570
    auto last = opcode::push_negative_1;
12✔
571

572
    for (const auto& op: ops())
36✔
573
    {
574
        const auto code = op.code();
24✔
575

576
        if (is_single_sigop(code))
24✔
577
        {
578
            total = ceilinged_add(total, one);
24✔
579
        }
580
        else if (is_multiple_sigop(code))
×
581
        {
NEW
582
            total = ceilinged_add(total, multisig_sigops(accurate, last));
×
583
        }
584

585
        last = code;
24✔
586
    }
587

588
    return total;
12✔
589
}
590

591
bool script::is_oversized() const NOEXCEPT
3,095✔
592
{
593
    return serialized_size(false) > max_script_size;
3,095✔
594
}
595

596
// An unspendable script is any that can provably not be spent under any
597
// circumstance. This allows for exclusion of the output as unspendable.
598
// The criteria below are not comprehensive but are fast to evaluate.
599
bool script::is_unspendable() const NOEXCEPT
3✔
600
{
601
    if (ops_.empty())
3✔
602
        return false;
603

604
    const auto& code = ops_.front().code();
3✔
605

606
    // There is no condition prior to the first opcode in a script, so
607
    // is_reserved must be checked. is_invalid short-circuits evaluation for
608
    // scripts that fail to parse, but would otherwise be caught in evaluation.
609
    return operation::is_reserved(code) || operation::is_invalid(code);
3✔
610
}
611

612
void script::clear_offset() const NOEXCEPT
3,095✔
613
{
614
    offset = ops_.begin();
3,095✔
615
}
3,095✔
616

617
BC_POP_WARNING()
618
BC_POP_WARNING()
619

620
// JSON value convertors.
621
// ----------------------------------------------------------------------------
622

623
namespace json = boost::json;
624

625
// boost/json will soon have NOEXCEPT: github.com/boostorg/json/pull/636
626
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
627

628
script tag_invoke(json::value_to_tag<script>,
11✔
629
    const json::value& value) NOEXCEPT
630
{
631
    return script{ std::string(value.get_string().c_str()) };
11✔
632
}
633

634
void tag_invoke(json::value_from_tag, json::value& value,
22✔
635
    const script& script) NOEXCEPT
636
{
637
    value = script.to_string(flags::all_rules);
22✔
638
}
22✔
639

640
BC_POP_WARNING()
641

642
script::cptr tag_invoke(json::value_to_tag<script::cptr>,
×
643
    const json::value& value) NOEXCEPT
644
{
645
    return to_shared(tag_invoke(json::value_to_tag<script>{}, value));
×
646
}
647

648
// Shared pointer overload is required for navigation.
649
BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED)
650
BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR)
651

652
void tag_invoke(json::value_from_tag tag, json::value& value,
×
653
    const script::cptr& script) NOEXCEPT
654
{
655
    tag_invoke(tag, value, *script);
×
656
}
×
657

658
BC_POP_WARNING()
659
BC_POP_WARNING()
660

661
} // namespace chain
662
} // namespace system
663
} // 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