471 lines
9.5 KiB
JavaScript
471 lines
9.5 KiB
JavaScript
/*!
|
|
* opcode.js - opcode object for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const BN = require('../crypto/bn');
|
|
const util = require('../utils/util');
|
|
const common = require('./common');
|
|
const BufferReader = require('../utils/reader');
|
|
const StaticWriter = require('../utils/staticwriter');
|
|
const opcodes = common.opcodes;
|
|
const opCache = [];
|
|
|
|
/**
|
|
* A simple struct which contains
|
|
* an opcode and pushdata buffer.
|
|
* @alias module:script.Opcode
|
|
* @constructor
|
|
* @param {Number} value - Opcode.
|
|
* @param {Buffer?} data - Pushdata buffer.
|
|
* @property {Number} value
|
|
* @property {Buffer|null} data
|
|
*/
|
|
|
|
function Opcode(value, data) {
|
|
if (!(this instanceof Opcode))
|
|
return new Opcode(value, data);
|
|
|
|
this.value = value || 0;
|
|
this.data = data || null;
|
|
}
|
|
|
|
/**
|
|
* Test whether a pushdata abides by minimaldata.
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Opcode.prototype.isMinimal = function isMinimal() {
|
|
if (!this.data)
|
|
return true;
|
|
|
|
if (this.data.length === 0)
|
|
return this.value === opcodes.OP_0;
|
|
|
|
if (this.data.length === 1 && this.data[0] >= 1 && this.data[0] <= 16)
|
|
return false;
|
|
|
|
if (this.data.length === 1 && this.data[0] === 0x81)
|
|
return false;
|
|
|
|
if (this.data.length <= 75)
|
|
return this.value === this.data.length;
|
|
|
|
if (this.data.length <= 255)
|
|
return this.value === opcodes.OP_PUSHDATA1;
|
|
|
|
if (this.data.length <= 65535)
|
|
return this.value === opcodes.OP_PUSHDATA2;
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Test whether opcode is a disabled opcode.
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Opcode.prototype.isDisabled = function 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;
|
|
};
|
|
|
|
/**
|
|
* Test whether opcode is a branch (if/else/endif).
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Opcode.prototype.isBranch = function isBranch() {
|
|
return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF;
|
|
};
|
|
|
|
/**
|
|
* Encode the opcode to a buffer writer.
|
|
* @param {BufferWriter} bw
|
|
*/
|
|
|
|
Opcode.prototype.toWriter = function toWriter(bw) {
|
|
if (this.value === -1)
|
|
throw new Error('Cannot reserialize a parse error.');
|
|
|
|
if (!this.data) {
|
|
bw.writeU8(this.value);
|
|
return bw;
|
|
}
|
|
|
|
if (this.value <= 0x4b) {
|
|
assert(this.value === this.data.length);
|
|
bw.writeU8(this.value);
|
|
bw.writeBytes(this.data);
|
|
return bw;
|
|
}
|
|
|
|
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:
|
|
throw new Error('Unknown pushdata opcode.');
|
|
}
|
|
|
|
return bw;
|
|
};
|
|
|
|
/**
|
|
* Encode the opcode.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
Opcode.prototype.toRaw = function toRaw() {
|
|
const size = this.getSize();
|
|
return this.toWriter(new StaticWriter(size)).render();
|
|
};
|
|
|
|
/**
|
|
* Calculate opcode size.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
Opcode.prototype.getSize = function getSize() {
|
|
if (!this.data)
|
|
return 1;
|
|
|
|
if (this.value <= 0x4b)
|
|
return 1 + this.data.length;
|
|
|
|
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:
|
|
throw new Error('Unknown pushdata opcode.');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Instantiate opcode from buffer reader.
|
|
* @param {BufferReader} br
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.fromReader = function fromReader(br) {
|
|
const value = br.readU8();
|
|
|
|
const cached = opCache[value];
|
|
|
|
if (cached)
|
|
return cached;
|
|
|
|
const op = new Opcode(value, null);
|
|
|
|
if (value >= 0x01 && value <= 0x4b) {
|
|
if (br.left() < value) {
|
|
op.value = -1;
|
|
br.seek(br.left());
|
|
return op;
|
|
}
|
|
op.value = value;
|
|
op.data = br.readBytes(value);
|
|
return op;
|
|
}
|
|
|
|
let size;
|
|
|
|
switch (value) {
|
|
case opcodes.OP_PUSHDATA1:
|
|
if (br.left() < 1) {
|
|
op.value = -1;
|
|
break;
|
|
}
|
|
size = br.readU8();
|
|
if (br.left() < size) {
|
|
op.value = -1;
|
|
br.seek(br.left());
|
|
break;
|
|
}
|
|
op.value = value;
|
|
op.data = br.readBytes(size);
|
|
break;
|
|
case opcodes.OP_PUSHDATA2:
|
|
if (br.left() < 2) {
|
|
op.value = -1;
|
|
br.seek(br.left());
|
|
break;
|
|
}
|
|
size = br.readU16();
|
|
if (br.left() < size) {
|
|
op.value = -1;
|
|
br.seek(br.left());
|
|
break;
|
|
}
|
|
op.value = value;
|
|
op.data = br.readBytes(size);
|
|
break;
|
|
case opcodes.OP_PUSHDATA4:
|
|
if (br.left() < 4) {
|
|
op.value = -1;
|
|
br.seek(br.left());
|
|
break;
|
|
}
|
|
size = br.readU32();
|
|
if (br.left() < size) {
|
|
op.value = -1;
|
|
br.seek(br.left());
|
|
break;
|
|
}
|
|
op.value = value;
|
|
op.data = br.readBytes(size);
|
|
break;
|
|
default:
|
|
op.value = value;
|
|
break;
|
|
}
|
|
|
|
return op;
|
|
};
|
|
|
|
/**
|
|
* Instantiate opcode from serialized data.
|
|
* @param {Buffer} data
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.fromRaw = function fromRaw(data) {
|
|
return Opcode.fromReader(new BufferReader(data));
|
|
};
|
|
|
|
/**
|
|
* Instantiate an opcode from a number opcode.
|
|
* @param {Number} op
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.fromOp = function fromOp(op) {
|
|
const cached = opCache[op];
|
|
|
|
if (cached)
|
|
return cached;
|
|
|
|
return new Opcode(op, null);
|
|
};
|
|
|
|
/**
|
|
* Instantiate a pushdata opcode from
|
|
* a buffer (will encode minimaldata).
|
|
* @param {Buffer} data
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.fromData = function fromData(data) {
|
|
if (data.length === 0)
|
|
return Opcode.fromOp(opcodes.OP_0);
|
|
|
|
if (data.length === 1) {
|
|
if (data[0] >= 1 && data[0] <= 16)
|
|
return Opcode.fromOp(data[0] + 0x50);
|
|
|
|
if (data[0] === 0x81)
|
|
return Opcode.fromOp(opcodes.OP_1NEGATE);
|
|
}
|
|
|
|
return Opcode.fromPush(data);
|
|
};
|
|
|
|
/**
|
|
* 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}
|
|
*/
|
|
|
|
Opcode.fromPush = function fromPush(data) {
|
|
if (data.length <= 0x4b)
|
|
return new Opcode(data.length, data);
|
|
|
|
if (data.length <= 0xff)
|
|
return new Opcode(opcodes.OP_PUSHDATA1, data);
|
|
|
|
if (data.length <= 0xffff)
|
|
return new Opcode(opcodes.OP_PUSHDATA2, data);
|
|
|
|
if (data.length <= 0xffffffff)
|
|
return new Opcode(opcodes.OP_PUSHDATA4, data);
|
|
|
|
throw new Error('Pushdata size too large.');
|
|
};
|
|
|
|
/**
|
|
* Instantiate an opcode from a Number.
|
|
* @param {Number|BN} num
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.fromNumber = function fromNumber(num) {
|
|
return Opcode.fromData(common.array(num));
|
|
};
|
|
|
|
/**
|
|
* Instantiate an opcode from a small number.
|
|
* @param {Number} num
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.fromSmall = function fromSmall(num) {
|
|
assert(util.isU8(num) && num >= 0 && num <= 16);
|
|
return Opcode.fromOp(num === 0 ? 0 : num + 0x50);
|
|
};
|
|
|
|
/**
|
|
* Instantiate a pushdata opcode from a string.
|
|
* @param {String} data
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.fromString = function fromString(data, enc) {
|
|
if (typeof data === 'string')
|
|
data = Buffer.from(data, enc);
|
|
|
|
return Opcode.fromData(data);
|
|
};
|
|
|
|
/**
|
|
* Instantiate a pushdata opcode from anything.
|
|
* @param {String|Buffer|Number|BN|Opcode} data
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.from = function from(data) {
|
|
if (data instanceof Opcode)
|
|
return data;
|
|
|
|
if (typeof data === 'number')
|
|
return Opcode.fromOp(data);
|
|
|
|
if (Buffer.isBuffer(data))
|
|
return Opcode.fromData(data);
|
|
|
|
if (typeof data === 'string')
|
|
return Opcode.fromString(data, 'utf8');
|
|
|
|
if (BN.isBN(data))
|
|
return Opcode.fromNumber(data);
|
|
|
|
throw new Error('Bad data for opcode.');
|
|
};
|
|
|
|
/**
|
|
* Instantiate a pushdata opcode from symbolic name.
|
|
* @example
|
|
* Opcode.fromSymbol('checksequenceverify')
|
|
* @param {String} name
|
|
* @returns {Opcode}
|
|
*/
|
|
|
|
Opcode.fromSymbol = function fromSymbol(name) {
|
|
assert(typeof name === 'string');
|
|
assert(name.length > 0);
|
|
|
|
if (!util.isUpperCase(name))
|
|
name = name.toUpperCase();
|
|
|
|
if (!util.startsWith(name, 'OP_'))
|
|
name = `OP_${name}`;
|
|
|
|
const op = common.opcodes[name];
|
|
assert(op != null, 'Unknown opcode.');
|
|
|
|
return Opcode.fromOp(op);
|
|
};
|
|
|
|
/**
|
|
* Convert opcode to its symbolic representation.
|
|
* @returns {String}
|
|
*/
|
|
|
|
Opcode.prototype.toSymbol = function toSymbol() {
|
|
let op = this.value;
|
|
|
|
if (op === -1)
|
|
op = 0xff;
|
|
|
|
let symbol = common.opcodesByVal[op];
|
|
|
|
if (symbol == null)
|
|
symbol = util.hex8(op);
|
|
|
|
return symbol;
|
|
};
|
|
|
|
/**
|
|
* Test whether an object an Opcode.
|
|
* @param {Object} obj
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
Opcode.isOpcode = function isOpcode(obj) {
|
|
return obj
|
|
&& typeof obj.value === 'number'
|
|
&& (Buffer.isBuffer(obj.data) || obj.data === null);
|
|
};
|
|
|
|
/*
|
|
* Fill Cache
|
|
*/
|
|
|
|
for (let value = 0x00; value <= 0xff; value++) {
|
|
if (value >= 0x01 && value <= 0x4e) {
|
|
opCache.push(null);
|
|
continue;
|
|
}
|
|
|
|
const op = new Opcode(value, null);
|
|
|
|
Object.freeze(op);
|
|
|
|
opCache.push(op);
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = Opcode;
|