itns-sidechain/lib/script/opcode.js

706 lines
14 KiB
JavaScript
Raw Permalink Normal View History

2016-08-24 04:08:40 -07:00
/*!
2018-08-01 20:00:09 -07:00
* opcode.js - opcode object for hsd
2018-02-01 13:40:45 -08:00
* Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
2018-08-01 20:00:09 -07:00
* https://github.com/handshake-org/hsd
2016-08-24 04:08:40 -07:00
*/
'use strict';
2018-07-19 05:40:48 -07:00
const assert = require('bsert');
2017-11-16 11:43:24 -08:00
const bio = require('bufio');
const ScriptNum = require('./scriptnum');
2017-06-29 20:54:07 -07:00
const common = require('./common');
const opcodes = common.opcodes;
2017-08-25 18:54:51 -07:00
/** @typedef {import('../types').BufioWriter} BufioWriter */
/** @type {Opcode[]} */
const opCache = [];
2017-08-25 18:54:51 -07:00
let PARSE_ERROR = null;
2016-08-24 04:08:40 -07:00
/**
2017-11-16 18:44:38 -08:00
* Opcode
2016-08-24 04:08:40 -07:00
* A simple struct which contains
* an opcode and pushdata buffer.
2017-02-03 22:47:26 -08:00
* @alias module:script.Opcode
2016-08-24 04:08:40 -07:00
* @property {Number} value
* @property {Buffer|null} data
*/
2017-11-16 18:44:38 -08:00
class Opcode {
/**
* Create an opcode.
* Note: this should not be called directly.
* @constructor
* @param {Number} value - Opcode.
* @param {Buffer?} [data] - Pushdata buffer.
2017-11-16 18:44:38 -08:00
*/
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
constructor(value, data) {
this.value = value || 0;
this.data = data || null;
}
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
/**
* Test whether a pushdata abides by minimaldata.
* @returns {Boolean}
*/
2017-11-16 18:44:38 -08:00
isMinimal() {
if (!this.data)
return true;
2017-11-16 18:44:38 -08:00
if (this.data.length === 1) {
if (this.data[0] === 0x81)
return false;
2017-11-16 18:44:38 -08:00
if (this.data[0] >= 1 && this.data[0] <= 16)
return false;
}
2017-11-16 18:44:38 -08:00
if (this.data.length <= 0x4b)
return this.value === this.data.length;
2017-11-16 18:44:38 -08:00
if (this.data.length <= 0xff)
return this.value === opcodes.OP_PUSHDATA1;
2017-11-16 18:44:38 -08:00
if (this.data.length <= 0xffff)
return this.value === opcodes.OP_PUSHDATA2;
2017-11-16 18:44:38 -08:00
assert(this.value === opcodes.OP_PUSHDATA4);
2017-08-25 18:54:51 -07:00
2017-11-16 18:44:38 -08:00
return true;
}
2017-11-16 18:44:38 -08:00
/**
* Test whether opcode is a disabled opcode.
* @returns {Boolean}
*/
isDisabled() {
switch (this.value) {
case opcodes.OP_CAT:
case opcodes.OP_SUBSTR:
case opcodes.OP_LEFT:
case opcodes.OP_RIGHT:
case opcodes.OP_INVERT:
case opcodes.OP_AND:
case opcodes.OP_OR:
case opcodes.OP_XOR:
case opcodes.OP_2MUL:
case opcodes.OP_2DIV:
case opcodes.OP_MUL:
case opcodes.OP_DIV:
case opcodes.OP_MOD:
case opcodes.OP_LSHIFT:
case opcodes.OP_RSHIFT:
return true;
}
return false;
}
2017-11-16 18:44:38 -08:00
/**
* Test whether opcode is a branch (if/else/endif).
* @returns {Boolean}
*/
isBranch() {
return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF;
}
2017-11-16 18:44:38 -08:00
/**
* Test opcode equality.
* @param {Opcode} op
* @returns {Boolean}
*/
2017-11-16 18:44:38 -08:00
equals(op) {
assert(Opcode.isOpcode(op));
2017-11-16 18:44:38 -08:00
if (this.value !== op.value)
return false;
2017-11-16 18:44:38 -08:00
if (!this.data) {
assert(!op.data);
return true;
}
2017-11-16 18:44:38 -08:00
assert(op.data);
2017-11-16 18:44:38 -08:00
return this.data.equals(op.data);
}
2017-11-16 18:44:38 -08:00
/**
* Convert Opcode to opcode value.
* @returns {Number}
*/
2017-11-16 18:44:38 -08:00
toOp() {
return this.value;
}
2017-11-16 18:44:38 -08:00
/**
* Covert opcode to data push.
* @returns {Buffer|null}
*/
2017-11-16 18:44:38 -08:00
toData() {
return this.data;
}
2017-11-16 18:44:38 -08:00
/**
* Covert opcode to data length.
* @returns {Number}
*/
2017-11-16 18:44:38 -08:00
toLength() {
return this.data ? this.data.length : -1;
}
2017-11-16 18:44:38 -08:00
/**
* Covert and _cast_ opcode to data push.
* @returns {Buffer|null}
*/
2017-11-16 18:44:38 -08:00
toPush() {
if (this.value === opcodes.OP_0)
return common.small[0 + 1];
2017-11-16 18:44:38 -08:00
if (this.value === opcodes.OP_1NEGATE)
return common.small[-1 + 1];
2017-11-16 18:44:38 -08:00
if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
return common.small[this.value - 0x50 + 1];
2017-11-16 18:44:38 -08:00
return this.toData();
}
2017-11-16 18:44:38 -08:00
/**
* Get string for opcode.
* @param {String?} [enc]
* @returns {String|null}
2017-11-16 18:44:38 -08:00
*/
2017-11-16 18:44:38 -08:00
toString(enc) {
const data = this.toPush();
2017-11-16 18:44:38 -08:00
if (!data)
return null;
2017-11-16 18:44:38 -08:00
return data.toString(enc || 'utf8');
}
2017-11-16 18:44:38 -08:00
/**
* Convert opcode to small integer.
* @returns {Number}
*/
2017-11-16 18:44:38 -08:00
toSmall() {
if (this.value === opcodes.OP_0)
return 0;
2017-11-16 18:44:38 -08:00
if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
return this.value - 0x50;
2017-11-16 18:44:38 -08:00
return -1;
}
2017-11-16 18:44:38 -08:00
/**
* Convert opcode to script number.
* @param {Boolean?} [minimal]
* @param {Number?} [limit]
2017-11-16 18:44:38 -08:00
* @returns {ScriptNum|null}
*/
2017-11-16 18:44:38 -08:00
toNum(minimal, limit) {
if (this.value === opcodes.OP_0)
return ScriptNum.fromInt(0);
2017-11-16 18:44:38 -08:00
if (this.value === opcodes.OP_1NEGATE)
return ScriptNum.fromInt(-1);
2017-08-25 18:54:51 -07:00
2017-11-16 18:44:38 -08:00
if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16)
return ScriptNum.fromInt(this.value - 0x50);
2017-11-16 18:44:38 -08:00
if (!this.data)
return null;
2017-11-16 18:44:38 -08:00
return ScriptNum.decode(this.data, minimal, limit);
}
2017-11-16 18:44:38 -08:00
/**
* Convert opcode to integer.
* @param {Boolean?} minimal
* @param {Number?} limit
* @returns {Number}
*/
2017-11-16 18:44:38 -08:00
toInt(minimal, limit) {
const num = this.toNum(minimal, limit);
2017-11-16 18:44:38 -08:00
if (!num)
return -1;
2017-11-16 18:44:38 -08:00
return num.getInt();
}
2017-11-16 18:44:38 -08:00
/**
* Convert opcode to boolean.
* @returns {Boolean}
*/
2017-11-16 18:44:38 -08:00
toBool() {
const smi = this.toSmall();
2017-11-16 18:44:38 -08:00
if (smi === -1)
return false;
2017-11-16 18:44:38 -08:00
return smi === 1;
}
2017-11-16 18:44:38 -08:00
/**
* Convert opcode to its symbolic representation.
* @returns {String}
*/
2017-11-16 18:44:38 -08:00
toSymbol() {
if (this.value === -1)
return 'OP_INVALIDOPCODE';
2017-11-16 18:44:38 -08:00
const symbol = common.opcodesByVal[this.value];
2017-11-16 18:44:38 -08:00
if (!symbol)
return `0x${hex8(this.value)}`;
2017-11-16 18:44:38 -08:00
return symbol;
}
2017-11-16 18:44:38 -08:00
/**
* Calculate opcode size.
* @returns {Number}
*/
getSize() {
if (!this.data)
return 1;
switch (this.value) {
case opcodes.OP_PUSHDATA1:
return 2 + this.data.length;
case opcodes.OP_PUSHDATA2:
return 3 + this.data.length;
case opcodes.OP_PUSHDATA4:
return 5 + this.data.length;
default:
return 1 + this.data.length;
}
}
2017-11-16 18:44:38 -08:00
/**
* Encode the opcode to a buffer writer.
* @param {BufioWriter} bw
* @returns {BufioWriter}
2017-11-16 18:44:38 -08:00
*/
write(bw) {
2017-11-16 18:44:38 -08:00
if (this.value === -1)
throw new Error('Cannot reserialize a parse error.');
2017-11-16 18:44:38 -08:00
if (!this.data) {
bw.writeU8(this.value);
return bw;
}
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
switch (this.value) {
case opcodes.OP_PUSHDATA1:
bw.writeU8(this.value);
bw.writeU8(this.data.length);
bw.writeBytes(this.data);
break;
case opcodes.OP_PUSHDATA2:
bw.writeU8(this.value);
bw.writeU16(this.data.length);
bw.writeBytes(this.data);
break;
case opcodes.OP_PUSHDATA4:
bw.writeU8(this.value);
bw.writeU32(this.data.length);
bw.writeBytes(this.data);
break;
default:
assert(this.value === this.data.length);
bw.writeU8(this.value);
bw.writeBytes(this.data);
break;
}
2016-11-19 02:26:05 -08:00
2016-12-03 18:02:10 -08:00
return bw;
}
2017-11-16 18:44:38 -08:00
/**
* Encode the opcode.
* @returns {Buffer}
*/
encode() {
2017-11-16 18:44:38 -08:00
const size = this.getSize();
return this.write(bio.write(size)).render();
2016-12-03 18:02:10 -08:00
}
2017-11-16 18:44:38 -08:00
/**
* Convert the opcode to a bitcoind test string.
* @returns {String} Human-readable script code.
*/
toFormat() {
if (this.value === -1)
return '0x01';
if (this.data) {
// Numbers
if (this.data.length <= 4) {
const num = this.toNum();
if (this.equals(Opcode.fromNum(num)))
return num.toString(10);
}
2016-12-03 18:02:10 -08:00
2017-11-16 18:44:38 -08:00
const symbol = common.opcodesByVal[this.value];
const data = this.data.toString('hex');
2016-12-03 18:02:10 -08:00
2017-11-16 18:44:38 -08:00
// Direct push
if (!symbol) {
const size = hex8(this.value);
return `0x${size} 0x${data}`;
}
2017-11-16 18:44:38 -08:00
// Pushdatas
let size = this.data.length.toString(16);
2017-11-16 18:44:38 -08:00
while (size.length % 2 !== 0)
size = '0' + size;
2016-12-03 18:02:10 -08:00
2017-11-16 18:44:38 -08:00
return `${symbol} 0x${size} 0x${data}`;
2017-08-25 18:54:51 -07:00
}
2017-11-16 18:44:38 -08:00
// Opcodes
const symbol = common.opcodesByVal[this.value];
2017-11-16 18:44:38 -08:00
if (symbol)
return symbol;
2016-12-03 18:02:10 -08:00
2017-11-16 18:44:38 -08:00
// Unknown opcodes
const value = hex8(this.value);
2017-11-16 18:44:38 -08:00
return `0x${value}`;
2016-11-19 02:26:05 -08:00
}
2017-11-16 18:44:38 -08:00
/**
* Format the opcode as bitcoind asm.
* @param {Boolean?} decode - Attempt to decode hash types.
* @returns {String} Human-readable script.
*/
2017-11-16 18:44:38 -08:00
toASM(decode) {
if (this.value === -1)
return '[error]';
2016-12-03 18:02:10 -08:00
2017-11-16 18:44:38 -08:00
if (this.data)
return common.toASM(this.data, decode);
2016-12-03 18:02:10 -08:00
2017-11-16 18:44:38 -08:00
return common.opcodesByVal[this.value] || 'OP_UNKNOWN';
}
2017-11-16 18:44:38 -08:00
/**
* Instantiate an opcode from a number opcode.
* @param {Number} op
* @returns {Opcode}
*/
2017-11-16 18:44:38 -08:00
static fromOp(op) {
assert(typeof op === 'number');
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
const cached = opCache[op];
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
assert(cached, 'Bad opcode.');
2017-11-16 18:44:38 -08:00
return cached;
}
2017-11-16 18:44:38 -08:00
/**
* Instantiate a pushdata opcode from
* a buffer (will encode minimaldata).
* @param {Buffer} data
* @returns {Opcode}
*/
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
static fromData(data) {
assert(Buffer.isBuffer(data));
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
if (data.length === 1) {
if (data[0] === 0x81)
return this.fromOp(opcodes.OP_1NEGATE);
2017-11-16 18:44:38 -08:00
if (data[0] >= 1 && data[0] <= 16)
return this.fromOp(data[0] + 0x50);
}
2017-08-25 18:54:51 -07:00
2017-11-16 18:44:38 -08:00
return this.fromPush(data);
2016-08-24 04:08:40 -07:00
}
2017-11-16 18:44:38 -08:00
/**
* Instantiate a pushdata opcode from a
* buffer (this differs from fromData in
* that it will _always_ be a pushdata op).
* @param {Buffer} data
* @returns {Opcode}
*/
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
static fromPush(data) {
assert(Buffer.isBuffer(data));
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
if (data.length === 0)
return this.fromOp(opcodes.OP_0);
2017-11-16 18:44:38 -08:00
if (data.length <= 0x4b)
return new this(data.length, data);
2017-11-16 18:44:38 -08:00
if (data.length <= 0xff)
return new this(opcodes.OP_PUSHDATA1, data);
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
if (data.length <= 0xffff)
return new this(opcodes.OP_PUSHDATA2, data);
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
if (data.length <= 0xffffffff)
return new this(opcodes.OP_PUSHDATA4, data);
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
throw new Error('Pushdata size too large.');
}
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
/**
* Instantiate a pushdata opcode from a string.
* @param {String} str
* @param {String} [enc=utf8]
* @returns {Opcode}
*/
static fromString(str, enc) {
assert(typeof str === 'string');
const data = Buffer.from(str, enc || 'utf8');
return this.fromData(data);
}
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
/**
* Instantiate an opcode from a small number.
* @param {Number} num
* @returns {Opcode}
*/
2017-11-16 18:44:38 -08:00
static fromSmall(num) {
assert((num & 0xff) === num && num >= 0 && num <= 16);
return this.fromOp(num === 0 ? 0 : num + 0x50);
}
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
/**
* Instantiate an opcode from a ScriptNum.
* @param {ScriptNum} num
2017-11-16 18:44:38 -08:00
* @returns {Opcode}
*/
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
static fromNum(num) {
assert(ScriptNum.isScriptNum(num));
return this.fromData(num.encode());
}
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
/**
* Instantiate an opcode from a Number.
* @param {Number} num
* @returns {Opcode}
*/
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
static fromInt(num) {
assert(Number.isSafeInteger(num));
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
if (num === 0)
return this.fromOp(opcodes.OP_0);
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
if (num === -1)
return this.fromOp(opcodes.OP_1NEGATE);
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
if (num >= 1 && num <= 16)
return this.fromOp(num + 0x50);
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
return this.fromNum(ScriptNum.fromNumber(num));
}
2017-08-25 18:54:51 -07:00
2017-11-16 18:44:38 -08:00
/**
* Instantiate an opcode from a Number.
* @param {Boolean} value
* @returns {Opcode}
*/
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
static fromBool(value) {
assert(typeof value === 'boolean');
return this.fromSmall(value ? 1 : 0);
}
2017-11-16 18:44:38 -08:00
/**
* Instantiate a pushdata opcode from symbolic name.
* @example
* Opcode.fromSymbol('checksequenceverify')
* @param {String} name
* @returns {Opcode}
*/
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
static fromSymbol(name) {
assert(typeof name === 'string');
assert(name.length > 0);
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
if (name.charCodeAt(0) & 32)
name = name.toUpperCase();
2016-12-10 21:20:22 -08:00
2017-11-16 18:44:38 -08:00
if (!/^OP_/.test(name))
name = `OP_${name}`;
2016-12-10 21:20:22 -08:00
2017-11-16 18:44:38 -08:00
const op = common.opcodes[name];
2016-12-10 21:20:22 -08:00
2017-11-16 18:44:38 -08:00
if (op != null)
return this.fromOp(op);
2016-12-10 21:20:22 -08:00
2017-11-16 18:44:38 -08:00
assert(/^OP_0X/.test(name), 'Unknown opcode.');
assert(name.length === 7, 'Unknown opcode.');
2017-11-16 18:44:38 -08:00
const value = parseInt(name.substring(5), 16);
2017-11-16 18:44:38 -08:00
assert((value & 0xff) === value, 'Unknown opcode.');
2017-11-16 18:44:38 -08:00
return this.fromOp(value);
}
2016-12-10 21:20:22 -08:00
2017-11-16 18:44:38 -08:00
/**
* Instantiate opcode from buffer reader.
* @param {bio.BufferReader} br
2017-11-16 18:44:38 -08:00
* @returns {Opcode}
*/
2017-08-25 18:54:51 -07:00
static read(br) {
2017-11-16 18:44:38 -08:00
const value = br.readU8();
const op = opCache[value];
2016-12-10 21:20:22 -08:00
2017-11-16 18:44:38 -08:00
if (op)
return op;
2017-06-11 23:37:55 -07:00
2017-11-16 18:44:38 -08:00
switch (value) {
case opcodes.OP_PUSHDATA1: {
if (br.left() < 1)
return PARSE_ERROR;
2017-06-11 23:37:55 -07:00
2017-11-16 18:44:38 -08:00
const size = br.readU8();
2017-11-16 18:44:38 -08:00
if (br.left() < size) {
br.seek(br.left());
return PARSE_ERROR;
}
2017-11-16 18:44:38 -08:00
const data = br.readBytes(size);
2017-11-16 18:44:38 -08:00
return new this(value, data);
}
2017-11-16 18:44:38 -08:00
case opcodes.OP_PUSHDATA2: {
if (br.left() < 2) {
br.seek(br.left());
return PARSE_ERROR;
}
2017-11-16 18:44:38 -08:00
const size = br.readU16();
2017-11-16 18:44:38 -08:00
if (br.left() < size) {
br.seek(br.left());
return PARSE_ERROR;
}
2017-11-16 18:44:38 -08:00
const data = br.readBytes(size);
2017-11-16 18:44:38 -08:00
return new this(value, data);
}
2017-11-16 18:44:38 -08:00
case opcodes.OP_PUSHDATA4: {
if (br.left() < 4) {
br.seek(br.left());
return PARSE_ERROR;
}
2017-11-16 18:44:38 -08:00
const size = br.readU32();
2017-11-16 18:44:38 -08:00
if (br.left() < size) {
br.seek(br.left());
return PARSE_ERROR;
}
2017-11-16 18:44:38 -08:00
const data = br.readBytes(size);
2017-11-16 18:44:38 -08:00
return new this(value, data);
}
2017-11-16 18:44:38 -08:00
default: {
if (br.left() < value) {
br.seek(br.left());
return PARSE_ERROR;
}
2017-11-16 18:44:38 -08:00
const data = br.readBytes(value);
2017-11-16 18:44:38 -08:00
return new this(value, data);
2017-08-25 18:54:51 -07:00
}
}
}
2017-11-16 18:44:38 -08:00
/**
* Instantiate opcode from serialized data.
* @param {Buffer} data
* @returns {Opcode}
*/
static decode(data) {
return this.read(bio.read(data));
2017-11-16 18:44:38 -08:00
}
2017-06-11 23:37:55 -07:00
2017-11-16 18:44:38 -08:00
/**
* Test whether an object an Opcode.
* @param {Object} obj
* @returns {Boolean}
*/
2016-08-24 04:08:40 -07:00
2017-11-16 18:44:38 -08:00
static isOpcode(obj) {
return obj instanceof Opcode;
}
}
2016-08-24 04:08:40 -07:00
2017-10-26 04:07:36 -07:00
/*
* Helpers
*/
function hex8(num) {
if (num <= 0x0f)
return '0' + num.toString(16);
return num.toString(16);
}
/*
* Fill Cache
*/
2017-08-25 18:54:51 -07:00
PARSE_ERROR = Object.freeze(new Opcode(-1));
for (let value = 0x00; value <= 0xff; value++) {
if (value >= 0x01 && value <= 0x4e) {
opCache.push(null);
continue;
}
2017-08-25 18:54:51 -07:00
const op = new Opcode(value);
opCache.push(Object.freeze(op));
}
2016-08-24 04:08:40 -07:00
/*
* Expose
*/
module.exports = Opcode;