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

handshake-org / hsd / 15042451478

15 May 2025 10:16AM UTC coverage: 19.792% (-51.5%) from 71.269%
15042451478

Pull #928

github

web-flow
Merge fa1d30da4 into 5f11d622b
Pull Request #928: Wallet coinselection

1570 of 13236 branches covered (11.86%)

Branch coverage included in aggregate %.

217 of 388 new or added lines in 6 files covered. (55.93%)

17866 existing lines in 127 files now uncovered.

7855 of 34384 relevant lines covered (22.84%)

129.11 hits per line

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

29.74
/lib/script/opcode.js
1
/*!
2
 * opcode.js - opcode object for hsd
3
 * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
4
 * https://github.com/handshake-org/hsd
5
 */
6

7
'use strict';
8

9
const assert = require('bsert');
1✔
10
const bio = require('bufio');
1✔
11
const ScriptNum = require('./scriptnum');
1✔
12
const common = require('./common');
1✔
13
const opcodes = common.opcodes;
1✔
14

15
/** @typedef {import('../types').BufioWriter} BufioWriter */
16

17
/** @type {Opcode[]} */
18
const opCache = [];
1✔
19

20
let PARSE_ERROR = null;
1✔
21

22
/**
23
 * Opcode
24
 * A simple struct which contains
25
 * an opcode and pushdata buffer.
26
 * @alias module:script.Opcode
27
 * @property {Number} value
28
 * @property {Buffer|null} data
29
 */
30

