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

handshake-org / hsd / 8452447998

27 Mar 2024 01:17PM UTC coverage: 16.226% (-52.4%) from 68.632%
8452447998

Pull #888

github

web-flow
Merge c40b9e40c into 0a4f24bdb
Pull Request #888: Wallet TX Count and time indexing

1001 of 12966 branches covered (7.72%)

Branch coverage included in aggregate %.

130 of 474 new or added lines in 11 files covered. (27.43%)

17522 existing lines in 124 files now uncovered.

6546 of 33547 relevant lines covered (19.51%)

37.41 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
const opCache = [];
1✔
16

17
let PARSE_ERROR = null;
1✔
18

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

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

37
  constructor(value, data) {
38
    this.value = value || 0;
2,840✔
39
    this.data = data || null;
2,840✔
40
  }
41

42
  /**
43
   * Test whether a pushdata abides by minimaldata.
44
   * @returns {Boolean}
45
   */
46

47
  isMinimal() {
UNCOV
48
    if (!this.data)
×
49
      return true;
×
50

UNCOV
51
    if (this.data.length === 1) {
×
UNCOV
52
      if (this.data[0] === 0x81)
×
53
        return false;
×
54

UNCOV
55
      if (this.data[0] >= 1 && this.data[0] <= 16)
×
56
        return false;
×
57
    }
58

UNCOV
59
    if (this.data.length <= 0x4b)
×
UNCOV
60
      return this.value === this.data.length;
×
61

62
    if (this.data.length <= 0xff)
×
63
      return this.value === opcodes.OP_PUSHDATA1;
×
64

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

68
    assert(this.value === opcodes.OP_PUSHDATA4);
×
69

70
    return true;
×
71
  }
72

73
  /**
74
   * Test whether opcode is a disabled opcode.
75
   * @returns {Boolean}
76
   */
77

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

100
  /**
101
   * Test whether opcode is a branch (if/else/endif).
102
   * @returns {Boolean}
103
   */
104

105
  isBranch() {
UNCOV
106
    return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF;
×
107
  }
108

109
  /**
110
   * Test opcode equality.
111
   * @param {Opcode} op
112
   * @returns {Boolean}
113
   */
114

115
  equals(op) {
116
    assert(Opcode.isOpcode(op));
×
117

118
    if (this.value !== op.value)
×
119
      return false;
×
120

121
    if (!this.data) {
×
122
      assert(!op.data);
×
123
      return true;
×
124
    }
125

126
    assert(op.data);
×
127

128
    return this.data.equals(op.data);
×
129
  }
130

131
  /**
132
   * Convert Opcode to opcode value.
133
   * @returns {Number}
134
   */
135

136
  toOp() {
137
    return this.value;
×
138
  }
139

140
  /**
141
   * Covert opcode to data push.
142
   * @returns {Buffer|null}
143
   */
144

145
  toData() {
146
    return this.data;
×
147
  }
148

149
  /**
150
   * Covert opcode to data length.
151
   * @returns {Number}
152
   */
153

154
  toLength() {
UNCOV
155
    return this.data ? this.data.length : -1;
×
156
  }
157

158
  /**
159
   * Covert and _cast_ opcode to data push.
160
   * @returns {Buffer|null}
161
   */
162

163
  toPush() {
164
    if (this.value === opcodes.OP_0)
×
165
      return common.small[0 + 1];
×
166

167
    if (this.value === opcodes.OP_1NEGATE)
×
168
      return common.small[-1 + 1];
×
169

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

173
    return this.toData();
×
174
  }
175

176
  /**
177
   * Get string for opcode.
178
   * @param {String?} enc
179
   * @returns {Buffer|null}
180
   */
181

182
  toString(enc) {
183
    const data = this.toPush();
×
184

185
    if (!data)
×
186
      return null;
×
187

188
    return data.toString(enc || 'utf8');
×
189
  }
190

191
  /**
192
   * Convert opcode to small integer.
193
   * @returns {Number}
194
   */
195

196
  toSmall() {
UNCOV
197
    if (this.value === opcodes.OP_0)
×
198
      return 0;
×
199

UNCOV
200
    if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
×
UNCOV
201
      return this.value - 0x50;
×
202

203
    return -1;
×
204
  }
