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

handshake-org / hsd / 11895505322

18 Nov 2024 03:21PM UTC coverage: 71.265% (+1.2%) from 70.033%
11895505322

Pull #888

github

web-flow
Merge a914b01ee into 1b331eedb
Pull Request #888: Wallet TX Count and time indexing

8052 of 13150 branches covered (61.23%)

Branch coverage included in aggregate %.

826 of 885 new or added lines in 16 files covered. (93.33%)

2063 existing lines in 60 files now uncovered.

25720 of 34239 relevant lines covered (75.12%)

34512.26 hits per line

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

46.76
/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');
68✔
10
const bio = require('bufio');
68✔
11
const ScriptNum = require('./scriptnum');
68✔
12
const common = require('./common');
68✔
13
const opcodes = common.opcodes;
68✔
14

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

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

20
let PARSE_ERROR = null;
68✔
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;
130,344✔
42
    this.data = data || null;
130,344✔
43
  }
44

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

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

54
    if (this.data.length === 1) {
23,697✔
55
      if (this.data[0] === 0x81)
5!
56
        return false;
×
57

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

62
    if (this.data.length <= 0x4b)
23,697!
63
      return this.value === this.data.length;
23,697✔
64

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

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

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

UNCOV
73
    return true;
×
74
  }
75

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

81
  isDisabled() {
82
    switch (this.value) {
208,289✔
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:
98
        return true;
7✔
99
    }
100
    return false;
208,282✔
101
  }
102

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

108
  isBranch() {
109
    return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF;
81,568✔
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

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

UNCOV
129
    assert(op.data);
×
130

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

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

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

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

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

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

157
  toLength() {
158
    return this.data ? this.data.length : -1;
29!
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)
×
UNCOV
174
      return common.small[this.value - 0x50 + 1];
×
175

UNCOV
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)
×
UNCOV
189
      return null;
×
190

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

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

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

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

UNCOV
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)
×
UNCOV
227
      return null;
×
228

UNCOV
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)
×
UNCOV
243
      return -1;
×
244

UNCOV
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)
×
UNCOV
257
      return false;
×
258

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

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

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

271
    const symbol = common.opcodesByVal[this.value];
195✔
272

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

276
    return symbol;
130✔
277
  }
278

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

284
  getSize() {
285
    if (!this.data)
19,530✔
286
      return 1;
10,221✔
287

288
    switch (this.value) {
9,309!
289
      case opcodes.OP_PUSHDATA1:
UNCOV
290
        return 2 + this.data.length;
×
291
      case opcodes.OP_PUSHDATA2:
UNCOV
292
        return 3 + this.data.length;
×
293
      case opcodes.OP_PUSHDATA4:
UNCOV
294
        return 5 + this.data.length;
×
295
      default:
296
        return 1 + this.data.length;
9,309✔
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)
19,530!
UNCOV
308
      throw new Error('Cannot reserialize a parse error.');
×
309

310
    if (!this.data) {
19,530✔
311
      bw.writeU8(this.value);
10,221✔
312
      return bw;
10,221✔
313
    }
314

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

338
    return bw;
9,309✔
339
  }
340

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

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

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

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

360
    if (this.data) {
×
361
      // Numbers
UNCOV
362
      if (this.data.length <= 4) {
×
UNCOV
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
UNCOV
372
      if (!symbol) {
×
UNCOV
373
        const size = hex8(this.value);
×
374
        return `0x${size} 0x${data}`;
×
375
      }
376

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

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

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

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

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

UNCOV
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)
×
UNCOV
405
      return '[error]';
×
406

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

UNCOV
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');
433,485✔
421

422
    const cached = opCache[op];
433,485✔
423

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

426
    return cached;
433,485✔
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));
9,333✔
438

439
    if (data.length === 1) {
9,333✔
440
      if (data[0] === 0x81)
1,259✔
441
        return this.fromOp(opcodes.OP_1NEGATE);
24✔
442

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

447
    return this.fromPush(data);
9,309✔
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));
115,125✔
460

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

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

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

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

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

UNCOV
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) {
487
    assert(typeof str === 'string');
2✔
488
    const data = Buffer.from(str, enc || 'utf8');
2!
489
    return this.fromData(data);
2✔
490
  }
491

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

498
  static fromSmall(num) {
499
    assert((num & 0xff) === num && num >= 0 && num <= 16);
6,644✔
500
    return this.fromOp(num === 0 ? 0 : num + 0x50);
6,644!
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,451✔
511
    return this.fromData(num.encode());
1,451✔
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));
59✔
522

523
    if (num === 0)
59✔
524
      return this.fromOp(opcodes.OP_0);
3✔
525

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

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

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

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

541
  static fromBool(value) {
UNCOV
542
    assert(typeof value === 'boolean');
×
UNCOV
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');
72✔
556
    assert(name.length > 0);
72✔
557

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

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

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

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

UNCOV
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

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

UNCOV
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();
437,259✔
587
    const op = opCache[value];
437,259✔
588

589
    if (op)
437,259✔
590
      return op;
434,212✔
591

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

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

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

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

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

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

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

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

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

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

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

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

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

648
        const data = br.readBytes(value);
3,033✔
649

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

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

661
  static decode(data) {
UNCOV
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;
18,084✔
673
  }
674
}
675

676
/*
677
 * Helpers
678
 */
679

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

686
/*
687
 * Fill Cache
688
 */
689

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

692
for (let value = 0x00; value <= 0xff; value++) {
68✔
693
  if (value >= 0x01 && value <= 0x4e) {
17,408✔
694
    opCache.push(null);
5,304✔
695
    continue;
5,304✔
696
  }
697
  const op = new Opcode(value);
12,104✔
698
  opCache.push(Object.freeze(op));
12,104✔
699
}
700

701
/*
702
 * Expose
703
 */
704

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