31
class Opcode {
32
  /**
33
   * Create an opcode.
34
   * Note: this should not be called directly.
35
   * @constructor
36
   * @param {Number} value - Opcode.
37
   * @param {Buffer?} [data] - Pushdata buffer.
38
   */
39

40
  constructor(value, data) {
41
    this.value = value || 0;
2,872✔
42
    this.data = data || null;
2,872✔
43
  }
44

45
  /**
46
   * Test whether a pushdata abides by minimaldata.
47
   * @returns {Boolean}
48
   */
49

50
  isMinimal() {
UNCOV
51
    if (!this.data)
×
52
      return true;
×
53

UNCOV
54
    if (this.data.length === 1) {
×
UNCOV
55
      if (this.data[0] === 0x81)
×
56
        return false;
×
57

UNCOV
58
      if (this.data[0] >= 1 && this.data[0] <= 16)
×
59
        return false;
×
60
    }
61

UNCOV
62
    if (this.data.length <= 0x4b)
×
UNCOV
63
      return this.value === this.data.length;
×
64

65
    if (this.data.length <= 0xff)
×
66
      return this.value === opcodes.OP_PUSHDATA1;
×
67

68
    if (this.data.length <= 0xffff)
×
69
      return this.value === opcodes.OP_PUSHDATA2;
×
70

71
    assert(this.value === opcodes.OP_PUSHDATA4);
×
72

73
    return true;
×
74
  }
75

76
  /**
77
   * Test whether opcode is a disabled opcode.
78
   * @returns {Boolean}
79
   */
80

81
  isDisabled() {
UNCOV
82
    switch (this.value) {
×
83
      case opcodes.OP_CAT:
84
      case opcodes.OP_SUBSTR:
85
      case opcodes.OP_LEFT:
86
      case opcodes.OP_RIGHT:
87
      case opcodes.OP_INVERT:
88
      case opcodes.OP_AND:
89
      case opcodes.OP_OR:
90
      case opcodes.OP_XOR:
91
      case opcodes.OP_2MUL:
92
      case opcodes.OP_2DIV:
93
      case opcodes.OP_MUL:
94
      case opcodes.OP_DIV:
95
      case opcodes.OP_MOD:
96
      case opcodes.OP_LSHIFT:
97
      case opcodes.OP_RSHIFT:
UNCOV
98
        return true;
×
99
    }
UNCOV
100
    return false;
×
101
  }
102

103
  /**
104
   * Test whether opcode is a branch (if/else/endif).
105
   * @returns {Boolean}
106
   */
107

108
  isBranch() {
UNCOV
109
    return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF;
×
110
  }
111

112
  /**
113
   * Test opcode equality.
114
   * @param {Opcode} op
115
   * @returns {Boolean}
116
   */
117

118
  equals(op) {
119
    assert(Opcode.isOpcode(op));
×
120

121
    if (this.value !== op.value)
×
122
      return false;
×
123

124
    if (!this.data) {
×
125
      assert(!op.data);
×
126
      return true;
×
127
    }
128

129
    assert(op.data);
×
130

131
    return this.data.equals(op.data);
×
132
  }
133

134
  /**
135
   * Convert Opcode to opcode value.
136
   * @returns {Number}
137
   */
138

139
  toOp() {
140
    return this.value;
×
141
  }
142

143
  /**
144
   * Covert opcode to data push.
145
   * @returns {Buffer|null}
146
   */
147

148
  toData() {
149
    return this.data;
×
150
  }
151

152
  /**
153
   * Covert opcode to data length.
154
   * @returns {Number}
155
   */
156

157
  toLength() {
UNCOV
158
    return this.data ? this.data.length : -1;
×
159
  }
160

161
  /**
162
   * Covert and _cast_ opcode to data push.
163
   * @returns {Buffer|null}
164
   */
165

166
  toPush() {
167
    if (this.value === opcodes.OP_0)
×
168
      return common.small[0 + 1];
×
169

170
    if (this.value === opcodes.OP_1NEGATE)
×
171
      return common.small[-1 + 1];
×
172

173
    if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
×
174
      return common.small[this.value - 0x50 + 1];
×
175

176
    return this.toData();
×
177
  }
178

179
  /**
180
   * Get string for opcode.
181
   * @param {String?} [enc]
182
   * @returns {String|null}
183
   */
184

185
  toString(enc) {
186
    const data = this.toPush();
×
187

188
    if (!data)
×
189
      return null;
×
190

191
    return data.toString(enc || 'utf8');
×
192
  }
193

194
  /**
195
   * Convert opcode to small integer.
196
   * @returns {Number}
197
   */
198

199
  toSmall() {
UNCOV
200
    if (this.value === opcodes.OP_0)
×
201
      return 0;
×
202

UNCOV
203
    if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
×
UNCOV
204
      return this.value - 0x50;
×
205

206
    return -1;
×
207
  }
208

209
  /**
210
   * Convert opcode to script number.
211
   * @param {Boolean?} [minimal]
212
   * @param {Number?} [limit]
213
   * @returns {ScriptNum|null}
214
   */
215

216
  toNum(minimal, limit) {
217
    if (this.value === opcodes.OP_0)
×
218
      return ScriptNum.fromInt(0);
×
219

220
    if (this.value === opcodes.OP_1NEGATE)
×
221
      return ScriptNum.fromInt(-1);
×
222

223
    if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
×
224
      return ScriptNum.fromInt(this.value - 0x50);
×
225

226
    if (!this.data)
×
227
      return null;
×
228

229
    return ScriptNum.decode(this.data, minimal, limit);
×
230
  }
231

232
  /**
233
   * Convert opcode to integer.
234
   * @param {Boolean?} minimal
235
   * @param {Number?} limit
236
   * @returns {Number}
237
   */
238

239
  toInt(minimal, limit) {
240
    const num = this.toNum(minimal, limit);
×
241

242
    if (!num)
×
243
      return -1;
×
244

245
    return num.getInt();
×
246
  }
247

248
  /**
249
   * Convert opcode to boolean.
250
   * @returns {Boolean}
251
   */
252

253
  toBool() {
254
    const smi = this.toSmall();
×
255

256
    if (smi === -1)
×
257
      return false;
×
258

259
    return smi === 1;
×
260
  }
261

262
  /**
263
   * Convert opcode to its symbolic representation.
264
   * @returns {String}
265
   */
266

267
  toSymbol() {
UNCOV
268
    if (this.value === -1)
×
269
      return 'OP_INVALIDOPCODE';
×
270

UNCOV
271
    const symbol = common.opcodesByVal[this.value];
×
272

UNCOV
273
    if (!symbol)
×
UNCOV
274
      return `0x${hex8(this.value)}`;
×
275

UNCOV
276
    return symbol;
×
277
  }
278

279
  /**
280
   * Calculate opcode size.
281
   * @returns {Number}
282
   */
283

284
  getSize() {
285
    if (!this.data)
1,365✔
286
      return 1;
111✔
287

288
    switch (this.value) {
1,254!
289
      case opcodes.OP_PUSHDATA1:
290
        return 2 + this.data.length;
×
291
      case opcodes.OP_PUSHDATA2:
292
        return 3 + this.data.length;
×
293
      case opcodes.OP_PUSHDATA4:
294
        return 5 + this.data.length;
×
295
      default:
296
        return 1 + this.data.length;
1,254✔
297
    }
298
  }
299

300
  /**
301
   * Encode the opcode to a buffer writer.
302
   * @param {BufioWriter} bw
303
   * @returns {BufioWriter}
304
   */
305

306
  write(bw) {
307
    if (this.value === -1)
1,365!
308
      throw new Error('Cannot reserialize a parse error.');
×
309

310
    if (!this.data) {
1,365✔
311
      bw.writeU8(this.value);
111✔
312
      return bw;
111✔
313
    }
314

315
    switch (this.value) {
1,254!
316
      case opcodes.OP_PUSHDATA1:
317
        bw.writeU8(this.value);
×
318
        bw.writeU8(this.data.length);
×
319
        bw.writeBytes(this.data);
×
320
        break;
×
321
      case opcodes.OP_PUSHDATA2:
322
        bw.writeU8(this.value);
×
323
        bw.writeU16(this.data.length);
×
324
        bw.writeBytes(this.data);
×
325
        break;
×
326
      case opcodes.OP_PUSHDATA4:
327
        bw.writeU8(this.value);
×
328
        bw.writeU32(this.data.length);
×
329
        bw.writeBytes(this.data);
×
330
        break;
×
331
      default:
332
        assert(this.value === this.data.length);
1,254✔
333
        bw.writeU8(this.value);
1,254✔
334
        bw.writeBytes(this.data);
1,254✔
335
        break;
1,254✔
336
    }
337

338
    return bw;
1,254✔
339
  }
340

341
  /**
342
   * Encode the opcode.
343
   * @returns {Buffer}
344
   */
345

346
  encode() {
347
    const size = this.getSize();
1,253✔
348
    return this.write(bio.write(size)).render();
1,253✔
349
  }
350

351
  /**
352
   * Convert the opcode to a bitcoind test string.
353
   * @returns {String} Human-readable script code.
354
   */
355

356
  toFormat() {
357
    if (this.value === -1)
×
358
      return '0x01';
×
359

360
    if (this.data) {
×
361
      // Numbers
362
      if (this.data.length <= 4) {
×
363
        const num = this.toNum();
×
364
        if (this.equals(Opcode.fromNum(num)))
×
365
          return num.toString(10);
×
366
      }
367

368
      const symbol = common.opcodesByVal[this.value];
×
369
      const data = this.data.toString('hex');
×
370

371
      // Direct push
372
      if (!symbol) {
×
373
        const size = hex8(this.value);
×
374
        return `0x${size} 0x${data}`;
×
375
      }
376

377
      // Pushdatas
378
      let size = this.data.length.toString(16);
×
379

380
      while (size.length % 2 !== 0)
×
381
        size = '0' + size;
×
382

383
      return `${symbol} 0x${size} 0x${data}`;
×
384
    }
385

386
    // Opcodes
387
    const symbol = common.opcodesByVal[this.value];
×
388
    if (symbol)
×
389
      return symbol;
×
390

391
    // Unknown opcodes
392
    const value = hex8(this.value);
×
393

394
    return `0x${value}`;
×
395
  }
396

397
  /**
398
   * Format the opcode as bitcoind asm.
399
   * @param {Boolean?} decode - Attempt to decode hash types.
400
   * @returns {String} Human-readable script.
401
   */
402

403
  toASM(decode) {
404
    if (this.value === -1)
×
405
      return '[error]';
×
406

407
    if (this.data)
×
408
      return common.toASM(this.data, decode);
×
409

410
    return common.opcodesByVal[this.value] || 'OP_UNKNOWN';
×
411
  }
412

413
  /**
414
   * Instantiate an opcode from a number opcode.
415
   * @param {Number} op
416
   * @returns {Opcode}
417
   */
418

419
  static fromOp(op) {
420
    assert(typeof op === 'number');
243✔
421

422
    const cached = opCache[op];
243✔
423

424
    assert(cached, 'Bad opcode.');
243✔
425

426
    return cached;
243✔
427
  }
428

429
  /**
430
   * Instantiate a pushdata opcode from
431
   * a buffer (will encode minimaldata).
432
   * @param {Buffer} data
433
   * @returns {Opcode}
434
   */
435

436
  static fromData(data) {
437
    assert(Buffer.isBuffer(data));
1,254✔
438

439
    if (data.length === 1) {
1,254✔
440
      if (data[0] === 0x81)
1,230!
UNCOV
441
        return this.fromOp(opcodes.OP_1NEGATE);
×
442

443
      if (data[0] >= 1 && data[0] <= 16)
1,230!
444
        return this.fromOp(data[0] + 0x50);
×
445
    }
446

447
    return this.fromPush(data);
1,254✔
448
  }
449

450
  /**
451
   * Instantiate a pushdata opcode from a
452
   * buffer (this differs from fromData in
453
   * that it will _always_ be a pushdata op).
454
   * @param {Buffer} data
455
   * @returns {Opcode}
456
   */
457

458
  static fromPush(data) {
459
    assert(Buffer.isBuffer(data));
1,287✔
460

461
    if (data.length === 0)
1,287!
462
      return this.fromOp(opcodes.OP_0);
×
463

464
    if (data.length <= 0x4b)
1,287!
465
      return new this(data.length, data);
1,287✔
466

467
    if (data.length <= 0xff)
×
468
      return new this(opcodes.OP_PUSHDATA1, data);
×
469

470
    if (data.length <= 0xffff)
×
471
      return new this(opcodes.OP_PUSHDATA2, data);
×
472

473
    if (data.length <= 0xffffffff)
×
474
      return new this(opcodes.OP_PUSHDATA4, data);
×
475

476
    throw new Error('Pushdata size too large.');
×
477
  }
478

479
  /**
480
   * Instantiate a pushdata opcode from a string.
481
   * @param {String} str
482
   * @param {String} [enc=utf8]
483
   * @returns {Opcode}
484
   */
485

486
  static fromString(str, enc) {
UNCOV
487
    assert(typeof str === 'string');
×
UNCOV
488
    const data = Buffer.from(str, enc || 'utf8');
×
UNCOV
489
    return this.fromData(data);
×
490
  }
491

492
  /**
493
   * Instantiate an opcode from a small number.
494
   * @param {Number} num
495
   * @returns {Opcode}
496
   */
497

498
  static fromSmall(num) {
UNCOV
499
    assert((num & 0xff) === num && num >= 0 && num <= 16);
×
UNCOV
500
    return this.fromOp(num === 0 ? 0 : num + 0x50);
×
501
  }
502

503
  /**
504
   * Instantiate an opcode from a ScriptNum.
505
   * @param {ScriptNum} num
506
   * @returns {Opcode}
507
   */
508

509
  static fromNum(num) {
510
    assert(ScriptNum.isScriptNum(num));
1,254✔
511
    return this.fromData(num.encode());
1,254✔
512
  }
513

514
  /**
515
   * Instantiate an opcode from a Number.
516
   * @param {Number} num
517
   * @returns {Opcode}
518
   */
519

520
  static fromInt(num) {
521
    assert(Number.isSafeInteger(num));
3✔
522

523
    if (num === 0)
3!
UNCOV
524
      return this.fromOp(opcodes.OP_0);
×
525

526
    if (num === -1)
3!
527
      return this.fromOp(opcodes.OP_1NEGATE);
×
528

529
    if (num >= 1 && num <= 16)
3✔
530
      return this.fromOp(num + 0x50);
2✔
531

532
    return this.fromNum(ScriptNum.fromNumber(num));
1✔
533
  }
534

535
  /**
536
   * Instantiate an opcode from a Number.
537
   * @param {Boolean} value
538
   * @returns {Opcode}
539
   */
540

541
  static fromBool(value) {
542
    assert(typeof value === 'boolean');
×
543
    return this.fromSmall(value ? 1 : 0);
×
544
  }
545

546
  /**
547
   * Instantiate a pushdata opcode from symbolic name.
548
   * @example
549
   *   Opcode.fromSymbol('checksequenceverify')
550
   * @param {String} name
551
   * @returns {Opcode}
552
   */
553

554
  static fromSymbol(name) {
555
    assert(typeof name === 'string');
4✔
556
    assert(name.length > 0);
4✔
557

558
    if (name.charCodeAt(0) & 32)
4!
559
      name = name.toUpperCase();
4✔
560

561
    if (!/^OP_/.test(name))
4!
562
      name = `OP_${name}`;
4✔
563

564
    const op = common.opcodes[name];
4✔
565

566
    if (op != null)
4!
567
      return this.fromOp(op);
4✔
568

569
    assert(/^OP_0X/.test(name), 'Unknown opcode.');
×
570
    assert(name.length === 7, 'Unknown opcode.');
×
571

572
    const value = parseInt(name.substring(5), 16);
×
573

574
    assert((value & 0xff) === value, 'Unknown opcode.');
×
575

576
    return this.fromOp(value);
×
577
  }
578

579
  /**
580
   * Instantiate opcode from buffer reader.
581
   * @param {bio.BufferReader} br
582
   * @returns {Opcode}
583
   */
584

585
  static read(br) {
586
    const value = br.readU8();
8,122✔
587
    const op = opCache[value];
8,122✔
588

589
    if (op)
8,122✔
590
      return op;
6,716✔
591

592
    switch (value) {
1,406!
593
      case opcodes.OP_PUSHDATA1: {
594
        if (br.left() < 1)
4!
595
          return PARSE_ERROR;
×
596

597
        const size = br.readU8();
4✔
598

599
        if (br.left() < size) {
4!
600
          br.seek(br.left());
×
601
          return PARSE_ERROR;
×
602
        }
603

604
        const data = br.readBytes(size);
4✔
605

606
        return new this(value, data);
4✔
607
      }
608
      case opcodes.OP_PUSHDATA2: {
609
        if (br.left() < 2) {
3!
610
          br.seek(br.left());
×
611
          return PARSE_ERROR;
×
612
        }
613

614
        const size = br.readU16();
3✔
615

616
        if (br.left() < size) {
3!
617
          br.seek(br.left());
×
618
          return PARSE_ERROR;
×
619
        }
620

621
        const data = br.readBytes(size);
3✔
622

623
        return new this(value, data);
3✔
624
      }
625
      case opcodes.OP_PUSHDATA4: {
626
        if (br.left() < 4) {
×
627
          br.seek(br.left());
×
628
          return PARSE_ERROR;
×
629
        }
630

631
        const size = br.readU32();
×
632

633
        if (br.left() < size) {
×
634
          br.seek(br.left());
×
635
          return PARSE_ERROR;
×
636
        }
637

638
        const data = br.readBytes(size);
×
639

640
        return new this(value, data);
×
641
      }
642
      default: {
643
        if (br.left() < value) {
1,399!
644
          br.seek(br.left());
×
645
          return PARSE_ERROR;
×
646
        }
647

648
        const data = br.readBytes(value);
1,399✔
649

650
        return new this(value, data);
1,399✔
651
      }
652
    }
653
  }
654

655
  /**
656
   * Instantiate opcode from serialized data.
657
   * @param {Buffer} data
658
   * @returns {Opcode}
659
   */
660

661
  static decode(data) {
662
    return this.read(bio.read(data));
×
663
  }
664

665
  /**
666
   * Test whether an object an Opcode.
667
   * @param {Object} obj
668
   * @returns {Boolean}
669
   */
670

671
  static isOpcode(obj) {
672
    return obj instanceof Opcode;
112✔
673
  }
674
}
675

676
/*
677
 * Helpers
678
 */
679

680
function hex8(num) {
UNCOV
681
  if (num <= 0x0f)
×
682
    return '0' + num.toString(16);
×
UNCOV
683
  return num.toString(16);
×
684
}
685

686
/*
687
 * Fill Cache
688
 */
689

690
PARSE_ERROR = Object.freeze(new Opcode(-1));
1✔
691

692
for (let value = 0x00; value <= 0xff; value++) {
1✔
693
  if (value >= 0x01 && value <= 0x4e) {
256✔
694
    opCache.push(null);
78✔
695
    continue;
78✔
696
  }
697
  const op = new Opcode(value);
178✔
698
  opCache.push(Object.freeze(op));
178✔
699
}
700

701
/*
702
 * Expose
703
 */
704

705
module.exports = Opcode;
1✔
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