205

206
  /**
207
   * Convert opcode to script number.
208
   * @param {Boolean?} minimal
209
   * @param {Number?} limit
210
   * @returns {ScriptNum|null}
211
   */
212

213
  toNum(minimal, limit) {
214
    if (this.value === opcodes.OP_0)
×
215
      return ScriptNum.fromInt(0);
×
216

217
    if (this.value === opcodes.OP_1NEGATE)
×
218
      return ScriptNum.fromInt(-1);
×
219

220
    if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
×
221
      return ScriptNum.fromInt(this.value - 0x50);
×
222

223
    if (!this.data)
×
224
      return null;
×
225

226
    return ScriptNum.decode(this.data, minimal, limit);
×
227
  }
228

229
  /**
230
   * Convert opcode to integer.
231
   * @param {Boolean?} minimal
232
   * @param {Number?} limit
233
   * @returns {Number}
234
   */
235

236
  toInt(minimal, limit) {
237
    const num = this.toNum(minimal, limit);
×
238

239
    if (!num)
×
240
      return -1;
×
241

242
    return num.getInt();
×
243
  }
244

245
  /**
246
   * Convert opcode to boolean.
247
   * @returns {Boolean}
248
   */
249

250
  toBool() {
251
    const smi = this.toSmall();
×
252

253
    if (smi === -1)
×
254
      return false;
×
255

256
    return smi === 1;
×
257
  }
258

259
  /**
260
   * Convert opcode to its symbolic representation.
261
   * @returns {String}
262
   */
263

264
  toSymbol() {
UNCOV
265
    if (this.value === -1)
×
266
      return 'OP_INVALIDOPCODE';
×
267

UNCOV
268
    const symbol = common.opcodesByVal[this.value];
×
269

UNCOV
270
    if (!symbol)
×
UNCOV
271
      return `0x${hex8(this.value)}`;
×
272

UNCOV
273
    return symbol;
×
274
  }
275

276
  /**
277
   * Calculate opcode size.
278
   * @returns {Number}
279
   */
280

281
  getSize() {
282
    if (!this.data)
1,365✔
283
      return 1;
111✔
284

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

297
  /**
298
   * Encode the opcode to a buffer writer.
299
   * @param {BufferWriter} bw
300
   */
301

302
  write(bw) {
303
    if (this.value === -1)
1,365!
304
      throw new Error('Cannot reserialize a parse error.');
×
305

306
    if (!this.data) {
1,365✔
307
      bw.writeU8(this.value);
111✔
308
      return bw;
111✔
309
    }
310

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

334
    return bw;
1,254✔
335
  }
336

337
  /**
338
   * Encode the opcode.
339
   * @returns {Buffer}
340
   */
341

342
  encode() {
343
    const size = this.getSize();
1,253✔
344
    return this.write(bio.write(size)).render();
1,253✔
345
  }
346

347
  /**
348
   * Convert the opcode to a bitcoind test string.
349
   * @returns {String} Human-readable script code.
350
   */
351

352
  toFormat() {
353
    if (this.value === -1)
×
354
      return '0x01';
×
355

356
    if (this.data) {
×
357
      // Numbers
358
      if (this.data.length <= 4) {
×
359
        const num = this.toNum();
×
360
        if (this.equals(Opcode.fromNum(num)))
×
361
          return num.toString(10);
×
362
      }
363

364
      const symbol = common.opcodesByVal[this.value];
×
365
      const data = this.data.toString('hex');
×
366

367
      // Direct push
368
      if (!symbol) {
×
369
        const size = hex8(this.value);
×
370
        return `0x${size} 0x${data}`;
×
371
      }
372

373
      // Pushdatas
374
      let size = this.data.length.toString(16);
×
375

376
      while (size.length % 2 !== 0)
×
377
        size = '0' + size;
×
378

379
      return `${symbol} 0x${size} 0x${data}`;
×
380
    }
381

382
    // Opcodes
383
    const symbol = common.opcodesByVal[this.value];
×
384
    if (symbol)
×
385
      return symbol;
×
386

387
    // Unknown opcodes
388
    const value = hex8(this.value);
×
389

390
    return `0x${value}`;
×
391
  }
392

393
  /**
394
   * Format the opcode as bitcoind asm.
395
   * @param {Boolean?} decode - Attempt to decode hash types.
396
   * @returns {String} Human-readable script.
397
   */
398

399
  toASM(decode) {
400
    if (this.value === -1)
×
401
      return '[error]';
×
402

403
    if (this.data)
×
404
      return common.toASM(this.data, decode);
×
405

406
    return common.opcodesByVal[this.value] || 'OP_UNKNOWN';
×
407
  }
408

409
  /**
410
   * Instantiate an opcode from a number opcode.
411
   * @param {Number} op
412
   * @returns {Opcode}
413
   */
414

415
  static fromOp(op) {
416
    assert(typeof op === 'number');
115✔
417

418
    const cached = opCache[op];
115✔
419

420
    assert(cached, 'Bad opcode.');
115✔
421

422
    return cached;
115✔
423
  }
424

425
  /**
426
   * Instantiate a pushdata opcode from
427
   * a buffer (will encode minimaldata).
428
   * @param {Buffer} data
429
   * @returns {Opcode}
430
   */
431

432
  static fromData(data) {
433
    assert(Buffer.isBuffer(data));
1,254✔
434

435
    if (data.length === 1) {
1,254✔
436
      if (data[0] === 0x81)
1,230!
UNCOV
437
        return this.fromOp(opcodes.OP_1NEGATE);
×
438

439
      if (data[0] >= 1 && data[0] <= 16)
1,230!
440
        return this.fromOp(data[0] + 0x50);
×
441
    }
442

443
    return this.fromPush(data);
1,254✔
444
  }
445

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

454
  static fromPush(data) {
455
    assert(Buffer.isBuffer(data));
1,255✔
456

457
    if (data.length === 0)
1,255!
458
      return this.fromOp(opcodes.OP_0);
×
459

460
    if (data.length <= 0x4b)
1,255!
461
      return new this(data.length, data);
1,255✔
462

463
    if (data.length <= 0xff)
×
464
      return new this(opcodes.OP_PUSHDATA1, data);
×
465

466
    if (data.length <= 0xffff)
×
467
      return new this(opcodes.OP_PUSHDATA2, data);
×
468

469
    if (data.length <= 0xffffffff)
×
470
      return new this(opcodes.OP_PUSHDATA4, data);
×
471

472
    throw new Error('Pushdata size too large.');
×
473
  }
474

475
  /**
476
   * Instantiate a pushdata opcode from a string.
477
   * @param {String} str
478
   * @param {String} [enc=utf8]
479
   * @returns {Opcode}
480
   */
481

482
  static fromString(str, enc) {
UNCOV
483
    assert(typeof str === 'string');
×
UNCOV
484
    const data = Buffer.from(str, enc || 'utf8');
×
UNCOV
485
    return this.fromData(data);
×
486
  }
487

488
  /**
489
   * Instantiate an opcode from a small number.
490
   * @param {Number} num
491
   * @returns {Opcode}
492
   */
493

494
  static fromSmall(num) {
UNCOV
495
    assert((num & 0xff) === num && num >= 0 && num <= 16);
×
UNCOV
496
    return this.fromOp(num === 0 ? 0 : num + 0x50);
×
497
  }
498

499
  /**
500
   * Instantiate an opcode from a ScriptNum.
501
   * @param {ScriptNumber} num
502
   * @returns {Opcode}
503
   */
504

505
  static fromNum(num) {
506
    assert(ScriptNum.isScriptNum(num));
1,254✔
507
    return this.fromData(num.encode());
1,254✔
508
  }
509

510
  /**
511
   * Instantiate an opcode from a Number.
512
   * @param {Number} num
513
   * @returns {Opcode}
514
   */
515

516
  static fromInt(num) {
517
    assert(Number.isSafeInteger(num));
3✔
518

519
    if (num === 0)
3!
UNCOV
520
      return this.fromOp(opcodes.OP_0);
×
521

522
    if (num === -1)
3!
523
      return this.fromOp(opcodes.OP_1NEGATE);
×
524

525
    if (num >= 1 && num <= 16)
3✔
526
      return this.fromOp(num + 0x50);
2✔
527

528
    return this.fromNum(ScriptNum.fromNumber(num));
1✔
529
  }
530

531
  /**
532
   * Instantiate an opcode from a Number.
533
   * @param {Boolean} value
534
   * @returns {Opcode}
535
   */
536

537
  static fromBool(value) {
538
    assert(typeof value === 'boolean');
×
539
    return this.fromSmall(value ? 1 : 0);
×
540
  }
541

542
  /**
543
   * Instantiate a pushdata opcode from symbolic name.
544
   * @example
545
   *   Opcode.fromSymbol('checksequenceverify')
546
   * @param {String} name
547
   * @returns {Opcode}
548
   */
549

550
  static fromSymbol(name) {
551
    assert(typeof name === 'string');
4✔
552
    assert(name.length > 0);
4✔
553

554
    if (name.charCodeAt(0) & 32)
4!
555
      name = name.toUpperCase();
4✔
556

557
    if (!/^OP_/.test(name))
4!
558
      name = `OP_${name}`;
4✔
559

560
    const op = common.opcodes[name];
4✔
561

562
    if (op != null)
4!
563
      return this.fromOp(op);
4✔
564

565
    assert(/^OP_0X/.test(name), 'Unknown opcode.');
×
566
    assert(name.length === 7, 'Unknown opcode.');
×
567

568
    const value = parseInt(name.substring(5), 16);
×
569

570
    assert((value & 0xff) === value, 'Unknown opcode.');
×
571

572
    return this.fromOp(value);
×
573
  }
574

575
  /**
576
   * Instantiate opcode from buffer reader.
577
   * @param {BufferReader} br
578
   * @returns {Opcode}
579
   */
580

581
  static read(br) {
582
    const value = br.readU8();
8,122✔
583
    const op = opCache[value];
8,122✔
584

585
    if (op)
8,122✔
586
      return op;
6,716✔
587

588
    switch (value) {
1,406!
589
      case opcodes.OP_PUSHDATA1: {
590
        if (br.left() < 1)
4!
591
          return PARSE_ERROR;
×
592

593
        const size = br.readU8();
4✔
594

595
        if (br.left() < size) {
4!
596
          br.seek(br.left());
×
597
          return PARSE_ERROR;
×
598
        }
599

600
        const data = br.readBytes(size);
4✔
601

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

610
        const size = br.readU16();
3✔
611

612
        if (br.left() < size) {
3!
613
          br.seek(br.left());
×
614
          return PARSE_ERROR;
×
615
        }
616

617
        const data = br.readBytes(size);
3✔
618

619
        return new this(value, data);
3✔
620
      }
621
      case opcodes.OP_PUSHDATA4: {
622
        if (br.left() < 4) {
×
623
          br.seek(br.left());
×
624
          return PARSE_ERROR;
×
625
        }
626

627
        const size = br.readU32();
×
628

629
        if (br.left() < size) {
×
630
          br.seek(br.left());
×
631
          return PARSE_ERROR;
×
632
        }
633

634
        const data = br.readBytes(size);
×
635

636
        return new this(value, data);
×
637
      }
638
      default: {
639
        if (br.left() < value) {
1,399!
640
          br.seek(br.left());
×
641
          return PARSE_ERROR;
×
642
        }
643

644
        const data = br.readBytes(value);
1,399✔
645

646
        return new this(value, data);
1,399✔
647
      }
648
    }
649
  }
650

651
  /**
652
   * Instantiate opcode from serialized data.
653
   * @param {Buffer} data
654
   * @returns {Opcode}
655
   */
656

657
  static decode(data) {
658
    return this.read(bio.read(data));
×
659
  }
660

661
  /**
662
   * Test whether an object an Opcode.
663
   * @param {Object} obj
664
   * @returns {Boolean}
665
   */
666

667
  static isOpcode(obj) {
668
    return obj instanceof Opcode;
112✔
669
  }
670
}
671

672
/*
673
 * Helpers
674
 */
675

676
function hex8(num) {
UNCOV
677
  if (num <= 0x0f)
×
678
    return '0' + num.toString(16);
×
UNCOV
679
  return num.toString(16);
×
680
}
681

682
/*
683
 * Fill Cache
684
 */
685

686
PARSE_ERROR = Object.freeze(new Opcode(-1));
1✔
687

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

697
/*
698
 * Expose
699
 */
700

701
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