diff --git a/lib/script/common.js b/lib/script/common.js index 71c30ccd..80adcb64 100644 --- a/lib/script/common.js +++ b/lib/script/common.js @@ -484,7 +484,7 @@ exports.isHashType = function isHashType(sig) { const type = sig[sig.length - 1] & ~exports.hashType.ANYONECANPAY; - if (!(type >= exports.hashType.ALL && type <= exports.hashType.SINGLE)) + if (type < exports.hashType.ALL || type > exports.hashType.SINGLE) return false; return true; diff --git a/lib/script/opcode.js b/lib/script/opcode.js index d7d1aecf..12ab32f5 100644 --- a/lib/script/opcode.js +++ b/lib/script/opcode.js @@ -18,653 +18,657 @@ const opCache = []; let PARSE_ERROR = null; /** + * Opcode * A simple struct which contains * an opcode and pushdata buffer. - * Note: this should not be called directly. * @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); +class Opcode { + /** + * Create an opcode. + * Note: this should not be called directly. + * @constructor + * @param {Number} value - Opcode. + * @param {Buffer?} data - Pushdata buffer. + */ - 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 === 1) { - if (this.data[0] === 0x81) - return false; - - if (this.data[0] >= 1 && this.data[0] <= 16) - return false; + constructor(value, data) { + this.value = value || 0; + this.data = data || null; } - if (this.data.length <= 0x4b) - return this.value === this.data.length; + /** + * Test whether a pushdata abides by minimaldata. + * @returns {Boolean} + */ - if (this.data.length <= 0xff) - return this.value === opcodes.OP_PUSHDATA1; - - if (this.data.length <= 0xffff) - return this.value === opcodes.OP_PUSHDATA2; - - assert(this.value === opcodes.OP_PUSHDATA4); - - 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: + isMinimal() { + if (!this.data) return true; - } - return false; -}; -/** - * Test whether opcode is a branch (if/else/endif). - * @returns {Boolean} - */ + if (this.data.length === 1) { + if (this.data[0] === 0x81) + return false; -Opcode.prototype.isBranch = function isBranch() { - return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF; -}; + if (this.data[0] >= 1 && this.data[0] <= 16) + return false; + } -/** - * Test opcode equality. - * @param {Opcode} op - * @returns {Boolean} - */ + if (this.data.length <= 0x4b) + return this.value === this.data.length; -Opcode.prototype.equals = function equals(op) { - assert(Opcode.isOpcode(op)); + if (this.data.length <= 0xff) + return this.value === opcodes.OP_PUSHDATA1; - if (this.value !== op.value) - return false; + if (this.data.length <= 0xffff) + return this.value === opcodes.OP_PUSHDATA2; + + assert(this.value === opcodes.OP_PUSHDATA4); - if (!this.data) { - assert(!op.data); return true; } - assert(op.data); + /** + * Test whether opcode is a disabled opcode. + * @returns {Boolean} + */ - return this.data.equals(op.data); -}; - -/** - * Convert Opcode to opcode value. - * @returns {Number} - */ - -Opcode.prototype.toOp = function toOp() { - return this.value; -}; - -/** - * Covert opcode to data push. - * @returns {Buffer|null} - */ - -Opcode.prototype.toData = function toData() { - return this.data; -}; - -/** - * Covert opcode to data length. - * @returns {Number} - */ - -Opcode.prototype.toLength = function toLength() { - return this.data ? this.data.length : -1; -}; - -/** - * Covert and _cast_ opcode to data push. - * @returns {Buffer|null} - */ - -Opcode.prototype.toPush = function toPush() { - if (this.value === opcodes.OP_0) - return common.small[0 + 1]; - - if (this.value === opcodes.OP_1NEGATE) - return common.small[-1 + 1]; - - if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) - return common.small[this.value - 0x50 + 1]; - - return this.toData(); -}; - -/** - * Get string for opcode. - * @param {String?} enc - * @returns {Buffer|null} - */ - -Opcode.prototype.toString = function toString(enc) { - const data = this.toPush(); - - if (!data) - return null; - - return data.toString(enc || 'utf8'); -}; - -/** - * Convert opcode to small integer. - * @returns {Number} - */ - -Opcode.prototype.toSmall = function toSmall() { - if (this.value === opcodes.OP_0) - return 0; - - if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) - return this.value - 0x50; - - return -1; -}; - -/** - * Convert opcode to script number. - * @param {Boolean?} minimal - * @param {Number?} limit - * @returns {ScriptNum|null} - */ - -Opcode.prototype.toNum = function toNum(minimal, limit) { - if (this.value === opcodes.OP_0) - return ScriptNum.fromInt(0); - - if (this.value === opcodes.OP_1NEGATE) - return ScriptNum.fromInt(-1); - - if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) - return ScriptNum.fromInt(this.value - 0x50); - - if (!this.data) - return null; - - return ScriptNum.decode(this.data, minimal, limit); -}; - -/** - * Convert opcode to integer. - * @param {Boolean?} minimal - * @param {Number?} limit - * @returns {Number} - */ - -Opcode.prototype.toInt = function toInt(minimal, limit) { - const num = this.toNum(minimal, limit); - - if (!num) - return -1; - - return num.getInt(); -}; - -/** - * Convert opcode to boolean. - * @returns {Boolean} - */ - -Opcode.prototype.toBool = function toBool() { - const smi = this.toSmall(); - - if (smi === -1) + 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; - - return smi === 1; -}; - -/** - * Convert opcode to its symbolic representation. - * @returns {String} - */ - -Opcode.prototype.toSymbol = function toSymbol() { - if (this.value === -1) - return 'OP_INVALIDOPCODE'; - - const symbol = common.opcodesByVal[this.value]; - - if (!symbol) - return `0x${hex8(this.value)}`; - - return symbol; -}; - -/** - * Calculate opcode size. - * @returns {Number} - */ - -Opcode.prototype.getSize = function 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; } -}; -/** - * Encode the opcode to a buffer writer. - * @param {BufferWriter} bw - */ + /** + * Test whether opcode is a branch (if/else/endif). + * @returns {Boolean} + */ -Opcode.prototype.toWriter = function toWriter(bw) { - if (this.value === -1) - throw new Error('Cannot reserialize a parse error.'); + isBranch() { + return this.value >= opcodes.OP_IF && this.value <= opcodes.OP_ENDIF; + } + + /** + * Test opcode equality. + * @param {Opcode} op + * @returns {Boolean} + */ + + equals(op) { + assert(Opcode.isOpcode(op)); + + if (this.value !== op.value) + return false; + + if (!this.data) { + assert(!op.data); + return true; + } + + assert(op.data); + + return this.data.equals(op.data); + } + + /** + * Convert Opcode to opcode value. + * @returns {Number} + */ + + toOp() { + return this.value; + } + + /** + * Covert opcode to data push. + * @returns {Buffer|null} + */ + + toData() { + return this.data; + } + + /** + * Covert opcode to data length. + * @returns {Number} + */ + + toLength() { + return this.data ? this.data.length : -1; + } + + /** + * Covert and _cast_ opcode to data push. + * @returns {Buffer|null} + */ + + toPush() { + if (this.value === opcodes.OP_0) + return common.small[0 + 1]; + + if (this.value === opcodes.OP_1NEGATE) + return common.small[-1 + 1]; + + if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) + return common.small[this.value - 0x50 + 1]; + + return this.toData(); + } + + /** + * Get string for opcode. + * @param {String?} enc + * @returns {Buffer|null} + */ + + toString(enc) { + const data = this.toPush(); + + if (!data) + return null; + + return data.toString(enc || 'utf8'); + } + + /** + * Convert opcode to small integer. + * @returns {Number} + */ + + toSmall() { + if (this.value === opcodes.OP_0) + return 0; + + if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) + return this.value - 0x50; + + return -1; + } + + /** + * Convert opcode to script number. + * @param {Boolean?} minimal + * @param {Number?} limit + * @returns {ScriptNum|null} + */ + + toNum(minimal, limit) { + if (this.value === opcodes.OP_0) + return ScriptNum.fromInt(0); + + if (this.value === opcodes.OP_1NEGATE) + return ScriptNum.fromInt(-1); + + if (this.value >= opcodes.OP_1 && this.value <= opcodes.OP_16) + return ScriptNum.fromInt(this.value - 0x50); + + if (!this.data) + return null; + + return ScriptNum.decode(this.data, minimal, limit); + } + + /** + * Convert opcode to integer. + * @param {Boolean?} minimal + * @param {Number?} limit + * @returns {Number} + */ + + toInt(minimal, limit) { + const num = this.toNum(minimal, limit); + + if (!num) + return -1; + + return num.getInt(); + } + + /** + * Convert opcode to boolean. + * @returns {Boolean} + */ + + toBool() { + const smi = this.toSmall(); + + if (smi === -1) + return false; + + return smi === 1; + } + + /** + * Convert opcode to its symbolic representation. + * @returns {String} + */ + + toSymbol() { + if (this.value === -1) + return 'OP_INVALIDOPCODE'; + + const symbol = common.opcodesByVal[this.value]; + + if (!symbol) + return `0x${hex8(this.value)}`; + + return symbol; + } + + /** + * 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; + } + } + + /** + * Encode the opcode to a buffer writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + if (this.value === -1) + throw new Error('Cannot reserialize a parse error.'); + + if (!this.data) { + bw.writeU8(this.value); + 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: + assert(this.value === this.data.length); + bw.writeU8(this.value); + bw.writeBytes(this.data); + break; + } - if (!this.data) { - bw.writeU8(this.value); 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: - assert(this.value === this.data.length); - bw.writeU8(this.value); - bw.writeBytes(this.data); - break; + /** + * Encode the opcode. + * @returns {Buffer} + */ + + toRaw() { + const size = this.getSize(); + return this.toWriter(bio.write(size)).render(); } - return bw; -}; + /** + * Convert the opcode to a bitcoind test string. + * @returns {String} Human-readable script code. + */ -/** - * Encode the opcode. - * @returns {Buffer} - */ + toFormat() { + if (this.value === -1) + return '0x01'; -Opcode.prototype.toRaw = function toRaw() { - const size = this.getSize(); - return this.toWriter(bio.write(size)).render(); -}; + if (this.data) { + // Numbers + if (this.data.length <= 4) { + const num = this.toNum(); + if (this.equals(Opcode.fromNum(num))) + return num.toString(10); + } -/** - * Convert the opcode to a bitcoind test string. - * @returns {String} Human-readable script code. - */ + const symbol = common.opcodesByVal[this.value]; + const data = this.data.toString('hex'); -Opcode.prototype.toFormat = function toFormat() { - if (this.value === -1) - return '0x01'; + // Direct push + if (!symbol) { + const size = hex8(this.value); + return `0x${size} 0x${data}`; + } - if (this.data) { - // Numbers - if (this.data.length <= 4) { - const num = this.toNum(); - if (this.equals(Opcode.fromNum(num))) - return num.toString(10); + // Pushdatas + let size = this.data.length.toString(16); + + while (size.length % 2 !== 0) + size = '0' + size; + + return `${symbol} 0x${size} 0x${data}`; } + // Opcodes const symbol = common.opcodesByVal[this.value]; - const data = this.data.toString('hex'); + if (symbol) + return symbol; - // Direct push - if (!symbol) { - const size = hex8(this.value); - return `0x${size} 0x${data}`; - } + // Unknown opcodes + const value = hex8(this.value); - // Pushdatas - let size = this.data.length.toString(16); - - while (size.length % 2 !== 0) - size = '0' + size; - - return `${symbol} 0x${size} 0x${data}`; + return `0x${value}`; } - // Opcodes - const symbol = common.opcodesByVal[this.value]; - if (symbol) - return symbol; + /** + * Format the opcode as bitcoind asm. + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable script. + */ - // Unknown opcodes - const value = hex8(this.value); + toASM(decode) { + if (this.value === -1) + return '[error]'; - return `0x${value}`; -}; + if (this.data) + return common.toASM(this.data, decode); -/** - * Format the opcode as bitcoind asm. - * @param {Boolean?} decode - Attempt to decode hash types. - * @returns {String} Human-readable script. - */ - -Opcode.prototype.toASM = function toASM(decode) { - if (this.value === -1) - return '[error]'; - - if (this.data) - return common.toASM(this.data, decode); - - return common.opcodesByVal[this.value] || 'OP_UNKNOWN'; -}; - -/** - * Instantiate an opcode from a number opcode. - * @param {Number} op - * @returns {Opcode} - */ - -Opcode.fromOp = function fromOp(op) { - assert(typeof op === 'number'); - - const cached = opCache[op]; - - assert(cached, 'Bad opcode.'); - - return cached; -}; - -/** - * Instantiate a pushdata opcode from - * a buffer (will encode minimaldata). - * @param {Buffer} data - * @returns {Opcode} - */ - -Opcode.fromData = function fromData(data) { - assert(Buffer.isBuffer(data)); - - if (data.length === 1) { - if (data[0] === 0x81) - return Opcode.fromOp(opcodes.OP_1NEGATE); - - if (data[0] >= 1 && data[0] <= 16) - return Opcode.fromOp(data[0] + 0x50); + return common.opcodesByVal[this.value] || 'OP_UNKNOWN'; } - return Opcode.fromPush(data); -}; + /** + * Instantiate an opcode from a number opcode. + * @param {Number} op + * @returns {Opcode} + */ -/** - * 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} - */ + static fromOp(op) { + assert(typeof op === 'number'); -Opcode.fromPush = function fromPush(data) { - assert(Buffer.isBuffer(data)); + const cached = opCache[op]; - if (data.length === 0) - return Opcode.fromOp(opcodes.OP_0); + assert(cached, 'Bad opcode.'); - if (data.length <= 0x4b) - return new Opcode(data.length, data); + return cached; + } - if (data.length <= 0xff) - return new Opcode(opcodes.OP_PUSHDATA1, data); + /** + * Instantiate a pushdata opcode from + * a buffer (will encode minimaldata). + * @param {Buffer} data + * @returns {Opcode} + */ - if (data.length <= 0xffff) - return new Opcode(opcodes.OP_PUSHDATA2, data); + static fromData(data) { + assert(Buffer.isBuffer(data)); - if (data.length <= 0xffffffff) - return new Opcode(opcodes.OP_PUSHDATA4, data); + if (data.length === 1) { + if (data[0] === 0x81) + return this.fromOp(opcodes.OP_1NEGATE); - throw new Error('Pushdata size too large.'); -}; - -/** - * Instantiate a pushdata opcode from a string. - * @param {String} str - * @param {String} [enc=utf8] - * @returns {Opcode} - */ - -Opcode.fromString = function fromString(str, enc) { - assert(typeof str === 'string'); - const data = Buffer.from(str, enc || 'utf8'); - return Opcode.fromData(data); -}; - -/** - * Instantiate an opcode from a small number. - * @param {Number} num - * @returns {Opcode} - */ - -Opcode.fromSmall = function fromSmall(num) { - assert((num & 0xff) === num && num >= 0 && num <= 16); - return Opcode.fromOp(num === 0 ? 0 : num + 0x50); -}; - -/** - * Instantiate an opcode from a ScriptNum. - * @param {ScriptNumber} num - * @returns {Opcode} - */ - -Opcode.fromNum = function fromNum(num) { - assert(ScriptNum.isScriptNum(num)); - return Opcode.fromData(num.encode()); -}; - -/** - * Instantiate an opcode from a Number. - * @param {Number} num - * @returns {Opcode} - */ - -Opcode.fromInt = function fromInt(num) { - assert(Number.isSafeInteger(num)); - - if (num === 0) - return Opcode.fromOp(opcodes.OP_0); - - if (num === -1) - return Opcode.fromOp(opcodes.OP_1NEGATE); - - if (num >= 1 && num <= 16) - return Opcode.fromOp(num + 0x50); - - return Opcode.fromNum(ScriptNum.fromNumber(num)); -}; - -/** - * Instantiate an opcode from a Number. - * @param {Boolean} value - * @returns {Opcode} - */ - -Opcode.fromBool = function fromBool(value) { - assert(typeof value === 'boolean'); - return Opcode.fromSmall(value ? 1 : 0); -}; - -/** - * 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 (name.charCodeAt(0) & 32) - name = name.toUpperCase(); - - if (!/^OP_/.test(name)) - name = `OP_${name}`; - - const op = common.opcodes[name]; - - if (op != null) - return Opcode.fromOp(op); - - assert(/^OP_0X/.test(name), 'Unknown opcode.'); - assert(name.length === 7, 'Unknown opcode.'); - - const value = parseInt(name.substring(5), 16); - - assert((value & 0xff) === value, 'Unknown opcode.'); - - return Opcode.fromOp(value); -}; - -/** - * Instantiate opcode from buffer reader. - * @param {BufferReader} br - * @returns {Opcode} - */ - -Opcode.fromReader = function fromReader(br) { - const value = br.readU8(); - const op = opCache[value]; - - if (op) - return op; - - switch (value) { - case opcodes.OP_PUSHDATA1: { - if (br.left() < 1) - return PARSE_ERROR; - - const size = br.readU8(); - - if (br.left() < size) { - br.seek(br.left()); - return PARSE_ERROR; - } - - const data = br.readBytes(size); - - return new Opcode(value, data); + if (data[0] >= 1 && data[0] <= 16) + return this.fromOp(data[0] + 0x50); } - case opcodes.OP_PUSHDATA2: { - if (br.left() < 2) { - br.seek(br.left()); - return PARSE_ERROR; + + return this.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} + */ + + static fromPush(data) { + assert(Buffer.isBuffer(data)); + + if (data.length === 0) + return this.fromOp(opcodes.OP_0); + + if (data.length <= 0x4b) + return new this(data.length, data); + + if (data.length <= 0xff) + return new this(opcodes.OP_PUSHDATA1, data); + + if (data.length <= 0xffff) + return new this(opcodes.OP_PUSHDATA2, data); + + if (data.length <= 0xffffffff) + return new this(opcodes.OP_PUSHDATA4, data); + + throw new Error('Pushdata size too large.'); + } + + /** + * 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); + } + + /** + * Instantiate an opcode from a small number. + * @param {Number} num + * @returns {Opcode} + */ + + static fromSmall(num) { + assert((num & 0xff) === num && num >= 0 && num <= 16); + return this.fromOp(num === 0 ? 0 : num + 0x50); + } + + /** + * Instantiate an opcode from a ScriptNum. + * @param {ScriptNumber} num + * @returns {Opcode} + */ + + static fromNum(num) { + assert(ScriptNum.isScriptNum(num)); + return this.fromData(num.encode()); + } + + /** + * Instantiate an opcode from a Number. + * @param {Number} num + * @returns {Opcode} + */ + + static fromInt(num) { + assert(Number.isSafeInteger(num)); + + if (num === 0) + return this.fromOp(opcodes.OP_0); + + if (num === -1) + return this.fromOp(opcodes.OP_1NEGATE); + + if (num >= 1 && num <= 16) + return this.fromOp(num + 0x50); + + return this.fromNum(ScriptNum.fromNumber(num)); + } + + /** + * Instantiate an opcode from a Number. + * @param {Boolean} value + * @returns {Opcode} + */ + + static fromBool(value) { + assert(typeof value === 'boolean'); + return this.fromSmall(value ? 1 : 0); + } + + /** + * Instantiate a pushdata opcode from symbolic name. + * @example + * Opcode.fromSymbol('checksequenceverify') + * @param {String} name + * @returns {Opcode} + */ + + static fromSymbol(name) { + assert(typeof name === 'string'); + assert(name.length > 0); + + if (name.charCodeAt(0) & 32) + name = name.toUpperCase(); + + if (!/^OP_/.test(name)) + name = `OP_${name}`; + + const op = common.opcodes[name]; + + if (op != null) + return this.fromOp(op); + + assert(/^OP_0X/.test(name), 'Unknown opcode.'); + assert(name.length === 7, 'Unknown opcode.'); + + const value = parseInt(name.substring(5), 16); + + assert((value & 0xff) === value, 'Unknown opcode.'); + + return this.fromOp(value); + } + + /** + * Instantiate opcode from buffer reader. + * @param {BufferReader} br + * @returns {Opcode} + */ + + static fromReader(br) { + const value = br.readU8(); + const op = opCache[value]; + + if (op) + return op; + + switch (value) { + case opcodes.OP_PUSHDATA1: { + if (br.left() < 1) + return PARSE_ERROR; + + const size = br.readU8(); + + if (br.left() < size) { + br.seek(br.left()); + return PARSE_ERROR; + } + + const data = br.readBytes(size); + + return new this(value, data); } + case opcodes.OP_PUSHDATA2: { + if (br.left() < 2) { + br.seek(br.left()); + return PARSE_ERROR; + } - const size = br.readU16(); + const size = br.readU16(); - if (br.left() < size) { - br.seek(br.left()); - return PARSE_ERROR; + if (br.left() < size) { + br.seek(br.left()); + return PARSE_ERROR; + } + + const data = br.readBytes(size); + + return new this(value, data); } + case opcodes.OP_PUSHDATA4: { + if (br.left() < 4) { + br.seek(br.left()); + return PARSE_ERROR; + } - const data = br.readBytes(size); + const size = br.readU32(); - return new Opcode(value, data); - } - case opcodes.OP_PUSHDATA4: { - if (br.left() < 4) { - br.seek(br.left()); - return PARSE_ERROR; + if (br.left() < size) { + br.seek(br.left()); + return PARSE_ERROR; + } + + const data = br.readBytes(size); + + return new this(value, data); } + default: { + if (br.left() < value) { + br.seek(br.left()); + return PARSE_ERROR; + } - const size = br.readU32(); + const data = br.readBytes(value); - if (br.left() < size) { - br.seek(br.left()); - return PARSE_ERROR; + return new this(value, data); } - - const data = br.readBytes(size); - - return new Opcode(value, data); - } - default: { - if (br.left() < value) { - br.seek(br.left()); - return PARSE_ERROR; - } - - const data = br.readBytes(value); - - return new Opcode(value, data); } } -}; -/** - * Instantiate opcode from serialized data. - * @param {Buffer} data - * @returns {Opcode} - */ + /** + * Instantiate opcode from serialized data. + * @param {Buffer} data + * @returns {Opcode} + */ -Opcode.fromRaw = function fromRaw(data) { - return Opcode.fromReader(bio.read(data)); -}; + static fromRaw(data) { + return this.fromReader(bio.read(data)); + } -/** - * Test whether an object an Opcode. - * @param {Object} obj - * @returns {Boolean} - */ + /** + * Test whether an object an Opcode. + * @param {Object} obj + * @returns {Boolean} + */ -Opcode.isOpcode = function isOpcode(obj) { - return obj instanceof Opcode; -}; + static isOpcode(obj) { + return obj instanceof Opcode; + } +} /* * Helpers diff --git a/lib/script/program.js b/lib/script/program.js index 6ad000c5..7ed1bcab 100644 --- a/lib/script/program.js +++ b/lib/script/program.js @@ -13,91 +13,92 @@ const scriptTypes = common.types; /** * Witness Program - * @constructor * @alias module:script.Program - * @param {Number} version - * @param {Buffer} data * @property {Number} version - Ranges from 0 to 16. - * @property {String|null} type - Null if malformed. `unknown` if unknown - * version (treated as anyone-can-spend). Otherwise one of `witnesspubkeyhash` - * or `witnessscripthash`. + * @property {String|null} type - Null if malformed. * @property {Buffer} data - The hash (for now). */ -function Program(version, data) { - if (!(this instanceof Program)) - return new Program(version, data); +class Program { + /** + * Create a witness program. + * @constructor + * @param {Number} version + * @param {Buffer} data + */ - assert((version & 0xff) === version); - assert(version >= 0 && version <= 16); - assert(Buffer.isBuffer(data)); - assert(data.length >= 2 && data.length <= 40); + constructor(version, data) { + assert((version & 0xff) === version); + assert(version >= 0 && version <= 16); + assert(Buffer.isBuffer(data)); + assert(data.length >= 2 && data.length <= 40); - this.version = version; - this.data = data; + this.version = version; + this.data = data; + } + + /** + * Get the witness program type. + * @returns {ScriptType} + */ + + getType() { + if (this.version === 0) { + if (this.data.length === 20) + return scriptTypes.WITNESSPUBKEYHASH; + + if (this.data.length === 32) + return scriptTypes.WITNESSSCRIPTHASH; + + // Fail on bad version=0 + return scriptTypes.WITNESSMALFORMED; + } + + if (this.version === 1) { + if (this.data.length === 32) + return scriptTypes.WITNESSMASTHASH; + + // Fail on bad version=1 + return scriptTypes.WITNESSMALFORMED; + } + + // No interpretation of script (anyone can spend) + return scriptTypes.NONSTANDARD; + } + + /** + * Test whether the program is either + * an unknown version or malformed. + * @returns {Boolean} + */ + + isUnknown() { + const type = this.getType(); + return type === scriptTypes.WITNESSMALFORMED + || type === scriptTypes.NONSTANDARD; + } + + /** + * Test whether the program is malformed. + * @returns {Boolean} + */ + + isMalformed() { + return this.getType() === scriptTypes.WITNESSMALFORMED; + } + + /** + * Inspect the program. + * @returns {String} + */ + + inspect() { + const data = this.data.toString('hex'); + const type = common.typesByVal[this.getType()].toLowerCase(); + return ``; + } } -/** - * Get the witness program type. - * @returns {ScriptType} - */ - -Program.prototype.getType = function getType() { - if (this.version === 0) { - if (this.data.length === 20) - return scriptTypes.WITNESSPUBKEYHASH; - - if (this.data.length === 32) - return scriptTypes.WITNESSSCRIPTHASH; - - // Fail on bad version=0 - return scriptTypes.WITNESSMALFORMED; - } - - if (this.version === 1) { - if (this.data.length === 32) - return scriptTypes.WITNESSMASTHASH; - - // Fail on bad version=1 - return scriptTypes.WITNESSMALFORMED; - } - - // No interpretation of script (anyone can spend) - return scriptTypes.NONSTANDARD; -}; - -/** - * Test whether the program is either - * an unknown version or malformed. - * @returns {Boolean} - */ - -Program.prototype.isUnknown = function isUnknown() { - const type = this.getType(); - return type === scriptTypes.WITNESSMALFORMED - || type === scriptTypes.NONSTANDARD; -}; - -/** - * Test whether the program is malformed. - * @returns {Boolean} - */ - -Program.prototype.isMalformed = function isMalformed() { - return this.getType() === scriptTypes.WITNESSMALFORMED; -}; - -/** - * Inspect the program. - * @returns {String} - */ - -Program.prototype.inspect = function inspect() { - const data = this.data.toString('hex'); - const type = common.typesByVal[this.getType()].toLowerCase(); - return ``; -}; - /* * Expose */ diff --git a/lib/script/script.js b/lib/script/script.js index df87b029..5fee5ab6 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -38,25 +38,3440 @@ const {encoding} = bio; const EMPTY_BUFFER = Buffer.alloc(0); /** + * Script * Represents a input or output script. * @alias module:script.Script - * @constructor - * @param {Buffer|Array|Object|NakedScript} code - Array - * of script code or a serialized script Buffer. * @property {Array} code - Parsed script code. * @property {Buffer?} raw - Serialized script. * @property {Number} length - Number of parsed opcodes. */ -function Script(options) { - if (!(this instanceof Script)) - return new Script(options); +class Script { + /** + * Create a script. + * @constructor + * @param {Buffer|Array|Object} code + */ - this.raw = EMPTY_BUFFER; - this.code = []; + constructor(options) { + this.raw = EMPTY_BUFFER; + this.code = []; - if (options) - this.fromOptions(options); + if (options) + this.fromOptions(options); + } + + /** + * Get length. + * @returns {Number} + */ + + get length() { + return this.code.length; + } + + /** + * Set length. + * @param {Number} value + */ + + set length(value) { + this.code.length = value; + } + + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(options, 'Script data is required.'); + + if (Buffer.isBuffer(options)) + return this.fromRaw(options); + + if (Array.isArray(options)) + return this.fromArray(options); + + if (options.raw) { + if (!options.code) + return this.fromRaw(options.raw); + assert(Buffer.isBuffer(options.raw), 'Raw must be a Buffer.'); + this.raw = options.raw; + } + + if (options.code) { + if (!options.raw) + return this.fromArray(options.code); + assert(Array.isArray(options.code), 'Code must be an array.'); + this.code = options.code; + } + + return this; + } + + /** + * Insantiate script from options object. + * @param {Object} options + * @returns {Script} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } + + /** + * Instantiate a value-only iterator. + * @returns {ScriptIterator} + */ + + values() { + return this.code.values(); + } + + /** + * Instantiate a key and value iterator. + * @returns {ScriptIterator} + */ + + entries() { + return this.code.entries(); + } + + /** + * Instantiate a value-only iterator. + * @returns {ScriptIterator} + */ + + [Symbol.iterator]() { + return this.code[Symbol.iterator](); + } + + /** + * Convert the script to an array of + * Buffers (pushdatas) and Numbers + * (opcodes). + * @returns {Array} + */ + + toArray() { + return this.code.slice(); + } + + /** + * Inject properties from an array of + * of buffers and numbers. + * @private + * @param {Array} code + * @returns {Script} + */ + + fromArray(code) { + assert(Array.isArray(code)); + + this.clear(); + + for (const op of code) + this.push(op); + + return this.compile(); + } + + /** + * Instantiate script from an array + * of buffers and numbers. + * @param {Array} code + * @returns {Script} + */ + + static fromArray(code) { + return new this().fromArray(code); + } + + /** + * Convert script to stack items. + * @returns {Buffer[]} + */ + + toItems() { + const items = []; + + for (const op of this.code) { + const data = op.toPush(); + + if (!data) + throw new Error('Non-push opcode in script.'); + + items.push(data); + } + + return items; + } + + /** + * Inject data from stack items. + * @private + * @param {Buffer[]} items + * @returns {Script} + */ + + fromItems(items) { + assert(Array.isArray(items)); + + this.clear(); + + for (const item of items) + this.pushData(item); + + return this.compile(); + } + + /** + * Instantiate script from stack items. + * @param {Buffer[]} items + * @returns {Script} + */ + + static fromItems(items) { + return new this().fromItems(items); + } + + /** + * Convert script to stack. + * @returns {Stack} + */ + + toStack() { + return new Stack(this.toItems()); + } + + /** + * Inject data from stack. + * @private + * @param {Stack} stack + * @returns {Script} + */ + + fromStack(stack) { + return this.fromItems(stack.items); + } + + /** + * Instantiate script from stack. + * @param {Stack} stack + * @returns {Script} + */ + + static fromStack(stack) { + return new this().fromStack(stack); + } + + /** + * Clone the script. + * @returns {Script} Cloned script. + */ + + clone() { + return new this.constructor().inject(this); + } + + /** + * Inject properties from script. + * Used for cloning. + * @private + * @param {Script} script + * @returns {Script} + */ + + inject(script) { + this.raw = script.raw; + this.code = script.code.slice(); + return this; + } + + /** + * Test equality against script. + * @param {Script} script + * @returns {Boolean} + */ + + equals(script) { + assert(Script.isScript(script)); + return this.raw.equals(script.raw); + } + + /** + * Compare against another script. + * @param {Script} script + * @returns {Number} + */ + + compare(script) { + assert(Script.isScript(script)); + return this.raw.compare(script.raw); + } + + /** + * Clear the script. + * @returns {Script} + */ + + clear() { + this.raw = EMPTY_BUFFER; + this.code.length = 0; + return this; + } + + /** + * Inspect the script. + * @returns {String} Human-readable script code. + */ + + inspect() { + return ``; + } + + /** + * Convert the script to a bitcoind test string. + * @returns {String} Human-readable script code. + */ + + toString() { + const out = []; + + for (const op of this.code) + out.push(op.toFormat()); + + return out.join(' '); + } + + /** + * Format the script as bitcoind asm. + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable script. + */ + + toASM(decode) { + if (this.isNulldata()) + decode = false; + + const out = []; + + for (const op of this.code) + out.push(op.toASM(decode)); + + return out.join(' '); + } + + /** + * Re-encode the script internally. Useful if you + * changed something manually in the `code` array. + * @returns {Script} + */ + + compile() { + if (this.code.length === 0) + return this.clear(); + + let size = 0; + + for (const op of this.code) + size += op.getSize(); + + const bw = bio.write(size); + + for (const op of this.code) + op.toWriter(bw); + + this.raw = bw.render(); + + return this; + } + + /** + * Write the script to a buffer writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeVarBytes(this.raw); + return bw; + } + + /** + * Encode the script to a Buffer. See {@link Script#encode}. + * @param {String} enc - Encoding, either `'hex'` or `null`. + * @returns {Buffer|String} Serialized script. + */ + + toRaw() { + return this.raw; + } + + /** + * Convert script to a hex string. + * @returns {String} + */ + + toJSON() { + return this.toRaw().toString('hex'); + } + + /** + * Inject properties from json object. + * @private + * @param {String} json + */ + + fromJSON(json) { + assert(typeof json === 'string', 'Code must be a string.'); + return this.fromRaw(Buffer.from(json, 'hex')); + } + + /** + * Instantiate script from a hex string. + * @params {String} json + * @returns {Script} + */ + + static fromJSON(json) { + return new this().fromJSON(json); + } + + /** + * Get the script's "subscript" starting at a separator. + * @param {Number} index - The last separator to sign/verify beyond. + * @returns {Script} Subscript. + */ + + getSubscript(index) { + if (index === 0) + return this.clone(); + + const script = new Script(); + + for (let i = index; i < this.code.length; i++) { + const op = this.code[i]; + + if (op.value === -1) + break; + + script.code.push(op); + } + + return script.compile(); + } + + /** + * Get the script's "subscript" starting at a separator. + * Remove all OP_CODESEPARATORs if present. This bizarre + * behavior is necessary for signing and verification when + * code separators are present. + * @returns {Script} Subscript. + */ + + removeSeparators() { + let found = false; + + // Optimizing for the common case: + // Check for any separators first. + for (const op of this.code) { + if (op.value === -1) + break; + + if (op.value === opcodes.OP_CODESEPARATOR) { + found = true; + break; + } + } + + if (!found) + return this; + + // Uncommon case: someone actually + // has a code separator. Go through + // and remove them all. + const script = new Script(); + + for (const op of this.code) { + if (op.value === -1) + break; + + if (op.value !== opcodes.OP_CODESEPARATOR) + script.code.push(op); + } + + return script.compile(); + } + + /** + * Execute and interpret the script. + * @param {Stack} stack - Script execution stack. + * @param {Number?} flags - Script standard flags. + * @param {TX?} tx - Transaction being verified. + * @param {Number?} index - Index of input being verified. + * @param {Amount?} value - Previous output value. + * @param {Number?} version - Signature hash version (0=legacy, 1=segwit). + * @throws {ScriptError} Will be thrown on VERIFY failures, among other things. + */ + + execute(stack, flags, tx, index, value, version) { + if (flags == null) + flags = Script.flags.STANDARD_VERIFY_FLAGS; + + if (version == null) + version = 0; + + if (this.raw.length > consensus.MAX_SCRIPT_SIZE) + throw new ScriptError('SCRIPT_SIZE'); + + const state = []; + const alt = []; + + let lastSep = 0; + let opCount = 0; + let negate = 0; + let minimal = false; + + if (flags & Script.flags.VERIFY_MINIMALDATA) + minimal = true; + + for (let ip = 0; ip < this.code.length; ip++) { + const op = this.code[ip]; + + if (op.value === -1) + throw new ScriptError('BAD_OPCODE', op, ip); + + if (op.data && op.data.length > consensus.MAX_SCRIPT_PUSH) + throw new ScriptError('PUSH_SIZE', op, ip); + + if (op.value > opcodes.OP_16 && ++opCount > consensus.MAX_SCRIPT_OPS) + throw new ScriptError('OP_COUNT', op, ip); + + if (op.isDisabled()) + throw new ScriptError('DISABLED_OPCODE', op, ip); + + if (negate && !op.isBranch()) { + if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) + throw new ScriptError('STACK_SIZE', op, ip); + continue; + } + + if (op.data) { + if (minimal && !op.isMinimal()) + throw new ScriptError('MINIMALDATA', op, ip); + + stack.push(op.data); + + if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) + throw new ScriptError('STACK_SIZE', op, ip); + + continue; + } + + switch (op.value) { + case opcodes.OP_0: { + stack.pushInt(0); + break; + } + case opcodes.OP_1NEGATE: { + stack.pushInt(-1); + break; + } + case opcodes.OP_1: + case opcodes.OP_2: + case opcodes.OP_3: + case opcodes.OP_4: + case opcodes.OP_5: + case opcodes.OP_6: + case opcodes.OP_7: + case opcodes.OP_8: + case opcodes.OP_9: + case opcodes.OP_10: + case opcodes.OP_11: + case opcodes.OP_12: + case opcodes.OP_13: + case opcodes.OP_14: + case opcodes.OP_15: + case opcodes.OP_16: { + stack.pushInt(op.value - 0x50); + break; + } + case opcodes.OP_NOP: { + break; + } + case opcodes.OP_CHECKLOCKTIMEVERIFY: { + // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 + if (!(flags & Script.flags.VERIFY_CHECKLOCKTIMEVERIFY)) { + if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) + throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); + break; + } + + if (!tx) + throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); + + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const num = stack.getNum(-1, minimal, 5); + + if (num.isNeg()) + throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); + + const locktime = num.toDouble(); + + if (!tx.verifyLocktime(index, locktime)) + throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); + + break; + } + case opcodes.OP_CHECKSEQUENCEVERIFY: { + // OP_CHECKSEQUENCEVERIFY = OP_NOP3 + if (!(flags & Script.flags.VERIFY_CHECKSEQUENCEVERIFY)) { + if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) + throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); + break; + } + + if (!tx) + throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); + + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const num = stack.getNum(-1, minimal, 5); + + if (num.isNeg()) + throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); + + const locktime = num.toDouble(); + + if (!tx.verifySequence(index, locktime)) + throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); + + break; + } + case opcodes.OP_NOP1: + case opcodes.OP_NOP4: + case opcodes.OP_NOP5: + case opcodes.OP_NOP6: + case opcodes.OP_NOP7: + case opcodes.OP_NOP8: + case opcodes.OP_NOP9: + case opcodes.OP_NOP10: { + if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) + throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); + break; + } + case opcodes.OP_IF: + case opcodes.OP_NOTIF: { + let val = false; + + if (!negate) { + if (stack.length < 1) + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); + + if (version === 1 && (flags & Script.flags.VERIFY_MINIMALIF)) { + const item = stack.get(-1); + + if (item.length > 1) + throw new ScriptError('MINIMALIF'); + + if (item.length === 1 && item[0] !== 1) + throw new ScriptError('MINIMALIF'); + } + + val = stack.getBool(-1); + + if (op.value === opcodes.OP_NOTIF) + val = !val; + + stack.pop(); + } + + state.push(val); + + if (!val) + negate += 1; + + break; + } + case opcodes.OP_ELSE: { + if (state.length === 0) + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); + + state[state.length - 1] = !state[state.length - 1]; + + if (!state[state.length - 1]) + negate += 1; + else + negate -= 1; + + break; + } + case opcodes.OP_ENDIF: { + if (state.length === 0) + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); + + if (!state.pop()) + negate -= 1; + + break; + } + case opcodes.OP_VERIFY: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + if (!stack.getBool(-1)) + throw new ScriptError('VERIFY', op, ip); + + stack.pop(); + + break; + } + case opcodes.OP_RETURN: { + throw new ScriptError('OP_RETURN', op, ip); + } + case opcodes.OP_TOALTSTACK: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + alt.push(stack.pop()); + break; + } + case opcodes.OP_FROMALTSTACK: { + if (alt.length === 0) + throw new ScriptError('INVALID_ALTSTACK_OPERATION', op, ip); + + stack.push(alt.pop()); + break; + } + case opcodes.OP_2DROP: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.pop(); + stack.pop(); + break; + } + case opcodes.OP_2DUP: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const v1 = stack.get(-2); + const v2 = stack.get(-1); + + stack.push(v1); + stack.push(v2); + break; + } + case opcodes.OP_3DUP: { + if (stack.length < 3) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const v1 = stack.get(-3); + const v2 = stack.get(-2); + const v3 = stack.get(-1); + + stack.push(v1); + stack.push(v2); + stack.push(v3); + break; + } + case opcodes.OP_2OVER: { + if (stack.length < 4) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const v1 = stack.get(-4); + const v2 = stack.get(-3); + + stack.push(v1); + stack.push(v2); + break; + } + case opcodes.OP_2ROT: { + if (stack.length < 6) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const v1 = stack.get(-6); + const v2 = stack.get(-5); + + stack.erase(-6, -4); + stack.push(v1); + stack.push(v2); + break; + } + case opcodes.OP_2SWAP: { + if (stack.length < 4) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.swap(-4, -2); + stack.swap(-3, -1); + break; + } + case opcodes.OP_IFDUP: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + if (stack.getBool(-1)) { + const val = stack.get(-1); + stack.push(val); + } + + break; + } + case opcodes.OP_DEPTH: { + stack.pushInt(stack.length); + break; + } + case opcodes.OP_DROP: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.pop(); + break; + } + case opcodes.OP_DUP: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(stack.get(-1)); + break; + } + case opcodes.OP_NIP: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.remove(-2); + break; + } + case opcodes.OP_OVER: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(stack.get(-2)); + break; + } + case opcodes.OP_PICK: + case opcodes.OP_ROLL: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const num = stack.getInt(-1, minimal, 4); + stack.pop(); + + if (num < 0 || num >= stack.length) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const val = stack.get(-num - 1); + + if (op.value === opcodes.OP_ROLL) + stack.remove(-num - 1); + + stack.push(val); + break; + } + case opcodes.OP_ROT: { + if (stack.length < 3) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.swap(-3, -2); + stack.swap(-2, -1); + break; + } + case opcodes.OP_SWAP: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.swap(-2, -1); + break; + } + case opcodes.OP_TUCK: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.insert(-2, stack.get(-1)); + break; + } + case opcodes.OP_SIZE: { + if (stack.length < 1) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.pushInt(stack.get(-1).length); + break; + } + case opcodes.OP_EQUAL: + case opcodes.OP_EQUALVERIFY: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const v1 = stack.get(-2); + const v2 = stack.get(-1); + + const res = v1.equals(v2); + + stack.pop(); + stack.pop(); + + stack.pushBool(res); + + if (op.value === opcodes.OP_EQUALVERIFY) { + if (!res) + throw new ScriptError('EQUALVERIFY', op, ip); + stack.pop(); + } + + break; + } + case opcodes.OP_1ADD: + case opcodes.OP_1SUB: + case opcodes.OP_NEGATE: + case opcodes.OP_ABS: + case opcodes.OP_NOT: + case opcodes.OP_0NOTEQUAL: { + if (stack.length < 1) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + let num = stack.getNum(-1, minimal, 4); + let cmp; + + switch (op.value) { + case opcodes.OP_1ADD: + num.iaddn(1); + break; + case opcodes.OP_1SUB: + num.isubn(1); + break; + case opcodes.OP_NEGATE: + num.ineg(); + break; + case opcodes.OP_ABS: + num.iabs(); + break; + case opcodes.OP_NOT: + cmp = num.isZero(); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_0NOTEQUAL: + cmp = !num.isZero(); + num = ScriptNum.fromBool(cmp); + break; + default: + assert(false, 'Fatal script error.'); + break; + } + + stack.pop(); + stack.pushNum(num); + + break; + } + case opcodes.OP_ADD: + case opcodes.OP_SUB: + case opcodes.OP_BOOLAND: + case opcodes.OP_BOOLOR: + case opcodes.OP_NUMEQUAL: + case opcodes.OP_NUMEQUALVERIFY: + case opcodes.OP_NUMNOTEQUAL: + case opcodes.OP_LESSTHAN: + case opcodes.OP_GREATERTHAN: + case opcodes.OP_LESSTHANOREQUAL: + case opcodes.OP_GREATERTHANOREQUAL: + case opcodes.OP_MIN: + case opcodes.OP_MAX: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const n1 = stack.getNum(-2, minimal, 4); + const n2 = stack.getNum(-1, minimal, 4); + let num, cmp; + + switch (op.value) { + case opcodes.OP_ADD: + num = n1.iadd(n2); + break; + case opcodes.OP_SUB: + num = n1.isub(n2); + break; + case opcodes.OP_BOOLAND: + cmp = n1.toBool() && n2.toBool(); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_BOOLOR: + cmp = n1.toBool() || n2.toBool(); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_NUMEQUAL: + cmp = n1.eq(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_NUMEQUALVERIFY: + cmp = n1.eq(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_NUMNOTEQUAL: + cmp = !n1.eq(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_LESSTHAN: + cmp = n1.lt(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_GREATERTHAN: + cmp = n1.gt(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_LESSTHANOREQUAL: + cmp = n1.lte(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_GREATERTHANOREQUAL: + cmp = n1.gte(n2); + num = ScriptNum.fromBool(cmp); + break; + case opcodes.OP_MIN: + num = ScriptNum.min(n1, n2); + break; + case opcodes.OP_MAX: + num = ScriptNum.max(n1, n2); + break; + default: + assert(false, 'Fatal script error.'); + break; + } + + stack.pop(); + stack.pop(); + stack.pushNum(num); + + if (op.value === opcodes.OP_NUMEQUALVERIFY) { + if (!stack.getBool(-1)) + throw new ScriptError('NUMEQUALVERIFY', op, ip); + stack.pop(); + } + + break; + } + case opcodes.OP_WITHIN: { + if (stack.length < 3) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const n1 = stack.getNum(-3, minimal, 4); + const n2 = stack.getNum(-2, minimal, 4); + const n3 = stack.getNum(-1, minimal, 4); + + const val = n2.lte(n1) && n1.lt(n3); + + stack.pop(); + stack.pop(); + stack.pop(); + + stack.pushBool(val); + break; + } + case opcodes.OP_RIPEMD160: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(ripemd160.digest(stack.pop())); + break; + } + case opcodes.OP_SHA1: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(sha1.digest(stack.pop())); + break; + } + case opcodes.OP_SHA256: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(sha256.digest(stack.pop())); + break; + } + case opcodes.OP_HASH160: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(hash160.digest(stack.pop())); + break; + } + case opcodes.OP_HASH256: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(hash256.digest(stack.pop())); + break; + } + case opcodes.OP_CODESEPARATOR: { + lastSep = ip + 1; + break; + } + case opcodes.OP_CHECKSIG: + case opcodes.OP_CHECKSIGVERIFY: { + if (!tx) + throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); + + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const sig = stack.get(-2); + const key = stack.get(-1); + + const subscript = this.getSubscript(lastSep); + + if (version === 0) + subscript.findAndDelete(sig); + + validateSignature(sig, flags); + validateKey(key, flags, version); + + let res = false; + + if (sig.length > 0) { + const type = sig[sig.length - 1]; + const hash = tx.signatureHash(index, subscript, value, type, version); + res = checksig(hash, sig, key); + } + + if (!res && (flags & Script.flags.VERIFY_NULLFAIL)) { + if (sig.length !== 0) + throw new ScriptError('NULLFAIL', op, ip); + } + + stack.pop(); + stack.pop(); + + stack.pushBool(res); + + if (op.value === opcodes.OP_CHECKSIGVERIFY) { + if (!res) + throw new ScriptError('CHECKSIGVERIFY', op, ip); + stack.pop(); + } + + break; + } + case opcodes.OP_CHECKMULTISIG: + case opcodes.OP_CHECKMULTISIGVERIFY: { + if (!tx) + throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); + + let i = 1; + if (stack.length < i) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + let n = stack.getInt(-i, minimal, 4); + let okey = n + 2; + let ikey, isig; + + if (n < 0 || n > consensus.MAX_MULTISIG_PUBKEYS) + throw new ScriptError('PUBKEY_COUNT', op, ip); + + opCount += n; + + if (opCount > consensus.MAX_SCRIPT_OPS) + throw new ScriptError('OP_COUNT', op, ip); + + i += 1; + ikey = i; + i += n; + + if (stack.length < i) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + let m = stack.getInt(-i, minimal, 4); + + if (m < 0 || m > n) + throw new ScriptError('SIG_COUNT', op, ip); + + i += 1; + isig = i; + i += m; + + if (stack.length < i) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + const subscript = this.getSubscript(lastSep); + + for (let j = 0; j < m; j++) { + const sig = stack.get(-isig - j); + if (version === 0) + subscript.findAndDelete(sig); + } + + let res = true; + while (res && m > 0) { + const sig = stack.get(-isig); + const key = stack.get(-ikey); + + validateSignature(sig, flags); + validateKey(key, flags, version); + + if (sig.length > 0) { + const type = sig[sig.length - 1]; + const hash = tx.signatureHash( + index, + subscript, + value, + type, + version + ); + + if (checksig(hash, sig, key)) { + isig += 1; + m -= 1; + } + } + + ikey += 1; + n -= 1; + + if (m > n) + res = false; + } + + while (i > 1) { + if (!res && (flags & Script.flags.VERIFY_NULLFAIL)) { + if (okey === 0 && stack.get(-1).length !== 0) + throw new ScriptError('NULLFAIL', op, ip); + } + + if (okey > 0) + okey -= 1; + + stack.pop(); + + i -= 1; + } + + if (stack.length < 1) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + if (flags & Script.flags.VERIFY_NULLDUMMY) { + if (stack.get(-1).length !== 0) + throw new ScriptError('SIG_NULLDUMMY', op, ip); + } + + stack.pop(); + + stack.pushBool(res); + + if (op.value === opcodes.OP_CHECKMULTISIGVERIFY) { + if (!res) + throw new ScriptError('CHECKMULTISIGVERIFY', op, ip); + stack.pop(); + } + + break; + } + default: { + throw new ScriptError('BAD_OPCODE', op, ip); + } + } + + if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) + throw new ScriptError('STACK_SIZE', op, ip); + } + + if (state.length !== 0) + throw new ScriptError('UNBALANCED_CONDITIONAL'); + } + + /** + * Remove all matched data elements from + * a script's code (used to remove signatures + * before verification). Note that this + * compares and removes data on the _byte level_. + * It also reserializes the data to a single + * script with minimaldata encoding beforehand. + * A signature will _not_ be removed if it is + * not minimaldata. + * @see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-November/006878.html + * @see https://test.webbtc.com/tx/19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff + * @param {Buffer} data - Data element to match against. + * @returns {Number} Total. + */ + + findAndDelete(data) { + const target = Opcode.fromPush(data); + + if (this.raw.length < target.getSize()) + return 0; + + let found = false; + + for (const op of this.code) { + if (op.value === -1) + break; + + if (op.equals(target)) { + found = true; + break; + } + } + + if (!found) + return 0; + + const code = []; + + let total = 0; + + for (const op of this.code) { + if (op.value === -1) + break; + + if (op.equals(target)) { + total += 1; + continue; + } + + code.push(op); + } + + this.code = code; + this.compile(); + + return total; + } + + /** + * Find a data element in a script. + * @param {Buffer} data - Data element to match against. + * @returns {Number} Index (`-1` if not present). + */ + + indexOf(data) { + for (let i = 0; i < this.code.length; i++) { + const op = this.code[i]; + + if (op.value === -1) + break; + + if (!op.data) + continue; + + if (op.data.equals(data)) + return i; + } + + return -1; + } + + /** + * Test a script to see if it is likely + * to be script code (no weird opcodes). + * @returns {Boolean} + */ + + isCode() { + for (const op of this.code) { + if (op.value === -1) + return false; + + if (op.isDisabled()) + return false; + + switch (op.value) { + case opcodes.OP_RESERVED: + case opcodes.OP_NOP: + case opcodes.OP_VER: + case opcodes.OP_VERIF: + case opcodes.OP_VERNOTIF: + case opcodes.OP_RESERVED1: + case opcodes.OP_RESERVED2: + case opcodes.OP_NOP1: + return false; + } + + if (op.value > opcodes.OP_CHECKSEQUENCEVERIFY) + return false; + } + + return true; + } + + /** + * Inject properties from a pay-to-pubkey script. + * @private + * @param {Buffer} key + */ + + fromPubkey(key) { + assert(Buffer.isBuffer(key) && (key.length === 33 || key.length === 65)); + + this.raw = Buffer.allocUnsafe(1 + key.length + 1); + this.raw[0] = key.length; + key.copy(this.raw, 1); + this.raw[1 + key.length] = opcodes.OP_CHECKSIG; + + key = this.raw.slice(1, 1 + key.length); + + this.code.length = 0; + this.code.push(Opcode.fromPush(key)); + this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); + + return this; + } + + /** + * Create a pay-to-pubkey script. + * @param {Buffer} key + * @returns {Script} + */ + + static fromPubkey(key) { + return new this().fromPubkey(key); + } + + /** + * Inject properties from a pay-to-pubkeyhash script. + * @private + * @param {Buffer} hash + */ + + fromPubkeyhash(hash) { + assert(Buffer.isBuffer(hash) && hash.length === 20); + + this.raw = Buffer.allocUnsafe(25); + this.raw[0] = opcodes.OP_DUP; + this.raw[1] = opcodes.OP_HASH160; + this.raw[2] = 0x14; + hash.copy(this.raw, 3); + this.raw[23] = opcodes.OP_EQUALVERIFY; + this.raw[24] = opcodes.OP_CHECKSIG; + + hash = this.raw.slice(3, 23); + + this.code.length = 0; + this.code.push(Opcode.fromOp(opcodes.OP_DUP)); + this.code.push(Opcode.fromOp(opcodes.OP_HASH160)); + this.code.push(Opcode.fromPush(hash)); + this.code.push(Opcode.fromOp(opcodes.OP_EQUALVERIFY)); + this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); + + return this; + } + + /** + * Create a pay-to-pubkeyhash script. + * @param {Buffer} hash + * @returns {Script} + */ + + static fromPubkeyhash(hash) { + return new this().fromPubkeyhash(hash); + } + + /** + * Inject properties from pay-to-multisig script. + * @private + * @param {Number} m + * @param {Number} n + * @param {Buffer[]} keys + */ + + fromMultisig(m, n, keys) { + assert((m & 0xff) === m && (n & 0xff) === n); + assert(Array.isArray(keys)); + assert(keys.length === n, '`n` keys are required for multisig.'); + assert(m >= 1 && m <= n); + assert(n >= 1 && n <= 15); + + this.clear(); + + this.pushSmall(m); + + for (const key of sortKeys(keys)) + this.pushData(key); + + this.pushSmall(n); + this.pushOp(opcodes.OP_CHECKMULTISIG); + + return this.compile(); + } + + /** + * Create a pay-to-multisig script. + * @param {Number} m + * @param {Number} n + * @param {Buffer[]} keys + * @returns {Script} + */ + + static fromMultisig(m, n, keys) { + return new this().fromMultisig(m, n, keys); + } + + /** + * Inject properties from a pay-to-scripthash script. + * @private + * @param {Buffer} hash + */ + + fromScripthash(hash) { + assert(Buffer.isBuffer(hash) && hash.length === 20); + + this.raw = Buffer.allocUnsafe(23); + this.raw[0] = opcodes.OP_HASH160; + this.raw[1] = 0x14; + hash.copy(this.raw, 2); + this.raw[22] = opcodes.OP_EQUAL; + + hash = this.raw.slice(2, 22); + + this.code.length = 0; + this.code.push(Opcode.fromOp(opcodes.OP_HASH160)); + this.code.push(Opcode.fromPush(hash)); + this.code.push(Opcode.fromOp(opcodes.OP_EQUAL)); + + return this; + } + + /** + * Create a pay-to-scripthash script. + * @param {Buffer} hash + * @returns {Script} + */ + + static fromScripthash(hash) { + return new this().fromScripthash(hash); + } + + /** + * Inject properties from a nulldata/opreturn script. + * @private + * @param {Buffer} flags + */ + + fromNulldata(flags) { + assert(Buffer.isBuffer(flags)); + assert(flags.length <= policy.MAX_OP_RETURN, 'Nulldata too large.'); + + this.clear(); + this.pushOp(opcodes.OP_RETURN); + this.pushData(flags); + + return this.compile(); + } + + /** + * Create a nulldata/opreturn script. + * @param {Buffer} flags + * @returns {Script} + */ + + static fromNulldata(flags) { + return new this().fromNulldata(flags); + } + + /** + * Inject properties from a witness program. + * @private + * @param {Number} version + * @param {Buffer} data + */ + + fromProgram(version, data) { + assert((version & 0xff) === version && version >= 0 && version <= 16); + assert(Buffer.isBuffer(data) && data.length >= 2 && data.length <= 40); + + this.raw = Buffer.allocUnsafe(2 + data.length); + this.raw[0] = version === 0 ? 0 : version + 0x50; + this.raw[1] = data.length; + data.copy(this.raw, 2); + + data = this.raw.slice(2, 2 + data.length); + + this.code.length = 0; + this.code.push(Opcode.fromSmall(version)); + this.code.push(Opcode.fromPush(data)); + + return this; + } + + /** + * Create a witness program. + * @param {Number} version + * @param {Buffer} data + * @returns {Script} + */ + + static fromProgram(version, data) { + return new this().fromProgram(version, data); + } + + /** + * Inject properties from an address. + * @private + * @param {Address|Base58Address} address + */ + + fromAddress(address) { + if (typeof address === 'string') + address = Address.fromString(address); + + assert(address instanceof Address, 'Not an address.'); + + if (address.isPubkeyhash()) + return this.fromPubkeyhash(address.hash); + + if (address.isScripthash()) + return this.fromScripthash(address.hash); + + if (address.isProgram()) + return this.fromProgram(address.version, address.hash); + + throw new Error('Unknown address type.'); + } + + /** + * Create an output script from an address. + * @param {Address|Base58Address} address + * @returns {Script} + */ + + static fromAddress(address) { + return new this().fromAddress(address); + } + + /** + * Inject properties from a witness block commitment. + * @private + * @param {Buffer} hash + * @param {String|Buffer} flags + */ + + fromCommitment(hash, flags) { + const bw = bio.write(36); + + bw.writeU32BE(0xaa21a9ed); + bw.writeHash(hash); + + this.clear(); + this.pushOp(opcodes.OP_RETURN); + this.pushData(bw.render()); + + if (flags) + this.pushData(flags); + + return this.compile(); + } + + /** + * Create a witness block commitment. + * @param {Buffer} hash + * @param {String|Buffer} flags + * @returns {Script} + */ + + static fromCommitment(hash, flags) { + return new this().fromCommitment(hash, flags); + } + + /** + * Grab and deserialize the redeem script. + * @returns {Script|null} Redeem script. + */ + + getRedeem() { + let data = null; + + for (const op of this.code) { + if (op.value === -1) + return null; + + if (op.value > opcodes.OP_16) + return null; + + data = op.data; + } + + if (!data) + return null; + + return Script.fromRaw(data); + } + + /** + * Get the standard script type. + * @returns {ScriptType} + */ + + getType() { + if (this.isPubkey()) + return scriptTypes.PUBKEY; + + if (this.isPubkeyhash()) + return scriptTypes.PUBKEYHASH; + + if (this.isScripthash()) + return scriptTypes.SCRIPTHASH; + + if (this.isWitnessPubkeyhash()) + return scriptTypes.WITNESSPUBKEYHASH; + + if (this.isWitnessScripthash()) + return scriptTypes.WITNESSSCRIPTHASH; + + if (this.isWitnessMasthash()) + return scriptTypes.WITNESSMASTHASH; + + if (this.isMultisig()) + return scriptTypes.MULTISIG; + + if (this.isNulldata()) + return scriptTypes.NULLDATA; + + return scriptTypes.NONSTANDARD; + } + + /** + * Test whether a script is of an unknown/non-standard type. + * @returns {Boolean} + */ + + isUnknown() { + return this.getType() === scriptTypes.NONSTANDARD; + } + + /** + * Test whether the script is standard by policy standards. + * @returns {Boolean} + */ + + isStandard() { + const [m, n] = this.getMultisig(); + + if (m !== -1) { + if (n < 1 || n > 3) + return false; + + if (m < 1 || m > n) + return false; + + return true; + } + + if (this.isNulldata()) + return this.raw.length <= policy.MAX_OP_RETURN_BYTES; + + return this.getType() !== scriptTypes.NONSTANDARD; + } + + /** + * Calculate the size of the script + * excluding the varint size bytes. + * @returns {Number} + */ + + getSize() { + return this.raw.length; + } + + /** + * Calculate the size of the script + * including the varint size bytes. + * @returns {Number} + */ + + getVarSize() { + return encoding.sizeVarBytes(this.raw); + } + + /** + * "Guess" the address of the input script. + * This method is not 100% reliable. + * @returns {Address|null} + */ + + getInputAddress() { + return Address.fromInputScript(this); + } + + /** + * Get the address of the script if present. Note that + * pubkey and multisig scripts will be treated as though + * they are pubkeyhash and scripthashes respectively. + * @returns {Address|null} + */ + + getAddress() { + return Address.fromScript(this); + } + + /** + * Get the hash160 of the raw script. + * @param {String?} enc + * @returns {Hash} + */ + + hash160(enc) { + let hash = Hash160.digest(this.toRaw()); + if (enc === 'hex') + hash = hash.toString('hex'); + return hash; + } + + /** + * Get the sha256 of the raw script. + * @param {String?} enc + * @returns {Hash} + */ + + sha256(enc) { + let hash = Sha256.digest(this.toRaw()); + if (enc === 'hex') + hash = hash.toString('hex'); + return hash; + } + + /** + * Test whether the output script is pay-to-pubkey. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Boolean} + */ + + isPubkey(minimal) { + if (minimal) { + return this.raw.length >= 35 + && (this.raw[0] === 33 || this.raw[0] === 65) + && this.raw[0] + 2 === this.raw.length + && this.raw[this.raw.length - 1] === opcodes.OP_CHECKSIG; + } + + if (this.code.length !== 2) + return false; + + const size = this.getLength(0); + + return (size === 33 || size === 65) + && this.getOp(1) === opcodes.OP_CHECKSIG; + } + + /** + * Get P2PK key if present. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Buffer|null} + */ + + getPubkey(minimal) { + if (!this.isPubkey(minimal)) + return null; + + if (minimal) + return this.raw.slice(1, 1 + this.raw[0]); + + return this.getData(0); + } + + /** + * Test whether the output script is pay-to-pubkeyhash. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Boolean} + */ + + isPubkeyhash(minimal) { + if (minimal || this.raw.length === 25) { + return this.raw.length === 25 + && this.raw[0] === opcodes.OP_DUP + && this.raw[1] === opcodes.OP_HASH160 + && this.raw[2] === 0x14 + && this.raw[23] === opcodes.OP_EQUALVERIFY + && this.raw[24] === opcodes.OP_CHECKSIG; + } + + if (this.code.length !== 5) + return false; + + return this.getOp(0) === opcodes.OP_DUP + && this.getOp(1) === opcodes.OP_HASH160 + && this.getLength(2) === 20 + && this.getOp(3) === opcodes.OP_EQUALVERIFY + && this.getOp(4) === opcodes.OP_CHECKSIG; + } + + /** + * Get P2PKH hash if present. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Buffer|null} + */ + + getPubkeyhash(minimal) { + if (!this.isPubkeyhash(minimal)) + return null; + + if (minimal) + return this.raw.slice(3, 23); + + return this.getData(2); + } + + /** + * Test whether the output script is pay-to-multisig. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Boolean} + */ + + isMultisig(minimal) { + if (this.code.length < 4 || this.code.length > 19) + return false; + + if (this.getOp(-1) !== opcodes.OP_CHECKMULTISIG) + return false; + + const m = this.getSmall(0); + + if (m < 1) + return false; + + const n = this.getSmall(-2); + + if (n < 1 || m > n) + return false; + + if (this.code.length !== n + 3) + return false; + + for (let i = 1; i < n + 1; i++) { + const op = this.code[i]; + const size = op.toLength(); + + if (size !== 33 && size !== 65) + return false; + + if (minimal && !op.isMinimal()) + return false; + } + + return true; + } + + /** + * Get multisig m and n values if present. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Array} [m, n] + */ + + getMultisig(minimal) { + if (!this.isMultisig(minimal)) + return [-1, -1]; + + return [this.getSmall(0), this.getSmall(-2)]; + } + + /** + * Test whether the output script is pay-to-scripthash. Note that + * bitcoin itself requires scripthashes to be in strict minimaldata + * encoding. Using `OP_HASH160 OP_PUSHDATA1 [hash] OP_EQUAL` will + * _not_ be recognized as a scripthash. + * @returns {Boolean} + */ + + isScripthash() { + return this.raw.length === 23 + && this.raw[0] === opcodes.OP_HASH160 + && this.raw[1] === 0x14 + && this.raw[22] === opcodes.OP_EQUAL; + } + + /** + * Get P2SH hash if present. + * @returns {Buffer|null} + */ + + getScripthash() { + if (!this.isScripthash()) + return null; + + return this.getData(1); + } + + /** + * Test whether the output script is nulldata/opreturn. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Boolean} + */ + + isNulldata(minimal) { + if (this.code.length === 0) + return false; + + if (this.getOp(0) !== opcodes.OP_RETURN) + return false; + + if (this.code.length === 1) + return true; + + if (minimal) { + if (this.raw.length > policy.MAX_OP_RETURN_BYTES) + return false; + } + + for (let i = 1; i < this.code.length; i++) { + const op = this.code[i]; + + if (op.value === -1) + return false; + + if (op.value > opcodes.OP_16) + return false; + + if (minimal && !op.isMinimal()) + return false; + } + + return true; + } + + /** + * Get OP_RETURN data if present. + * @param {Boolean} [minimal=false] - Minimaldata only. + * @returns {Buffer|null} + */ + + getNulldata(minimal) { + if (!this.isNulldata(minimal)) + return null; + + for (let i = 1; i < this.code.length; i++) { + const op = this.code[i]; + const data = op.toPush(); + if (data) + return data; + } + + return EMPTY_BUFFER; + } + + /** + * Test whether the output script is a segregated witness + * commitment. + * @returns {Boolean} + */ + + isCommitment() { + return this.raw.length >= 38 + && this.raw[0] === opcodes.OP_RETURN + && this.raw[1] === 0x24 + && this.raw.readUInt32BE(2, true) === 0xaa21a9ed; + } + + /** + * Get the commitment hash if present. + * @returns {Buffer|null} + */ + + getCommitment() { + if (!this.isCommitment()) + return null; + + return this.raw.slice(6, 38); + } + + /** + * Test whether the output script is a witness program. + * Note that this will return true even for malformed + * witness v0 programs. + * @return {Boolean} + */ + + isProgram() { + if (this.raw.length < 4 || this.raw.length > 42) + return false; + + if (this.raw[0] !== opcodes.OP_0 + && (this.raw[0] < opcodes.OP_1 || this.raw[0] > opcodes.OP_16)) { + return false; + } + + if (this.raw[1] + 2 !== this.raw.length) + return false; + + return true; + } + + /** + * Get the witness program if present. + * @returns {Program|null} + */ + + getProgram() { + if (!this.isProgram()) + return null; + + const version = this.getSmall(0); + const data = this.getData(1); + + return new Program(version, data); + } + + /** + * Get the script to the equivalent witness + * program (mimics bitcoind's scriptForWitness). + * @returns {Script|null} + */ + + forWitness() { + if (this.isProgram()) + return this.clone(); + + const pk = this.getPubkey(); + if (pk) { + const hash = hash160.digest(pk); + return Script.fromProgram(0, hash); + } + + const pkh = this.getPubkeyhash(); + if (pkh) + return Script.fromProgram(0, pkh); + + return Script.fromProgram(0, this.sha256()); + } + + /** + * Test whether the output script is + * a pay-to-witness-pubkeyhash program. + * @returns {Boolean} + */ + + isWitnessPubkeyhash() { + return this.raw.length === 22 + && this.raw[0] === opcodes.OP_0 + && this.raw[1] === 0x14; + } + + /** + * Get P2WPKH hash if present. + * @returns {Buffer|null} + */ + + getWitnessPubkeyhash() { + if (!this.isWitnessPubkeyhash()) + return null; + + return this.getData(1); + } + + /** + * Test whether the output script is + * a pay-to-witness-scripthash program. + * @returns {Boolean} + */ + + isWitnessScripthash() { + return this.raw.length === 34 + && this.raw[0] === opcodes.OP_0 + && this.raw[1] === 0x20; + } + + /** + * Get P2WSH hash if present. + * @returns {Buffer|null} + */ + + getWitnessScripthash() { + if (!this.isWitnessScripthash()) + return null; + + return this.getData(1); + } + + /** + * Test whether the output script + * is a pay-to-mast program. + * @returns {Boolean} + */ + + isWitnessMasthash() { + return this.raw.length === 34 + && this.raw[0] === opcodes.OP_1 + && this.raw[1] === 0x20; + } + + /** + * Get P2WMH hash if present. + * @returns {Buffer|null} + */ + + getWitnessMasthash() { + if (!this.isWitnessMasthash()) + return null; + + return this.getData(1); + } + + /** + * Test whether the output script is unspendable. + * @returns {Boolean} + */ + + isUnspendable() { + if (this.raw.length > consensus.MAX_SCRIPT_SIZE) + return true; + + return this.raw.length > 0 && this.raw[0] === opcodes.OP_RETURN; + } + + /** + * "Guess" the type of the input script. + * This method is not 100% reliable. + * @returns {ScriptType} + */ + + getInputType() { + if (this.isPubkeyInput()) + return scriptTypes.PUBKEY; + + if (this.isPubkeyhashInput()) + return scriptTypes.PUBKEYHASH; + + if (this.isScripthashInput()) + return scriptTypes.SCRIPTHASH; + + if (this.isMultisigInput()) + return scriptTypes.MULTISIG; + + return scriptTypes.NONSTANDARD; + } + + /** + * "Guess" whether the input script is an unknown/non-standard type. + * This method is not 100% reliable. + * @returns {Boolean} + */ + + isUnknownInput() { + return this.getInputType() === scriptTypes.NONSTANDARD; + } + + /** + * "Guess" whether the input script is pay-to-pubkey. + * This method is not 100% reliable. + * @returns {Boolean} + */ + + isPubkeyInput() { + if (this.code.length !== 1) + return false; + + const size = this.getLength(0); + + return size >= 9 && size <= 73; + } + + /** + * Get P2PK signature if present. + * @returns {Buffer|null} + */ + + getPubkeyInput() { + if (!this.isPubkeyInput()) + return null; + + return this.getData(0); + } + + /** + * "Guess" whether the input script is pay-to-pubkeyhash. + * This method is not 100% reliable. + * @returns {Boolean} + */ + + isPubkeyhashInput() { + if (this.code.length !== 2) + return false; + + const sig = this.getLength(0); + const key = this.getLength(1); + + return sig >= 9 && sig <= 73 + && (key === 33 || key === 65); + } + + /** + * Get P2PKH signature and key if present. + * @returns {Array} [sig, key] + */ + + getPubkeyhashInput() { + if (!this.isPubkeyhashInput()) + return [null, null]; + + return [this.getData(0), this.getData(1)]; + } + + /** + * "Guess" whether the input script is pay-to-multisig. + * This method is not 100% reliable. + * @returns {Boolean} + */ + + isMultisigInput() { + if (this.code.length < 2) + return false; + + if (this.getOp(0) !== opcodes.OP_0) + return false; + + if (this.getOp(1) > opcodes.OP_PUSHDATA4) + return false; + + // We need to rule out scripthash + // because it may look like multisig. + if (this.isScripthashInput()) + return false; + + for (let i = 1; i < this.code.length; i++) { + const size = this.getLength(i); + if (size < 9 || size > 73) + return false; + } + + return true; + } + + /** + * Get multisig signatures if present. + * @returns {Buffer[]|null} + */ + + getMultisigInput() { + if (!this.isMultisigInput()) + return null; + + const sigs = []; + + for (let i = 1; i < this.code.length; i++) + sigs.push(this.getData(i)); + + return sigs; + } + + /** + * "Guess" whether the input script is pay-to-scripthash. + * This method is not 100% reliable. + * @returns {Boolean} + */ + + isScripthashInput() { + if (this.code.length < 2) + return false; + + // Grab the raw redeem script. + const raw = this.getData(-1); + + // Last data element should be an array + // for the redeem script. + if (!raw) + return false; + + // Testing for scripthash inputs requires + // some evil magic to work. We do it by + // ruling things _out_. This test will not + // be correct 100% of the time. We rule + // out that the last data element is: a + // null dummy, a valid signature, a valid + // key, and we ensure that it is at least + // a script that does not use undefined + // opcodes. + if (raw.length === 0) + return false; + + if (common.isSignatureEncoding(raw)) + return false; + + if (common.isKeyEncoding(raw)) + return false; + + const redeem = Script.fromRaw(raw); + + if (!redeem.isCode()) + return false; + + if (redeem.isUnspendable()) + return false; + + if (!this.isPushOnly()) + return false; + + return true; + } + + /** + * Get P2SH redeem script if present. + * @returns {Buffer|null} + */ + + getScripthashInput() { + if (!this.isScripthashInput()) + return null; + + return this.getData(-1); + } + + /** + * Get coinbase height. + * @returns {Number} `-1` if not present. + */ + + getCoinbaseHeight() { + return Script.getCoinbaseHeight(this.raw); + } + + /** + * Get coinbase height. + * @param {Buffer} raw - Raw script. + * @returns {Number} `-1` if not present. + */ + + static getCoinbaseHeight(raw) { + if (raw.length === 0) + return -1; + + if (raw[0] >= opcodes.OP_1 && raw[0] <= opcodes.OP_16) + return raw[0] - 0x50; + + if (raw[0] > 0x06) + return -1; + + const op = Opcode.fromRaw(raw); + const num = op.toNum(); + + if (!num) + return 1; + + if (num.isNeg()) + return -1; + + if (!op.equals(Opcode.fromNum(num))) + return -1; + + return num.toDouble(); + } + + /** + * Test the script against a bloom filter. + * @param {Bloom} filter + * @returns {Boolean} + */ + + test(filter) { + for (const op of this.code) { + if (op.value === -1) + break; + + if (!op.data || op.data.length === 0) + continue; + + if (filter.test(op.data)) + return true; + } + + return false; + } + + /** + * Test the script to see if it contains only push ops. + * Push ops are: OP_1NEGATE, OP_0-OP_16 and all PUSHDATAs. + * @returns {Boolean} + */ + + isPushOnly() { + for (const op of this.code) { + if (op.value === -1) + return false; + + if (op.value > opcodes.OP_16) + return false; + } + + return true; + } + + /** + * Count the sigops in the script. + * @param {Boolean} accurate - Whether to enable accurate counting. This will + * take into account the `n` value for OP_CHECKMULTISIG(VERIFY). + * @returns {Number} sigop count + */ + + getSigops(accurate) { + let total = 0; + let lastOp = -1; + + for (const op of this.code) { + if (op.value === -1) + break; + + switch (op.value) { + case opcodes.OP_CHECKSIG: + case opcodes.OP_CHECKSIGVERIFY: + total += 1; + break; + case opcodes.OP_CHECKMULTISIG: + case opcodes.OP_CHECKMULTISIGVERIFY: + if (accurate && lastOp >= opcodes.OP_1 && lastOp <= opcodes.OP_16) + total += lastOp - 0x50; + else + total += consensus.MAX_MULTISIG_PUBKEYS; + break; + } + + lastOp = op.value; + } + + return total; + } + + /** + * Count the sigops in the script, taking into account redeem scripts. + * @param {Script} input - Input script, needed for access to redeem script. + * @returns {Number} sigop count + */ + + getScripthashSigops(input) { + if (!this.isScripthash()) + return this.getSigops(true); + + const redeem = input.getRedeem(); + + if (!redeem) + return 0; + + return redeem.getSigops(true); + } + + /** + * Count the sigops in a script, taking into account witness programs. + * @param {Script} input + * @param {Witness} witness + * @returns {Number} sigop count + */ + + getWitnessSigops(input, witness) { + let program = this.getProgram(); + + if (!program) { + if (this.isScripthash()) { + const redeem = input.getRedeem(); + if (redeem) + program = redeem.getProgram(); + } + } + + if (!program) + return 0; + + if (program.version === 0) { + if (program.data.length === 20) + return 1; + + if (program.data.length === 32 && witness.items.length > 0) { + const redeem = witness.getRedeem(); + return redeem.getSigops(true); + } + } + + return 0; + } + + /* + * Mutation + */ + + get(index) { + if (index < 0) + index += this.code.length; + + if (index < 0 || index >= this.code.length) + return null; + + return this.code[index]; + } + + pop() { + const op = this.code.pop(); + return op || null; + } + + shift() { + const op = this.code.shift(); + return op || null; + } + + remove(index) { + if (index < 0) + index += this.code.length; + + if (index < 0 || index >= this.code.length) + return null; + + const items = this.code.splice(index, 1); + + if (items.length === 0) + return null; + + return items[0]; + } + + set(index, op) { + if (index < 0) + index += this.code.length; + + assert(Opcode.isOpcode(op)); + assert(index >= 0 && index <= this.code.length); + + this.code[index] = op; + + return this; + } + + push(op) { + assert(Opcode.isOpcode(op)); + this.code.push(op); + return this; + } + + unshift(op) { + assert(Opcode.isOpcode(op)); + this.code.unshift(op); + return this; + } + + insert(index, op) { + if (index < 0) + index += this.code.length; + + assert(Opcode.isOpcode(op)); + assert(index >= 0 && index <= this.code.length); + + this.code.splice(index, 0, op); + + return this; + } + + /* + * Op + */ + + getOp(index) { + const op = this.get(index); + return op ? op.value : -1; + } + + popOp() { + const op = this.pop(); + return op ? op.value : -1; + } + + shiftOp() { + const op = this.shift(); + return op ? op.value : -1; + } + + removeOp(index) { + const op = this.remove(index); + return op ? op.value : -1; + } + + setOp(index, value) { + return this.set(index, Opcode.fromOp(value)); + } + + pushOp(value) { + return this.push(Opcode.fromOp(value)); + } + + unshiftOp(value) { + return this.unshift(Opcode.fromOp(value)); + } + + insertOp(index, value) { + return this.insert(index, Opcode.fromOp(value)); + } + + /* + * Data + */ + + getData(index) { + const op = this.get(index); + return op ? op.data : null; + } + + popData() { + const op = this.pop(); + return op ? op.data : null; + } + + shiftData() { + const op = this.shift(); + return op ? op.data : null; + } + + removeData(index) { + const op = this.remove(index); + return op ? op.data : null; + } + + setData(index, data) { + return this.set(index, Opcode.fromData(data)); + } + + pushData(data) { + return this.push(Opcode.fromData(data)); + } + + unshiftData(data) { + return this.unshift(Opcode.fromData(data)); + } + + insertData(index, data) { + return this.insert(index, Opcode.fromData(data)); + } + + /* + * Length + */ + + getLength(index) { + const op = this.get(index); + return op ? op.toLength() : -1; + } + + /* + * Push + */ + + getPush(index) { + const op = this.get(index); + return op ? op.toPush() : null; + } + + popPush() { + const op = this.pop(); + return op ? op.toPush() : null; + } + + shiftPush() { + const op = this.shift(); + return op ? op.toPush() : null; + } + + removePush(index) { + const op = this.remove(index); + return op ? op.toPush() : null; + } + + setPush(index, data) { + return this.set(index, Opcode.fromPush(data)); + } + + pushPush(data) { + return this.push(Opcode.fromPush(data)); + } + + unshiftPush(data) { + return this.unshift(Opcode.fromPush(data)); + } + + insertPush(index, data) { + return this.insert(index, Opcode.fromPush(data)); + } + + /* + * String + */ + + getString(index, enc) { + const op = this.get(index); + return op ? op.toString(enc) : null; + } + + popString(enc) { + const op = this.pop(); + return op ? op.toString(enc) : null; + } + + shiftString(enc) { + const op = this.shift(); + return op ? op.toString(enc) : null; + } + + removeString(index, enc) { + const op = this.remove(index); + return op ? op.toString(enc) : null; + } + + setString(index, str, enc) { + return this.set(index, Opcode.fromString(str, enc)); + } + + pushString(str, enc) { + return this.push(Opcode.fromString(str, enc)); + } + + unshiftString(str, enc) { + return this.unshift(Opcode.fromString(str, enc)); + } + + insertString(index, str, enc) { + return this.insert(index, Opcode.fromString(str, enc)); + } + + /* + * Small + */ + + getSmall(index) { + const op = this.get(index); + return op ? op.toSmall() : -1; + } + + popSmall() { + const op = this.pop(); + return op ? op.toSmall() : -1; + } + + shiftSmall() { + const op = this.shift(); + return op ? op.toSmall() : -1; + } + + removeSmall(index) { + const op = this.remove(index); + return op ? op.toSmall() : -1; + } + + setSmall(index, num) { + return this.set(index, Opcode.fromSmall(num)); + } + + pushSmall(num) { + return this.push(Opcode.fromSmall(num)); + } + + unshiftSmall(num) { + return this.unshift(Opcode.fromSmall(num)); + } + + insertSmall(index, num) { + return this.insert(index, Opcode.fromSmall(num)); + } + + /* + * Num + */ + + getNum(index, minimal, limit) { + const op = this.get(index); + return op ? op.toNum(minimal, limit) : null; + } + + popNum(minimal, limit) { + const op = this.pop(); + return op ? op.toNum(minimal, limit) : null; + } + + shiftNum(minimal, limit) { + const op = this.shift(); + return op ? op.toNum(minimal, limit) : null; + } + + removeNum(index, minimal, limit) { + const op = this.remove(index); + return op ? op.toNum(minimal, limit) : null; + } + + setNum(index, num) { + return this.set(index, Opcode.fromNum(num)); + } + + pushNum(num) { + return this.push(Opcode.fromNum(num)); + } + + unshiftNum(num) { + return this.unshift(Opcode.fromNum(num)); + } + + insertNum(index, num) { + return this.insert(index, Opcode.fromNum(num)); + } + + /* + * Int + */ + + getInt(index, minimal, limit) { + const op = this.get(index); + return op ? op.toInt(minimal, limit) : -1; + } + + popInt(minimal, limit) { + const op = this.pop(); + return op ? op.toInt(minimal, limit) : -1; + } + + shiftInt(minimal, limit) { + const op = this.shift(); + return op ? op.toInt(minimal, limit) : -1; + } + + removeInt(index, minimal, limit) { + const op = this.remove(index); + return op ? op.toInt(minimal, limit) : -1; + } + + setInt(index, num) { + return this.set(index, Opcode.fromInt(num)); + } + + pushInt(num) { + return this.push(Opcode.fromInt(num)); + } + + unshiftInt(num) { + return this.unshift(Opcode.fromInt(num)); + } + + insertInt(index, num) { + return this.insert(index, Opcode.fromInt(num)); + } + + /* + * Bool + */ + + getBool(index) { + const op = this.get(index); + return op ? op.toBool() : false; + } + + popBool() { + const op = this.pop(); + return op ? op.toBool() : false; + } + + shiftBool() { + const op = this.shift(); + return op ? op.toBool() : false; + } + + removeBool(index) { + const op = this.remove(index); + return op ? op.toBool() : false; + } + + setBool(index, value) { + return this.set(index, Opcode.fromBool(value)); + } + + pushBool(value) { + return this.push(Opcode.fromBool(value)); + } + + unshiftBool(value) { + return this.unshift(Opcode.fromBool(value)); + } + + insertBool(index, value) { + return this.insert(index, Opcode.fromBool(value)); + } + + /* + * Symbol + */ + + getSym(index) { + const op = this.get(index); + return op ? op.toSymbol() : null; + } + + popSym() { + const op = this.pop(); + return op ? op.toSymbol() : null; + } + + shiftSym() { + const op = this.shift(); + return op ? op.toSymbol() : null; + } + + removeSym(index) { + const op = this.remove(index); + return op ? op.toSymbol() : null; + } + + setSym(index, symbol) { + return this.set(index, Opcode.fromSymbol(symbol)); + } + + pushSym(symbol) { + return this.push(Opcode.fromSymbol(symbol)); + } + + unshiftSym(symbol) { + return this.unshift(Opcode.fromSymbol(symbol)); + } + + insertSym(index, symbol) { + return this.insert(index, Opcode.fromSymbol(symbol)); + } + + /** + * Inject properties from bitcoind test string. + * @private + * @param {String} items - Script string. + * @throws Parse error. + */ + + fromString(code) { + assert(typeof code === 'string'); + + code = code.trim(); + + if (code.length === 0) + return this; + + const items = code.split(/\s+/); + const bw = bio.write(); + + for (const item of items) { + let symbol = item; + + if (symbol.charCodeAt(0) & 32) + symbol = symbol.toUpperCase(); + + if (!/^OP_/.test(symbol)) + symbol = `OP_${symbol}`; + + const value = opcodes[symbol]; + + if (value == null) { + if (item[0] === '\'') { + assert(item[item.length - 1] === '\'', 'Invalid string.'); + const str = item.slice(1, -1); + const op = Opcode.fromString(str); + bw.writeBytes(op.toRaw()); + continue; + } + + if (/^-?\d+$/.test(item)) { + const num = ScriptNum.fromString(item, 10); + const op = Opcode.fromNum(num); + bw.writeBytes(op.toRaw()); + continue; + } + + assert(item.indexOf('0x') === 0, 'Unknown opcode.'); + + const hex = item.substring(2); + const data = Buffer.from(hex, 'hex'); + + assert(data.length === hex.length / 2, 'Invalid hex string.'); + + bw.writeBytes(data); + + continue; + } + + bw.writeU8(value); + } + + return this.fromRaw(bw.render()); + } + + /** + * Parse a bitcoind test script + * string into a script object. + * @param {String} items - Script string. + * @returns {Script} + * @throws Parse error. + */ + + static fromString(code) { + return new this().fromString(code); + } + + /** + * Verify an input and output script, and a witness if present. + * @param {Script} input + * @param {Witness} witness + * @param {Script} output + * @param {TX} tx + * @param {Number} index + * @param {Amount} value + * @param {VerifyFlags} flags + * @throws {ScriptError} + */ + + static verify(input, witness, output, tx, index, value, flags) { + if (flags == null) + flags = Script.flags.STANDARD_VERIFY_FLAGS; + + if (flags & Script.flags.VERIFY_SIGPUSHONLY) { + if (!input.isPushOnly()) + throw new ScriptError('SIG_PUSHONLY'); + } + + // Setup a stack. + let stack = new Stack(); + + // Execute the input script + input.execute(stack, flags, tx, index, value, 0); + + // Copy the stack for P2SH + let copy; + if (flags & Script.flags.VERIFY_P2SH) + copy = stack.clone(); + + // Execute the previous output script. + output.execute(stack, flags, tx, index, value, 0); + + // Verify the stack values. + if (stack.length === 0 || !stack.getBool(-1)) + throw new ScriptError('EVAL_FALSE'); + + let hadWitness = false; + + if ((flags & Script.flags.VERIFY_WITNESS) && output.isProgram()) { + hadWitness = true; + + // Input script must be empty. + if (input.raw.length !== 0) + throw new ScriptError('WITNESS_MALLEATED'); + + // Verify the program in the output script. + Script.verifyProgram(witness, output, flags, tx, index, value); + + // Force a cleanstack + stack.length = 1; + } + + // If the script is P2SH, execute the real output script + if ((flags & Script.flags.VERIFY_P2SH) && output.isScripthash()) { + // P2SH can only have push ops in the scriptSig + if (!input.isPushOnly()) + throw new ScriptError('SIG_PUSHONLY'); + + // Reset the stack + stack = copy; + + // Stack should not be empty at this point + if (stack.length === 0) + throw new ScriptError('EVAL_FALSE'); + + // Grab the real redeem script + const raw = stack.pop(); + const redeem = Script.fromRaw(raw); + + // Execute the redeem script. + redeem.execute(stack, flags, tx, index, value, 0); + + // Verify the the stack values. + if (stack.length === 0 || !stack.getBool(-1)) + throw new ScriptError('EVAL_FALSE'); + + if ((flags & Script.flags.VERIFY_WITNESS) && redeem.isProgram()) { + hadWitness = true; + + // Input script must be exactly one push of the redeem script. + if (!input.raw.equals(Opcode.fromPush(raw).toRaw())) + throw new ScriptError('WITNESS_MALLEATED_P2SH'); + + // Verify the program in the redeem script. + Script.verifyProgram(witness, redeem, flags, tx, index, value); + + // Force a cleanstack. + stack.length = 1; + } + } + + // Ensure there is nothing left on the stack. + if (flags & Script.flags.VERIFY_CLEANSTACK) { + assert((flags & Script.flags.VERIFY_P2SH) !== 0); + if (stack.length !== 1) + throw new ScriptError('CLEANSTACK'); + } + + // If we had a witness but no witness program, fail. + if (flags & Script.flags.VERIFY_WITNESS) { + assert((flags & Script.flags.VERIFY_P2SH) !== 0); + if (!hadWitness && witness.items.length > 0) + throw new ScriptError('WITNESS_UNEXPECTED'); + } + } + + /** + * Verify a witness program. This runs after regular script + * execution if a witness program is present. It will convert + * the witness to a stack and execute the program. + * @param {Witness} witness + * @param {Script} output + * @param {VerifyFlags} flags + * @param {TX} tx + * @param {Number} index + * @param {Amount} value + * @throws {ScriptError} + */ + + static verifyProgram(witness, output, flags, tx, index, value) { + const program = output.getProgram(); + + assert(program, 'verifyProgram called on non-witness-program.'); + assert((flags & Script.flags.VERIFY_WITNESS) !== 0); + + const stack = witness.toStack(); + let redeem; + + if (program.version === 0) { + if (program.data.length === 32) { + if (stack.length === 0) + throw new ScriptError('WITNESS_PROGRAM_WITNESS_EMPTY'); + + const witnessScript = stack.pop(); + + if (!sha256.digest(witnessScript).equals(program.data)) + throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); + + redeem = Script.fromRaw(witnessScript); + } else if (program.data.length === 20) { + if (stack.length !== 2) + throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); + + redeem = Script.fromPubkeyhash(program.data); + } else { + // Failure on version=0 (bad program data length). + throw new ScriptError('WITNESS_PROGRAM_WRONG_LENGTH'); + } + } else if ((flags & Script.flags.VERIFY_MAST) && program.version === 1) { + Script.verifyMast(program, stack, output, flags, tx, index); + return; + } else { + // Anyone can spend (we can return true here + // if we want to always relay these transactions). + // Otherwise, if we want to act like an "old" + // implementation and only accept them in blocks, + // we can use the regular output script which will + // succeed in a block, but fail in the mempool + // due to VERIFY_CLEANSTACK. + if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) + throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); + return; + } + + // Witnesses still have push limits. + for (let j = 0; j < stack.length; j++) { + if (stack.get(j).length > consensus.MAX_SCRIPT_PUSH) + throw new ScriptError('PUSH_SIZE'); + } + + // Verify the redeem script. + redeem.execute(stack, flags, tx, index, value, 1); + + // Verify the stack values. + if (stack.length !== 1 || !stack.getBool(-1)) + throw new ScriptError('EVAL_FALSE'); + } + + /** + * Verify a MAST witness program. + * @param {Program} program + * @param {Stack} stack + * @param {Script} output + * @param {VerifyFlags} flags + * @param {TX} tx + * @param {Number} index + * @param {Amount} value + * @throws {ScriptError} + */ + + static verifyMast(program, stack, output, flags, tx, index, value) { + assert(program.version === 1); + assert((flags & Script.flags.VERIFY_MAST) !== 0); + + if (stack.length < 4) + throw new ScriptError('INVALID_MAST_STACK'); + + const metadata = stack.get(-1); + if (metadata.length < 1 || metadata.length > 5) + throw new ScriptError('INVALID_MAST_STACK'); + + const subscripts = metadata[0]; + if (subscripts === 0 || stack.length < subscripts + 3) + throw new ScriptError('INVALID_MAST_STACK'); + + let ops = subscripts; + let scriptRoot = bio.write(); + scriptRoot.writeU8(subscripts); + + if (metadata[metadata.length - 1] === 0x00) + throw new ScriptError('INVALID_MAST_STACK'); + + let version = 0; + + for (let j = 1; j < metadata.length; j++) + version |= metadata[j] << 8 * (j - 1); + + if (version < 0) + version += 0x100000000; + + if (version > 0) { + if (flags & Script.flags.DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) + throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); + } + + let mastRoot = bio.write(); + mastRoot.writeU32(version); + + const pathdata = stack.get(-2); + + if (pathdata.length & 0x1f) + throw new ScriptError('INVALID_MAST_STACK'); + + const depth = pathdata.length >>> 5; + + if (depth > 32) + throw new ScriptError('INVALID_MAST_STACK'); + + ops += depth; + if (version === 0) { + if (ops > consensus.MAX_SCRIPT_OPS) + throw new ScriptError('OP_COUNT'); + } + + const path = []; + + for (let j = 0; j < depth; j++) + path.push(pathdata.slice(j * 32, j * 32 + 32)); + + const posdata = stack.get(-3); + + if (posdata.length > 4) + throw new ScriptError('INVALID_MAST_STACK'); + + let pos = 0; + if (posdata.length > 0) { + if (posdata[posdata.length - 1] === 0x00) + throw new ScriptError('INVALID_MAST_STACK'); + + for (let j = 0; j < posdata.length; j++) + pos |= posdata[j] << 8 * j; + + if (pos < 0) + pos += 0x100000000; + } + + if (depth < 32) { + if (pos >= ((1 << depth) >>> 0)) + throw new ScriptError('INVALID_MAST_STACK'); + } + + let scripts = bio.write(); + scripts.writeBytes(output.raw); + + for (let j = 0; j < subscripts; j++) { + const script = stack.get(-(4 + j)); + if (version === 0) { + if ((scripts.offset + script.length) > consensus.MAX_SCRIPT_SIZE) + throw new ScriptError('SCRIPT_SIZE'); + } + scriptRoot.writeBytes(hash256.digest(script)); + scripts.writeBytes(script); + } + + scriptRoot = hash256.digest(scriptRoot.render()); + scriptRoot = merkle.verifyBranch(scriptRoot, path, pos); + + mastRoot.writeBytes(scriptRoot); + mastRoot = hash256.digest(mastRoot.render()); + + if (!mastRoot.equals(program.data)) + throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); + + if (version === 0) { + stack.length -= 3 + subscripts; + + for (let j = 0; j < stack.length; j++) { + if (stack.get(j).length > consensus.MAX_SCRIPT_PUSH) + throw new ScriptError('PUSH_SIZE'); + } + + scripts = scripts.render(); + output = Script.fromRaw(scripts); + output.execute(stack, flags, tx, index, value, 1); + + if (stack.length !== 0) + throw new ScriptError('EVAL_FALSE'); + } + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + return this.fromRaw(br.readVarBytes()); + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} + */ + + fromRaw(data) { + const br = bio.read(data, true); + + this.raw = data; + + while (br.left()) + this.code.push(Opcode.fromReader(br)); + + return this; + } + + /** + * Create a script from buffer reader. + * @param {BufferReader} br + * @param {String?} enc - Either `"hex"` or `null`. + * @returns {Script} + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Create a script from a serialized buffer. + * @param {Buffer|String} data - Serialized script. + * @param {String?} enc - Either `"hex"` or `null`. + * @returns {Script} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } + + /** + * Test whether an object a Script. + * @param {Object} obj + * @returns {Boolean} + */ + + static isScript(obj) { + return obj instanceof Script; + } } /** @@ -110,3414 +3525,6 @@ Script.types = common.types; Script.typesByVal = common.typesByVal; -/* - * Expose length setter and getter. - */ - -Object.defineProperty(Script.prototype, 'length', { - get() { - return this.code.length; - }, - set(length) { - this.code.length = length; - return this.code.length; - } -}); - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -Script.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Script data is required.'); - - if (Buffer.isBuffer(options)) - return this.fromRaw(options); - - if (Array.isArray(options)) - return this.fromArray(options); - - if (options.raw) { - if (!options.code) - return this.fromRaw(options.raw); - assert(Buffer.isBuffer(options.raw), 'Raw must be a Buffer.'); - this.raw = options.raw; - } - - if (options.code) { - if (!options.raw) - return this.fromArray(options.code); - assert(Array.isArray(options.code), 'Code must be an array.'); - this.code = options.code; - } - - return this; -}; - -/** - * Insantiate script from options object. - * @param {Object} options - * @returns {Script} - */ - -Script.fromOptions = function fromOptions(options) { - return new Script().fromOptions(options); -}; - -/** - * Instantiate a value-only iterator. - * @returns {ScriptIterator} - */ - -Script.prototype.values = function values() { - return this.code.values(); -}; - -/** - * Instantiate a key and value iterator. - * @returns {ScriptIterator} - */ - -Script.prototype.entries = function entries() { - return this.code.entries(); -}; - -/** - * Instantiate a value-only iterator. - * @returns {ScriptIterator} - */ - -Script.prototype[Symbol.iterator] = function() { - return this.code[Symbol.iterator](); -}; - -/** - * Convert the script to an array of - * Buffers (pushdatas) and Numbers - * (opcodes). - * @returns {Array} - */ - -Script.prototype.toArray = function toArray() { - return this.code.slice(); -}; - -/** - * Inject properties from an array of - * of buffers and numbers. - * @private - * @param {Array} code - * @returns {Script} - */ - -Script.prototype.fromArray = function fromArray(code) { - assert(Array.isArray(code)); - - this.clear(); - - for (const op of code) - this.push(op); - - return this.compile(); -}; - -/** - * Instantiate script from an array - * of buffers and numbers. - * @param {Array} code - * @returns {Script} - */ - -Script.fromArray = function fromArray(code) { - return new Script().fromArray(code); -}; - -/** - * Convert script to stack items. - * @returns {Buffer[]} - */ - -Script.prototype.toItems = function toItems() { - const items = []; - - for (const op of this.code) { - const data = op.toPush(); - - if (!data) - throw new Error('Non-push opcode in script.'); - - items.push(data); - } - - return items; -}; - -/** - * Inject data from stack items. - * @private - * @param {Buffer[]} items - * @returns {Script} - */ - -Script.prototype.fromItems = function fromItems(items) { - assert(Array.isArray(items)); - - this.clear(); - - for (const item of items) - this.pushData(item); - - return this.compile(); -}; - -/** - * Instantiate script from stack items. - * @param {Buffer[]} items - * @returns {Script} - */ - -Script.fromItems = function fromItems(items) { - return new Script().fromItems(items); -}; - -/** - * Convert script to stack. - * @returns {Stack} - */ - -Script.prototype.toStack = function toStack() { - return new Stack(this.toItems()); -}; - -/** - * Inject data from stack. - * @private - * @param {Stack} stack - * @returns {Script} - */ - -Script.prototype.fromStack = function fromStack(stack) { - return this.fromItems(stack.items); -}; - -/** - * Instantiate script from stack. - * @param {Stack} stack - * @returns {Script} - */ - -Script.fromStack = function fromStack(stack) { - return new Script().fromStack(stack); -}; - -/** - * Clone the script. - * @returns {Script} Cloned script. - */ - -Script.prototype.clone = function clone() { - return new Script().inject(this); -}; - -/** - * Inject properties from script. - * Used for cloning. - * @private - * @param {Script} script - * @returns {Script} - */ - -Script.prototype.inject = function inject(script) { - this.raw = script.raw; - this.code = script.code.slice(); - return this; -}; - -/** - * Test equality against script. - * @param {Script} script - * @returns {Boolean} - */ - -Script.prototype.equals = function equals(script) { - assert(Script.isScript(script)); - return this.raw.equals(script.raw); -}; - -/** - * Compare against another script. - * @param {Script} script - * @returns {Number} - */ - -Script.prototype.compare = function compare(script) { - assert(Script.isScript(script)); - return this.raw.compare(script.raw); -}; - -/** - * Clear the script. - * @returns {Script} - */ - -Script.prototype.clear = function clear() { - this.raw = EMPTY_BUFFER; - this.code.length = 0; - return this; -}; - -/** - * Inspect the script. - * @returns {String} Human-readable script code. - */ - -Script.prototype.inspect = function inspect() { - return ``; -}; - -/** - * Convert the script to a bitcoind test string. - * @returns {String} Human-readable script code. - */ - -Script.prototype.toString = function toString() { - const out = []; - - for (const op of this.code) - out.push(op.toFormat()); - - return out.join(' '); -}; - -/** - * Format the script as bitcoind asm. - * @param {Boolean?} decode - Attempt to decode hash types. - * @returns {String} Human-readable script. - */ - -Script.prototype.toASM = function toASM(decode) { - if (this.isNulldata()) - decode = false; - - const out = []; - - for (const op of this.code) - out.push(op.toASM(decode)); - - return out.join(' '); -}; - -/** - * Re-encode the script internally. Useful if you - * changed something manually in the `code` array. - * @returns {Script} - */ - -Script.prototype.compile = function compile() { - if (this.code.length === 0) - return this.clear(); - - let size = 0; - - for (const op of this.code) - size += op.getSize(); - - const bw = bio.write(size); - - for (const op of this.code) - op.toWriter(bw); - - this.raw = bw.render(); - - return this; -}; - -/** - * Write the script to a buffer writer. - * @param {BufferWriter} bw - */ - -Script.prototype.toWriter = function toWriter(bw) { - bw.writeVarBytes(this.raw); - return bw; -}; - -/** - * Encode the script to a Buffer. See {@link Script#encode}. - * @param {String} enc - Encoding, either `'hex'` or `null`. - * @returns {Buffer|String} Serialized script. - */ - -Script.prototype.toRaw = function toRaw() { - return this.raw; -}; - -/** - * Convert script to a hex string. - * @returns {String} - */ - -Script.prototype.toJSON = function toJSON() { - return this.toRaw().toString('hex'); -}; - -/** - * Inject properties from json object. - * @private - * @param {String} json - */ - -Script.prototype.fromJSON = function fromJSON(json) { - assert(typeof json === 'string', 'Code must be a string.'); - return this.fromRaw(Buffer.from(json, 'hex')); -}; - -/** - * Instantiate script from a hex string. - * @params {String} json - * @returns {Script} - */ - -Script.fromJSON = function fromJSON(json) { - return new Script().fromJSON(json); -}; - -/** - * Get the script's "subscript" starting at a separator. - * @param {Number} index - The last separator to sign/verify beyond. - * @returns {Script} Subscript. - */ - -Script.prototype.getSubscript = function getSubscript(index) { - if (index === 0) - return this.clone(); - - const script = new Script(); - - for (let i = index; i < this.code.length; i++) { - const op = this.code[i]; - - if (op.value === -1) - break; - - script.code.push(op); - } - - return script.compile(); -}; - -/** - * Get the script's "subscript" starting at a separator. - * Remove all OP_CODESEPARATORs if present. This bizarre - * behavior is necessary for signing and verification when - * code separators are present. - * @returns {Script} Subscript. - */ - -Script.prototype.removeSeparators = function removeSeparators() { - let found = false; - - // Optimizing for the common case: - // Check for any separators first. - for (const op of this.code) { - if (op.value === -1) - break; - - if (op.value === opcodes.OP_CODESEPARATOR) { - found = true; - break; - } - } - - if (!found) - return this; - - // Uncommon case: someone actually - // has a code separator. Go through - // and remove them all. - const script = new Script(); - - for (const op of this.code) { - if (op.value === -1) - break; - - if (op.value !== opcodes.OP_CODESEPARATOR) - script.code.push(op); - } - - return script.compile(); -}; - -/** - * Execute and interpret the script. - * @param {Stack} stack - Script execution stack. - * @param {Number?} flags - Script standard flags. - * @param {TX?} tx - Transaction being verified. - * @param {Number?} index - Index of input being verified. - * @param {Amount?} value - Previous output value. - * @param {Number?} version - Signature hash version (0=legacy, 1=segwit). - * @throws {ScriptError} Will be thrown on VERIFY failures, among other things. - */ - -Script.prototype.execute = function execute(stack, flags, tx, index, value, version) { - if (flags == null) - flags = Script.flags.STANDARD_VERIFY_FLAGS; - - if (version == null) - version = 0; - - if (this.raw.length > consensus.MAX_SCRIPT_SIZE) - throw new ScriptError('SCRIPT_SIZE'); - - const state = []; - const alt = []; - - let lastSep = 0; - let opCount = 0; - let negate = 0; - let minimal = false; - - if (flags & Script.flags.VERIFY_MINIMALDATA) - minimal = true; - - for (let ip = 0; ip < this.code.length; ip++) { - const op = this.code[ip]; - - if (op.value === -1) - throw new ScriptError('BAD_OPCODE', op, ip); - - if (op.data && op.data.length > consensus.MAX_SCRIPT_PUSH) - throw new ScriptError('PUSH_SIZE', op, ip); - - if (op.value > opcodes.OP_16 && ++opCount > consensus.MAX_SCRIPT_OPS) - throw new ScriptError('OP_COUNT', op, ip); - - if (op.isDisabled()) - throw new ScriptError('DISABLED_OPCODE', op, ip); - - if (negate && !op.isBranch()) { - if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) - throw new ScriptError('STACK_SIZE', op, ip); - continue; - } - - if (op.data) { - if (minimal && !op.isMinimal()) - throw new ScriptError('MINIMALDATA', op, ip); - - stack.push(op.data); - - if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) - throw new ScriptError('STACK_SIZE', op, ip); - - continue; - } - - switch (op.value) { - case opcodes.OP_0: { - stack.pushInt(0); - break; - } - case opcodes.OP_1NEGATE: { - stack.pushInt(-1); - break; - } - case opcodes.OP_1: - case opcodes.OP_2: - case opcodes.OP_3: - case opcodes.OP_4: - case opcodes.OP_5: - case opcodes.OP_6: - case opcodes.OP_7: - case opcodes.OP_8: - case opcodes.OP_9: - case opcodes.OP_10: - case opcodes.OP_11: - case opcodes.OP_12: - case opcodes.OP_13: - case opcodes.OP_14: - case opcodes.OP_15: - case opcodes.OP_16: { - stack.pushInt(op.value - 0x50); - break; - } - case opcodes.OP_NOP: { - break; - } - case opcodes.OP_CHECKLOCKTIMEVERIFY: { - // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 - if (!(flags & Script.flags.VERIFY_CHECKLOCKTIMEVERIFY)) { - if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); - break; - } - - if (!tx) - throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); - - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const num = stack.getNum(-1, minimal, 5); - - if (num.isNeg()) - throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); - - const locktime = num.toDouble(); - - if (!tx.verifyLocktime(index, locktime)) - throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); - - break; - } - case opcodes.OP_CHECKSEQUENCEVERIFY: { - // OP_CHECKSEQUENCEVERIFY = OP_NOP3 - if (!(flags & Script.flags.VERIFY_CHECKSEQUENCEVERIFY)) { - if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); - break; - } - - if (!tx) - throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); - - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const num = stack.getNum(-1, minimal, 5); - - if (num.isNeg()) - throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); - - const locktime = num.toDouble(); - - if (!tx.verifySequence(index, locktime)) - throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); - - break; - } - case opcodes.OP_NOP1: - case opcodes.OP_NOP4: - case opcodes.OP_NOP5: - case opcodes.OP_NOP6: - case opcodes.OP_NOP7: - case opcodes.OP_NOP8: - case opcodes.OP_NOP9: - case opcodes.OP_NOP10: { - if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); - break; - } - case opcodes.OP_IF: - case opcodes.OP_NOTIF: { - let val = false; - - if (!negate) { - if (stack.length < 1) - throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); - - if (version === 1 && (flags & Script.flags.VERIFY_MINIMALIF)) { - const item = stack.get(-1); - - if (item.length > 1) - throw new ScriptError('MINIMALIF'); - - if (item.length === 1 && item[0] !== 1) - throw new ScriptError('MINIMALIF'); - } - - val = stack.getBool(-1); - - if (op.value === opcodes.OP_NOTIF) - val = !val; - - stack.pop(); - } - - state.push(val); - - if (!val) - negate += 1; - - break; - } - case opcodes.OP_ELSE: { - if (state.length === 0) - throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); - - state[state.length - 1] = !state[state.length - 1]; - - if (!state[state.length - 1]) - negate += 1; - else - negate -= 1; - - break; - } - case opcodes.OP_ENDIF: { - if (state.length === 0) - throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); - - if (!state.pop()) - negate -= 1; - - break; - } - case opcodes.OP_VERIFY: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - if (!stack.getBool(-1)) - throw new ScriptError('VERIFY', op, ip); - - stack.pop(); - - break; - } - case opcodes.OP_RETURN: { - throw new ScriptError('OP_RETURN', op, ip); - } - case opcodes.OP_TOALTSTACK: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - alt.push(stack.pop()); - break; - } - case opcodes.OP_FROMALTSTACK: { - if (alt.length === 0) - throw new ScriptError('INVALID_ALTSTACK_OPERATION', op, ip); - - stack.push(alt.pop()); - break; - } - case opcodes.OP_2DROP: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.pop(); - stack.pop(); - break; - } - case opcodes.OP_2DUP: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const v1 = stack.get(-2); - const v2 = stack.get(-1); - - stack.push(v1); - stack.push(v2); - break; - } - case opcodes.OP_3DUP: { - if (stack.length < 3) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const v1 = stack.get(-3); - const v2 = stack.get(-2); - const v3 = stack.get(-1); - - stack.push(v1); - stack.push(v2); - stack.push(v3); - break; - } - case opcodes.OP_2OVER: { - if (stack.length < 4) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const v1 = stack.get(-4); - const v2 = stack.get(-3); - - stack.push(v1); - stack.push(v2); - break; - } - case opcodes.OP_2ROT: { - if (stack.length < 6) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const v1 = stack.get(-6); - const v2 = stack.get(-5); - - stack.erase(-6, -4); - stack.push(v1); - stack.push(v2); - break; - } - case opcodes.OP_2SWAP: { - if (stack.length < 4) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.swap(-4, -2); - stack.swap(-3, -1); - break; - } - case opcodes.OP_IFDUP: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - if (stack.getBool(-1)) { - const val = stack.get(-1); - stack.push(val); - } - - break; - } - case opcodes.OP_DEPTH: { - stack.pushInt(stack.length); - break; - } - case opcodes.OP_DROP: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.pop(); - break; - } - case opcodes.OP_DUP: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(stack.get(-1)); - break; - } - case opcodes.OP_NIP: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.remove(-2); - break; - } - case opcodes.OP_OVER: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(stack.get(-2)); - break; - } - case opcodes.OP_PICK: - case opcodes.OP_ROLL: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const num = stack.getInt(-1, minimal, 4); - stack.pop(); - - if (num < 0 || num >= stack.length) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const val = stack.get(-num - 1); - - if (op.value === opcodes.OP_ROLL) - stack.remove(-num - 1); - - stack.push(val); - break; - } - case opcodes.OP_ROT: { - if (stack.length < 3) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.swap(-3, -2); - stack.swap(-2, -1); - break; - } - case opcodes.OP_SWAP: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.swap(-2, -1); - break; - } - case opcodes.OP_TUCK: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.insert(-2, stack.get(-1)); - break; - } - case opcodes.OP_SIZE: { - if (stack.length < 1) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.pushInt(stack.get(-1).length); - break; - } - case opcodes.OP_EQUAL: - case opcodes.OP_EQUALVERIFY: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const v1 = stack.get(-2); - const v2 = stack.get(-1); - - const res = v1.equals(v2); - - stack.pop(); - stack.pop(); - - stack.pushBool(res); - - if (op.value === opcodes.OP_EQUALVERIFY) { - if (!res) - throw new ScriptError('EQUALVERIFY', op, ip); - stack.pop(); - } - - break; - } - case opcodes.OP_1ADD: - case opcodes.OP_1SUB: - case opcodes.OP_NEGATE: - case opcodes.OP_ABS: - case opcodes.OP_NOT: - case opcodes.OP_0NOTEQUAL: { - if (stack.length < 1) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - let num = stack.getNum(-1, minimal, 4); - let cmp; - - switch (op.value) { - case opcodes.OP_1ADD: - num.iaddn(1); - break; - case opcodes.OP_1SUB: - num.isubn(1); - break; - case opcodes.OP_NEGATE: - num.ineg(); - break; - case opcodes.OP_ABS: - num.iabs(); - break; - case opcodes.OP_NOT: - cmp = num.isZero(); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_0NOTEQUAL: - cmp = !num.isZero(); - num = ScriptNum.fromBool(cmp); - break; - default: - assert(false, 'Fatal script error.'); - break; - } - - stack.pop(); - stack.pushNum(num); - - break; - } - case opcodes.OP_ADD: - case opcodes.OP_SUB: - case opcodes.OP_BOOLAND: - case opcodes.OP_BOOLOR: - case opcodes.OP_NUMEQUAL: - case opcodes.OP_NUMEQUALVERIFY: - case opcodes.OP_NUMNOTEQUAL: - case opcodes.OP_LESSTHAN: - case opcodes.OP_GREATERTHAN: - case opcodes.OP_LESSTHANOREQUAL: - case opcodes.OP_GREATERTHANOREQUAL: - case opcodes.OP_MIN: - case opcodes.OP_MAX: { - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const n1 = stack.getNum(-2, minimal, 4); - const n2 = stack.getNum(-1, minimal, 4); - let num, cmp; - - switch (op.value) { - case opcodes.OP_ADD: - num = n1.iadd(n2); - break; - case opcodes.OP_SUB: - num = n1.isub(n2); - break; - case opcodes.OP_BOOLAND: - cmp = n1.toBool() && n2.toBool(); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_BOOLOR: - cmp = n1.toBool() || n2.toBool(); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_NUMEQUAL: - cmp = n1.eq(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_NUMEQUALVERIFY: - cmp = n1.eq(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_NUMNOTEQUAL: - cmp = !n1.eq(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_LESSTHAN: - cmp = n1.lt(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_GREATERTHAN: - cmp = n1.gt(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_LESSTHANOREQUAL: - cmp = n1.lte(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_GREATERTHANOREQUAL: - cmp = n1.gte(n2); - num = ScriptNum.fromBool(cmp); - break; - case opcodes.OP_MIN: - num = ScriptNum.min(n1, n2); - break; - case opcodes.OP_MAX: - num = ScriptNum.max(n1, n2); - break; - default: - assert(false, 'Fatal script error.'); - break; - } - - stack.pop(); - stack.pop(); - stack.pushNum(num); - - if (op.value === opcodes.OP_NUMEQUALVERIFY) { - if (!stack.getBool(-1)) - throw new ScriptError('NUMEQUALVERIFY', op, ip); - stack.pop(); - } - - break; - } - case opcodes.OP_WITHIN: { - if (stack.length < 3) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const n1 = stack.getNum(-3, minimal, 4); - const n2 = stack.getNum(-2, minimal, 4); - const n3 = stack.getNum(-1, minimal, 4); - - const val = n2.lte(n1) && n1.lt(n3); - - stack.pop(); - stack.pop(); - stack.pop(); - - stack.pushBool(val); - break; - } - case opcodes.OP_RIPEMD160: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(ripemd160.digest(stack.pop())); - break; - } - case opcodes.OP_SHA1: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(sha1.digest(stack.pop())); - break; - } - case opcodes.OP_SHA256: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(sha256.digest(stack.pop())); - break; - } - case opcodes.OP_HASH160: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(hash160.digest(stack.pop())); - break; - } - case opcodes.OP_HASH256: { - if (stack.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - stack.push(hash256.digest(stack.pop())); - break; - } - case opcodes.OP_CODESEPARATOR: { - lastSep = ip + 1; - break; - } - case opcodes.OP_CHECKSIG: - case opcodes.OP_CHECKSIGVERIFY: { - if (!tx) - throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); - - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const sig = stack.get(-2); - const key = stack.get(-1); - - const subscript = this.getSubscript(lastSep); - - if (version === 0) - subscript.findAndDelete(sig); - - validateSignature(sig, flags); - validateKey(key, flags, version); - - let res = false; - - if (sig.length > 0) { - const type = sig[sig.length - 1]; - const hash = tx.signatureHash(index, subscript, value, type, version); - res = checksig(hash, sig, key); - } - - if (!res && (flags & Script.flags.VERIFY_NULLFAIL)) { - if (sig.length !== 0) - throw new ScriptError('NULLFAIL', op, ip); - } - - stack.pop(); - stack.pop(); - - stack.pushBool(res); - - if (op.value === opcodes.OP_CHECKSIGVERIFY) { - if (!res) - throw new ScriptError('CHECKSIGVERIFY', op, ip); - stack.pop(); - } - - break; - } - case opcodes.OP_CHECKMULTISIG: - case opcodes.OP_CHECKMULTISIGVERIFY: { - if (!tx) - throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.'); - - let i = 1; - if (stack.length < i) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - let n = stack.getInt(-i, minimal, 4); - let okey = n + 2; - let ikey, isig; - - if (n < 0 || n > consensus.MAX_MULTISIG_PUBKEYS) - throw new ScriptError('PUBKEY_COUNT', op, ip); - - opCount += n; - - if (opCount > consensus.MAX_SCRIPT_OPS) - throw new ScriptError('OP_COUNT', op, ip); - - i += 1; - ikey = i; - i += n; - - if (stack.length < i) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - let m = stack.getInt(-i, minimal, 4); - - if (m < 0 || m > n) - throw new ScriptError('SIG_COUNT', op, ip); - - i += 1; - isig = i; - i += m; - - if (stack.length < i) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - const subscript = this.getSubscript(lastSep); - - for (let j = 0; j < m; j++) { - const sig = stack.get(-isig - j); - if (version === 0) - subscript.findAndDelete(sig); - } - - let res = true; - while (res && m > 0) { - const sig = stack.get(-isig); - const key = stack.get(-ikey); - - validateSignature(sig, flags); - validateKey(key, flags, version); - - if (sig.length > 0) { - const type = sig[sig.length - 1]; - const hash = tx.signatureHash( - index, - subscript, - value, - type, - version - ); - - if (checksig(hash, sig, key)) { - isig += 1; - m -= 1; - } - } - - ikey += 1; - n -= 1; - - if (m > n) - res = false; - } - - while (i > 1) { - if (!res && (flags & Script.flags.VERIFY_NULLFAIL)) { - if (okey === 0 && stack.get(-1).length !== 0) - throw new ScriptError('NULLFAIL', op, ip); - } - - if (okey > 0) - okey -= 1; - - stack.pop(); - - i -= 1; - } - - if (stack.length < 1) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - - if (flags & Script.flags.VERIFY_NULLDUMMY) { - if (stack.get(-1).length !== 0) - throw new ScriptError('SIG_NULLDUMMY', op, ip); - } - - stack.pop(); - - stack.pushBool(res); - - if (op.value === opcodes.OP_CHECKMULTISIGVERIFY) { - if (!res) - throw new ScriptError('CHECKMULTISIGVERIFY', op, ip); - stack.pop(); - } - - break; - } - default: { - throw new ScriptError('BAD_OPCODE', op, ip); - } - } - - if (stack.length + alt.length > consensus.MAX_SCRIPT_STACK) - throw new ScriptError('STACK_SIZE', op, ip); - } - - if (state.length !== 0) - throw new ScriptError('UNBALANCED_CONDITIONAL'); -}; - -/** - * Remove all matched data elements from - * a script's code (used to remove signatures - * before verification). Note that this - * compares and removes data on the _byte level_. - * It also reserializes the data to a single - * script with minimaldata encoding beforehand. - * A signature will _not_ be removed if it is - * not minimaldata. - * @see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-November/006878.html - * @see https://test.webbtc.com/tx/19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff - * @param {Buffer} data - Data element to match against. - * @returns {Number} Total. - */ - -Script.prototype.findAndDelete = function findAndDelete(data) { - const target = Opcode.fromPush(data); - - if (this.raw.length < target.getSize()) - return 0; - - let found = false; - - for (const op of this.code) { - if (op.value === -1) - break; - - if (op.equals(target)) { - found = true; - break; - } - } - - if (!found) - return 0; - - const code = []; - - let total = 0; - - for (const op of this.code) { - if (op.value === -1) - break; - - if (op.equals(target)) { - total += 1; - continue; - } - - code.push(op); - } - - this.code = code; - this.compile(); - - return total; -}; - -/** - * Find a data element in a script. - * @param {Buffer} data - Data element to match against. - * @returns {Number} Index (`-1` if not present). - */ - -Script.prototype.indexOf = function indexOf(data) { - for (let i = 0; i < this.code.length; i++) { - const op = this.code[i]; - - if (op.value === -1) - break; - - if (!op.data) - continue; - - if (op.data.equals(data)) - return i; - } - - return -1; -}; - -/** - * Test a script to see if it is likely - * to be script code (no weird opcodes). - * @returns {Boolean} - */ - -Script.prototype.isCode = function isCode() { - for (const op of this.code) { - if (op.value === -1) - return false; - - if (op.isDisabled()) - return false; - - switch (op.value) { - case opcodes.OP_RESERVED: - case opcodes.OP_NOP: - case opcodes.OP_VER: - case opcodes.OP_VERIF: - case opcodes.OP_VERNOTIF: - case opcodes.OP_RESERVED1: - case opcodes.OP_RESERVED2: - case opcodes.OP_NOP1: - return false; - } - - if (op.value > opcodes.OP_CHECKSEQUENCEVERIFY) - return false; - } - - return true; -}; - -/** - * Inject properties from a pay-to-pubkey script. - * @private - * @param {Buffer} key - */ - -Script.prototype.fromPubkey = function fromPubkey(key) { - assert(Buffer.isBuffer(key) && (key.length === 33 || key.length === 65)); - - this.raw = Buffer.allocUnsafe(1 + key.length + 1); - this.raw[0] = key.length; - key.copy(this.raw, 1); - this.raw[1 + key.length] = opcodes.OP_CHECKSIG; - - key = this.raw.slice(1, 1 + key.length); - - this.code.length = 0; - this.code.push(Opcode.fromPush(key)); - this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); - - return this; -}; - -/** - * Create a pay-to-pubkey script. - * @param {Buffer} key - * @returns {Script} - */ - -Script.fromPubkey = function fromPubkey(key) { - return new Script().fromPubkey(key); -}; - -/** - * Inject properties from a pay-to-pubkeyhash script. - * @private - * @param {Buffer} hash - */ - -Script.prototype.fromPubkeyhash = function fromPubkeyhash(hash) { - assert(Buffer.isBuffer(hash) && hash.length === 20); - - this.raw = Buffer.allocUnsafe(25); - this.raw[0] = opcodes.OP_DUP; - this.raw[1] = opcodes.OP_HASH160; - this.raw[2] = 0x14; - hash.copy(this.raw, 3); - this.raw[23] = opcodes.OP_EQUALVERIFY; - this.raw[24] = opcodes.OP_CHECKSIG; - - hash = this.raw.slice(3, 23); - - this.code.length = 0; - this.code.push(Opcode.fromOp(opcodes.OP_DUP)); - this.code.push(Opcode.fromOp(opcodes.OP_HASH160)); - this.code.push(Opcode.fromPush(hash)); - this.code.push(Opcode.fromOp(opcodes.OP_EQUALVERIFY)); - this.code.push(Opcode.fromOp(opcodes.OP_CHECKSIG)); - - return this; -}; - -/** - * Create a pay-to-pubkeyhash script. - * @param {Buffer} hash - * @returns {Script} - */ - -Script.fromPubkeyhash = function fromPubkeyhash(hash) { - return new Script().fromPubkeyhash(hash); -}; - -/** - * Inject properties from pay-to-multisig script. - * @private - * @param {Number} m - * @param {Number} n - * @param {Buffer[]} keys - */ - -Script.prototype.fromMultisig = function fromMultisig(m, n, keys) { - assert((m & 0xff) === m && (n & 0xff) === n); - assert(Array.isArray(keys)); - assert(keys.length === n, '`n` keys are required for multisig.'); - assert(m >= 1 && m <= n); - assert(n >= 1 && n <= 15); - - this.clear(); - - this.pushSmall(m); - - for (const key of sortKeys(keys)) - this.pushData(key); - - this.pushSmall(n); - this.pushOp(opcodes.OP_CHECKMULTISIG); - - return this.compile(); -}; - -/** - * Create a pay-to-multisig script. - * @param {Number} m - * @param {Number} n - * @param {Buffer[]} keys - * @returns {Script} - */ - -Script.fromMultisig = function fromMultisig(m, n, keys) { - return new Script().fromMultisig(m, n, keys); -}; - -/** - * Inject properties from a pay-to-scripthash script. - * @private - * @param {Buffer} hash - */ - -Script.prototype.fromScripthash = function fromScripthash(hash) { - assert(Buffer.isBuffer(hash) && hash.length === 20); - - this.raw = Buffer.allocUnsafe(23); - this.raw[0] = opcodes.OP_HASH160; - this.raw[1] = 0x14; - hash.copy(this.raw, 2); - this.raw[22] = opcodes.OP_EQUAL; - - hash = this.raw.slice(2, 22); - - this.code.length = 0; - this.code.push(Opcode.fromOp(opcodes.OP_HASH160)); - this.code.push(Opcode.fromPush(hash)); - this.code.push(Opcode.fromOp(opcodes.OP_EQUAL)); - - return this; -}; - -/** - * Create a pay-to-scripthash script. - * @param {Buffer} hash - * @returns {Script} - */ - -Script.fromScripthash = function fromScripthash(hash) { - return new Script().fromScripthash(hash); -}; - -/** - * Inject properties from a nulldata/opreturn script. - * @private - * @param {Buffer} flags - */ - -Script.prototype.fromNulldata = function fromNulldata(flags) { - assert(Buffer.isBuffer(flags)); - assert(flags.length <= policy.MAX_OP_RETURN, 'Nulldata too large.'); - - this.clear(); - this.pushOp(opcodes.OP_RETURN); - this.pushData(flags); - - return this.compile(); -}; - -/** - * Create a nulldata/opreturn script. - * @param {Buffer} flags - * @returns {Script} - */ - -Script.fromNulldata = function fromNulldata(flags) { - return new Script().fromNulldata(flags); -}; - -/** - * Inject properties from a witness program. - * @private - * @param {Number} version - * @param {Buffer} data - */ - -Script.prototype.fromProgram = function fromProgram(version, data) { - assert((version & 0xff) === version && version >= 0 && version <= 16); - assert(Buffer.isBuffer(data) && data.length >= 2 && data.length <= 40); - - this.raw = Buffer.allocUnsafe(2 + data.length); - this.raw[0] = version === 0 ? 0 : version + 0x50; - this.raw[1] = data.length; - data.copy(this.raw, 2); - - data = this.raw.slice(2, 2 + data.length); - - this.code.length = 0; - this.code.push(Opcode.fromSmall(version)); - this.code.push(Opcode.fromPush(data)); - - return this; -}; - -/** - * Create a witness program. - * @param {Number} version - * @param {Buffer} data - * @returns {Script} - */ - -Script.fromProgram = function fromProgram(version, data) { - return new Script().fromProgram(version, data); -}; - -/** - * Inject properties from an address. - * @private - * @param {Address|Base58Address} address - */ - -Script.prototype.fromAddress = function fromAddress(address) { - if (typeof address === 'string') - address = Address.fromString(address); - - assert(address instanceof Address, 'Not an address.'); - - if (address.isPubkeyhash()) - return this.fromPubkeyhash(address.hash); - - if (address.isScripthash()) - return this.fromScripthash(address.hash); - - if (address.isProgram()) - return this.fromProgram(address.version, address.hash); - - throw new Error('Unknown address type.'); -}; - -/** - * Create an output script from an address. - * @param {Address|Base58Address} address - * @returns {Script} - */ - -Script.fromAddress = function fromAddress(address) { - return new Script().fromAddress(address); -}; - -/** - * Inject properties from a witness block commitment. - * @private - * @param {Buffer} hash - * @param {String|Buffer} flags - */ - -Script.prototype.fromCommitment = function fromCommitment(hash, flags) { - const bw = bio.write(36); - - bw.writeU32BE(0xaa21a9ed); - bw.writeHash(hash); - - this.clear(); - this.pushOp(opcodes.OP_RETURN); - this.pushData(bw.render()); - - if (flags) - this.pushData(flags); - - return this.compile(); -}; - -/** - * Create a witness block commitment. - * @param {Buffer} hash - * @param {String|Buffer} flags - * @returns {Script} - */ - -Script.fromCommitment = function fromCommitment(hash, flags) { - return new Script().fromCommitment(hash, flags); -}; - -/** - * Grab and deserialize the redeem script. - * @returns {Script|null} Redeem script. - */ - -Script.prototype.getRedeem = function getRedeem() { - let data = null; - - for (const op of this.code) { - if (op.value === -1) - return null; - - if (op.value > opcodes.OP_16) - return null; - - data = op.data; - } - - if (!data) - return null; - - return Script.fromRaw(data); -}; - -/** - * Get the standard script type. - * @returns {ScriptType} - */ - -Script.prototype.getType = function getType() { - if (this.isPubkey()) - return scriptTypes.PUBKEY; - - if (this.isPubkeyhash()) - return scriptTypes.PUBKEYHASH; - - if (this.isScripthash()) - return scriptTypes.SCRIPTHASH; - - if (this.isWitnessPubkeyhash()) - return scriptTypes.WITNESSPUBKEYHASH; - - if (this.isWitnessScripthash()) - return scriptTypes.WITNESSSCRIPTHASH; - - if (this.isWitnessMasthash()) - return scriptTypes.WITNESSMASTHASH; - - if (this.isMultisig()) - return scriptTypes.MULTISIG; - - if (this.isNulldata()) - return scriptTypes.NULLDATA; - - return scriptTypes.NONSTANDARD; -}; - -/** - * Test whether a script is of an unknown/non-standard type. - * @returns {Boolean} - */ - -Script.prototype.isUnknown = function isUnknown() { - return this.getType() === scriptTypes.NONSTANDARD; -}; - -/** - * Test whether the script is standard by policy standards. - * @returns {Boolean} - */ - -Script.prototype.isStandard = function isStandard() { - const [m, n] = this.getMultisig(); - - if (m !== -1) { - if (n < 1 || n > 3) - return false; - - if (m < 1 || m > n) - return false; - - return true; - } - - if (this.isNulldata()) - return this.raw.length <= policy.MAX_OP_RETURN_BYTES; - - return this.getType() !== scriptTypes.NONSTANDARD; -}; - -/** - * Calculate the size of the script - * excluding the varint size bytes. - * @returns {Number} - */ - -Script.prototype.getSize = function getSize() { - return this.raw.length; -}; - -/** - * Calculate the size of the script - * including the varint size bytes. - * @returns {Number} - */ - -Script.prototype.getVarSize = function getVarSize() { - return encoding.sizeVarBytes(this.raw); -}; - -/** - * "Guess" the address of the input script. - * This method is not 100% reliable. - * @returns {Address|null} - */ - -Script.prototype.getInputAddress = function getInputAddress() { - return Address.fromInputScript(this); -}; - -/** - * Get the address of the script if present. Note that - * pubkey and multisig scripts will be treated as though - * they are pubkeyhash and scripthashes respectively. - * @returns {Address|null} - */ - -Script.prototype.getAddress = function getAddress() { - return Address.fromScript(this); -}; - -/** - * Get the hash160 of the raw script. - * @param {String?} enc - * @returns {Hash} - */ - -Script.prototype.hash160 = function hash160(enc) { - let hash = Hash160.digest(this.toRaw()); - if (enc === 'hex') - hash = hash.toString('hex'); - return hash; -}; - -/** - * Get the sha256 of the raw script. - * @param {String?} enc - * @returns {Hash} - */ - -Script.prototype.sha256 = function sha256(enc) { - let hash = Sha256.digest(this.toRaw()); - if (enc === 'hex') - hash = hash.toString('hex'); - return hash; -}; - -/** - * Test whether the output script is pay-to-pubkey. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Boolean} - */ - -Script.prototype.isPubkey = function isPubkey(minimal) { - if (minimal) { - return this.raw.length >= 35 - && (this.raw[0] === 33 || this.raw[0] === 65) - && this.raw[0] + 2 === this.raw.length - && this.raw[this.raw.length - 1] === opcodes.OP_CHECKSIG; - } - - if (this.code.length !== 2) - return false; - - const size = this.getLength(0); - - return (size === 33 || size === 65) - && this.getOp(1) === opcodes.OP_CHECKSIG; -}; - -/** - * Get P2PK key if present. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Buffer|null} - */ - -Script.prototype.getPubkey = function getPubkey(minimal) { - if (!this.isPubkey(minimal)) - return null; - - if (minimal) - return this.raw.slice(1, 1 + this.raw[0]); - - return this.getData(0); -}; - -/** - * Test whether the output script is pay-to-pubkeyhash. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Boolean} - */ - -Script.prototype.isPubkeyhash = function isPubkeyhash(minimal) { - if (minimal || this.raw.length === 25) { - return this.raw.length === 25 - && this.raw[0] === opcodes.OP_DUP - && this.raw[1] === opcodes.OP_HASH160 - && this.raw[2] === 0x14 - && this.raw[23] === opcodes.OP_EQUALVERIFY - && this.raw[24] === opcodes.OP_CHECKSIG; - } - - if (this.code.length !== 5) - return false; - - return this.getOp(0) === opcodes.OP_DUP - && this.getOp(1) === opcodes.OP_HASH160 - && this.getLength(2) === 20 - && this.getOp(3) === opcodes.OP_EQUALVERIFY - && this.getOp(4) === opcodes.OP_CHECKSIG; -}; - -/** - * Get P2PKH hash if present. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Buffer|null} - */ - -Script.prototype.getPubkeyhash = function getPubkeyhash(minimal) { - if (!this.isPubkeyhash(minimal)) - return null; - - if (minimal) - return this.raw.slice(3, 23); - - return this.getData(2); -}; - -/** - * Test whether the output script is pay-to-multisig. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Boolean} - */ - -Script.prototype.isMultisig = function isMultisig(minimal) { - if (this.code.length < 4 || this.code.length > 19) - return false; - - if (this.getOp(-1) !== opcodes.OP_CHECKMULTISIG) - return false; - - const m = this.getSmall(0); - - if (m < 1) - return false; - - const n = this.getSmall(-2); - - if (n < 1 || m > n) - return false; - - if (this.code.length !== n + 3) - return false; - - for (let i = 1; i < n + 1; i++) { - const op = this.code[i]; - const size = op.toLength(); - - if (size !== 33 && size !== 65) - return false; - - if (minimal && !op.isMinimal()) - return false; - } - - return true; -}; - -/** - * Get multisig m and n values if present. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Array} [m, n] - */ - -Script.prototype.getMultisig = function getMultisig(minimal) { - if (!this.isMultisig(minimal)) - return [-1, -1]; - - return [this.getSmall(0), this.getSmall(-2)]; -}; - -/** - * Test whether the output script is pay-to-scripthash. Note that - * bitcoin itself requires scripthashes to be in strict minimaldata - * encoding. Using `OP_HASH160 OP_PUSHDATA1 [hash] OP_EQUAL` will - * _not_ be recognized as a scripthash. - * @returns {Boolean} - */ - -Script.prototype.isScripthash = function isScripthash() { - return this.raw.length === 23 - && this.raw[0] === opcodes.OP_HASH160 - && this.raw[1] === 0x14 - && this.raw[22] === opcodes.OP_EQUAL; -}; - -/** - * Get P2SH hash if present. - * @returns {Buffer|null} - */ - -Script.prototype.getScripthash = function getScripthash() { - if (!this.isScripthash()) - return null; - - return this.getData(1); -}; - -/** - * Test whether the output script is nulldata/opreturn. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Boolean} - */ - -Script.prototype.isNulldata = function isNulldata(minimal) { - if (this.code.length === 0) - return false; - - if (this.getOp(0) !== opcodes.OP_RETURN) - return false; - - if (this.code.length === 1) - return true; - - if (minimal) { - if (this.raw.length > policy.MAX_OP_RETURN_BYTES) - return false; - } - - for (let i = 1; i < this.code.length; i++) { - const op = this.code[i]; - - if (op.value === -1) - return false; - - if (op.value > opcodes.OP_16) - return false; - - if (minimal && !op.isMinimal()) - return false; - } - - return true; -}; - -/** - * Get OP_RETURN data if present. - * @param {Boolean} [minimal=false] - Minimaldata only. - * @returns {Buffer|null} - */ - -Script.prototype.getNulldata = function getNulldata(minimal) { - if (!this.isNulldata(minimal)) - return null; - - for (let i = 1; i < this.code.length; i++) { - const op = this.code[i]; - const data = op.toPush(); - if (data) - return data; - } - - return EMPTY_BUFFER; -}; - -/** - * Test whether the output script is a segregated witness - * commitment. - * @returns {Boolean} - */ - -Script.prototype.isCommitment = function isCommitment() { - return this.raw.length >= 38 - && this.raw[0] === opcodes.OP_RETURN - && this.raw[1] === 0x24 - && this.raw.readUInt32BE(2, true) === 0xaa21a9ed; -}; - -/** - * Get the commitment hash if present. - * @returns {Buffer|null} - */ - -Script.prototype.getCommitment = function getCommitment() { - if (!this.isCommitment()) - return null; - - return this.raw.slice(6, 38); -}; - -/** - * Test whether the output script is a witness program. - * Note that this will return true even for malformed - * witness v0 programs. - * @return {Boolean} - */ - -Script.prototype.isProgram = function isProgram() { - if (this.raw.length < 4 || this.raw.length > 42) - return false; - - if (this.raw[0] !== opcodes.OP_0 - && (this.raw[0] < opcodes.OP_1 || this.raw[0] > opcodes.OP_16)) { - return false; - } - - if (this.raw[1] + 2 !== this.raw.length) - return false; - - return true; -}; - -/** - * Get the witness program if present. - * @returns {Program|null} - */ - -Script.prototype.getProgram = function getProgram() { - if (!this.isProgram()) - return null; - - const version = this.getSmall(0); - const data = this.getData(1); - - return new Program(version, data); -}; - -/** - * Get the script to the equivalent witness - * program (mimics bitcoind's scriptForWitness). - * @returns {Script|null} - */ - -Script.prototype.forWitness = function forWitness() { - if (this.isProgram()) - return this.clone(); - - const pk = this.getPubkey(); - if (pk) { - const hash = hash160.digest(pk); - return Script.fromProgram(0, hash); - } - - const pkh = this.getPubkeyhash(); - if (pkh) - return Script.fromProgram(0, pkh); - - return Script.fromProgram(0, this.sha256()); -}; - -/** - * Test whether the output script is - * a pay-to-witness-pubkeyhash program. - * @returns {Boolean} - */ - -Script.prototype.isWitnessPubkeyhash = function isWitnessPubkeyhash() { - return this.raw.length === 22 - && this.raw[0] === opcodes.OP_0 - && this.raw[1] === 0x14; -}; - -/** - * Get P2WPKH hash if present. - * @returns {Buffer|null} - */ - -Script.prototype.getWitnessPubkeyhash = function getWitnessPubkeyhash() { - if (!this.isWitnessPubkeyhash()) - return null; - - return this.getData(1); -}; - -/** - * Test whether the output script is - * a pay-to-witness-scripthash program. - * @returns {Boolean} - */ - -Script.prototype.isWitnessScripthash = function isWitnessScripthash() { - return this.raw.length === 34 - && this.raw[0] === opcodes.OP_0 - && this.raw[1] === 0x20; -}; - -/** - * Get P2WSH hash if present. - * @returns {Buffer|null} - */ - -Script.prototype.getWitnessScripthash = function getWitnessScripthash() { - if (!this.isWitnessScripthash()) - return null; - - return this.getData(1); -}; - -/** - * Test whether the output script - * is a pay-to-mast program. - * @returns {Boolean} - */ - -Script.prototype.isWitnessMasthash = function isWitnessMasthash() { - return this.raw.length === 34 - && this.raw[0] === opcodes.OP_1 - && this.raw[1] === 0x20; -}; - -/** - * Get P2WMH hash if present. - * @returns {Buffer|null} - */ - -Script.prototype.getWitnessMasthash = function getWitnessMasthash() { - if (!this.isWitnessMasthash()) - return null; - - return this.getData(1); -}; - -/** - * Test whether the output script is unspendable. - * @returns {Boolean} - */ - -Script.prototype.isUnspendable = function isUnspendable() { - if (this.raw.length > consensus.MAX_SCRIPT_SIZE) - return true; - - return this.raw.length > 0 && this.raw[0] === opcodes.OP_RETURN; -}; - -/** - * "Guess" the type of the input script. - * This method is not 100% reliable. - * @returns {ScriptType} - */ - -Script.prototype.getInputType = function getInputType() { - if (this.isPubkeyInput()) - return scriptTypes.PUBKEY; - - if (this.isPubkeyhashInput()) - return scriptTypes.PUBKEYHASH; - - if (this.isScripthashInput()) - return scriptTypes.SCRIPTHASH; - - if (this.isMultisigInput()) - return scriptTypes.MULTISIG; - - return scriptTypes.NONSTANDARD; -}; - -/** - * "Guess" whether the input script is an unknown/non-standard type. - * This method is not 100% reliable. - * @returns {Boolean} - */ - -Script.prototype.isUnknownInput = function isUnknownInput() { - return this.getInputType() === scriptTypes.NONSTANDARD; -}; - -/** - * "Guess" whether the input script is pay-to-pubkey. - * This method is not 100% reliable. - * @returns {Boolean} - */ - -Script.prototype.isPubkeyInput = function isPubkeyInput() { - if (this.code.length !== 1) - return false; - - const size = this.getLength(0); - - return size >= 9 && size <= 73; -}; - -/** - * Get P2PK signature if present. - * @returns {Buffer|null} - */ - -Script.prototype.getPubkeyInput = function getPubkeyInput() { - if (!this.isPubkeyInput()) - return null; - - return this.getData(0); -}; - -/** - * "Guess" whether the input script is pay-to-pubkeyhash. - * This method is not 100% reliable. - * @returns {Boolean} - */ - -Script.prototype.isPubkeyhashInput = function isPubkeyhashInput() { - if (this.code.length !== 2) - return false; - - const sig = this.getLength(0); - const key = this.getLength(1); - - return sig >= 9 && sig <= 73 - && (key === 33 || key === 65); -}; - -/** - * Get P2PKH signature and key if present. - * @returns {Array} [sig, key] - */ - -Script.prototype.getPubkeyhashInput = function getPubkeyhashInput() { - if (!this.isPubkeyhashInput()) - return [null, null]; - - return [this.getData(0), this.getData(1)]; -}; - -/** - * "Guess" whether the input script is pay-to-multisig. - * This method is not 100% reliable. - * @returns {Boolean} - */ - -Script.prototype.isMultisigInput = function isMultisigInput() { - if (this.code.length < 2) - return false; - - if (this.getOp(0) !== opcodes.OP_0) - return false; - - if (this.getOp(1) > opcodes.OP_PUSHDATA4) - return false; - - // We need to rule out scripthash - // because it may look like multisig. - if (this.isScripthashInput()) - return false; - - for (let i = 1; i < this.code.length; i++) { - const size = this.getLength(i); - if (size < 9 || size > 73) - return false; - } - - return true; -}; - -/** - * Get multisig signatures if present. - * @returns {Buffer[]|null} - */ - -Script.prototype.getMultisigInput = function getMultisigInput() { - if (!this.isMultisigInput()) - return null; - - const sigs = []; - - for (let i = 1; i < this.code.length; i++) - sigs.push(this.getData(i)); - - return sigs; -}; - -/** - * "Guess" whether the input script is pay-to-scripthash. - * This method is not 100% reliable. - * @returns {Boolean} - */ - -Script.prototype.isScripthashInput = function isScripthashInput() { - if (this.code.length < 2) - return false; - - // Grab the raw redeem script. - const raw = this.getData(-1); - - // Last data element should be an array - // for the redeem script. - if (!raw) - return false; - - // Testing for scripthash inputs requires - // some evil magic to work. We do it by - // ruling things _out_. This test will not - // be correct 100% of the time. We rule - // out that the last data element is: a - // null dummy, a valid signature, a valid - // key, and we ensure that it is at least - // a script that does not use undefined - // opcodes. - if (raw.length === 0) - return false; - - if (common.isSignatureEncoding(raw)) - return false; - - if (common.isKeyEncoding(raw)) - return false; - - const redeem = Script.fromRaw(raw); - - if (!redeem.isCode()) - return false; - - if (redeem.isUnspendable()) - return false; - - if (!this.isPushOnly()) - return false; - - return true; -}; - -/** - * Get P2SH redeem script if present. - * @returns {Buffer|null} - */ - -Script.prototype.getScripthashInput = function getScripthashInput() { - if (!this.isScripthashInput()) - return null; - - return this.getData(-1); -}; - -/** - * Get coinbase height. - * @returns {Number} `-1` if not present. - */ - -Script.prototype.getCoinbaseHeight = function getCoinbaseHeight() { - return Script.getCoinbaseHeight(this.raw); -}; - -/** - * Get coinbase height. - * @param {Buffer} raw - Raw script. - * @returns {Number} `-1` if not present. - */ - -Script.getCoinbaseHeight = function getCoinbaseHeight(raw) { - if (raw.length === 0) - return -1; - - if (raw[0] >= opcodes.OP_1 && raw[0] <= opcodes.OP_16) - return raw[0] - 0x50; - - if (raw[0] > 0x06) - return -1; - - const op = Opcode.fromRaw(raw); - const num = op.toNum(); - - if (!num) - return 1; - - if (num.isNeg()) - return -1; - - if (!op.equals(Opcode.fromNum(num))) - return -1; - - return num.toDouble(); -}; - -/** - * Test the script against a bloom filter. - * @param {Bloom} filter - * @returns {Boolean} - */ - -Script.prototype.test = function test(filter) { - for (const op of this.code) { - if (op.value === -1) - break; - - if (!op.data || op.data.length === 0) - continue; - - if (filter.test(op.data)) - return true; - } - - return false; -}; - -/** - * Test the script to see if it contains only push ops. - * Push ops are: OP_1NEGATE, OP_0-OP_16 and all PUSHDATAs. - * @returns {Boolean} - */ - -Script.prototype.isPushOnly = function isPushOnly() { - for (const op of this.code) { - if (op.value === -1) - return false; - - if (op.value > opcodes.OP_16) - return false; - } - - return true; -}; - -/** - * Count the sigops in the script. - * @param {Boolean} accurate - Whether to enable accurate counting. This will - * take into account the `n` value for OP_CHECKMULTISIG(VERIFY). - * @returns {Number} sigop count - */ - -Script.prototype.getSigops = function getSigops(accurate) { - let total = 0; - let lastOp = -1; - - for (const op of this.code) { - if (op.value === -1) - break; - - switch (op.value) { - case opcodes.OP_CHECKSIG: - case opcodes.OP_CHECKSIGVERIFY: - total += 1; - break; - case opcodes.OP_CHECKMULTISIG: - case opcodes.OP_CHECKMULTISIGVERIFY: - if (accurate && lastOp >= opcodes.OP_1 && lastOp <= opcodes.OP_16) - total += lastOp - 0x50; - else - total += consensus.MAX_MULTISIG_PUBKEYS; - break; - } - - lastOp = op.value; - } - - return total; -}; - -/** - * Count the sigops in the script, taking into account redeem scripts. - * @param {Script} input - Input script, needed for access to redeem script. - * @returns {Number} sigop count - */ - -Script.prototype.getScripthashSigops = function getScripthashSigops(input) { - if (!this.isScripthash()) - return this.getSigops(true); - - const redeem = input.getRedeem(); - - if (!redeem) - return 0; - - return redeem.getSigops(true); -}; - -/** - * Count the sigops in a script, taking into account witness programs. - * @param {Script} input - * @param {Witness} witness - * @returns {Number} sigop count - */ - -Script.prototype.getWitnessSigops = function getWitnessSigops(input, witness) { - let program = this.getProgram(); - - if (!program) { - if (this.isScripthash()) { - const redeem = input.getRedeem(); - if (redeem) - program = redeem.getProgram(); - } - } - - if (!program) - return 0; - - if (program.version === 0) { - if (program.data.length === 20) - return 1; - - if (program.data.length === 32 && witness.items.length > 0) { - const redeem = witness.getRedeem(); - return redeem.getSigops(true); - } - } - - return 0; -}; - -/* - * Mutation - */ - -Script.prototype.get = function get(index) { - if (index < 0) - index += this.code.length; - - if (index < 0 || index >= this.code.length) - return null; - - return this.code[index]; -}; - -Script.prototype.pop = function pop() { - const op = this.code.pop(); - return op || null; -}; - -Script.prototype.shift = function shift() { - const op = this.code.shift(); - return op || null; -}; - -Script.prototype.remove = function remove(index) { - if (index < 0) - index += this.code.length; - - if (index < 0 || index >= this.code.length) - return null; - - const items = this.code.splice(index, 1); - - if (items.length === 0) - return null; - - return items[0]; -}; - -Script.prototype.set = function set(index, op) { - if (index < 0) - index += this.code.length; - - assert(Opcode.isOpcode(op)); - assert(index >= 0 && index <= this.code.length); - - this.code[index] = op; - - return this; -}; - -Script.prototype.push = function push(op) { - assert(Opcode.isOpcode(op)); - this.code.push(op); - return this; -}; - -Script.prototype.unshift = function unshift(op) { - assert(Opcode.isOpcode(op)); - this.code.unshift(op); - return this; -}; - -Script.prototype.insert = function insert(index, op) { - if (index < 0) - index += this.code.length; - - assert(Opcode.isOpcode(op)); - assert(index >= 0 && index <= this.code.length); - - this.code.splice(index, 0, op); - - return this; -}; - -/* - * Op - */ - -Script.prototype.getOp = function getOp(index) { - const op = this.get(index); - return op ? op.value : -1; -}; - -Script.prototype.popOp = function popOp() { - const op = this.pop(); - return op ? op.value : -1; -}; - -Script.prototype.shiftOp = function shiftOp() { - const op = this.shift(); - return op ? op.value : -1; -}; - -Script.prototype.removeOp = function removeOp(index) { - const op = this.remove(index); - return op ? op.value : -1; -}; - -Script.prototype.setOp = function setOp(index, value) { - return this.set(index, Opcode.fromOp(value)); -}; - -Script.prototype.pushOp = function pushOp(value) { - return this.push(Opcode.fromOp(value)); -}; - -Script.prototype.unshiftOp = function unshiftOp(value) { - return this.unshift(Opcode.fromOp(value)); -}; - -Script.prototype.insertOp = function insertOp(index, value) { - return this.insert(index, Opcode.fromOp(value)); -}; - -/* - * Data - */ - -Script.prototype.getData = function getData(index) { - const op = this.get(index); - return op ? op.data : null; -}; - -Script.prototype.popData = function popData() { - const op = this.pop(); - return op ? op.data : null; -}; - -Script.prototype.shiftData = function shiftData() { - const op = this.shift(); - return op ? op.data : null; -}; - -Script.prototype.removeData = function removeData(index) { - const op = this.remove(index); - return op ? op.data : null; -}; - -Script.prototype.setData = function setData(index, data) { - return this.set(index, Opcode.fromData(data)); -}; - -Script.prototype.pushData = function pushData(data) { - return this.push(Opcode.fromData(data)); -}; - -Script.prototype.unshiftData = function unshiftData(data) { - return this.unshift(Opcode.fromData(data)); -}; - -Script.prototype.insertData = function insertData(index, data) { - return this.insert(index, Opcode.fromData(data)); -}; - -/* - * Length - */ - -Script.prototype.getLength = function getLength(index) { - const op = this.get(index); - return op ? op.toLength() : -1; -}; - -/* - * Push - */ - -Script.prototype.getPush = function getPush(index) { - const op = this.get(index); - return op ? op.toPush() : null; -}; - -Script.prototype.popPush = function popPush() { - const op = this.pop(); - return op ? op.toPush() : null; -}; - -Script.prototype.shiftPush = function shiftPush() { - const op = this.shift(); - return op ? op.toPush() : null; -}; - -Script.prototype.removePush = function removePush(index) { - const op = this.remove(index); - return op ? op.toPush() : null; -}; - -Script.prototype.setPush = function setPush(index, data) { - return this.set(index, Opcode.fromPush(data)); -}; - -Script.prototype.pushPush = function pushPush(data) { - return this.push(Opcode.fromPush(data)); -}; - -Script.prototype.unshiftPush = function unshiftPush(data) { - return this.unshift(Opcode.fromPush(data)); -}; - -Script.prototype.insertPush = function insertPush(index, data) { - return this.insert(index, Opcode.fromPush(data)); -}; - -/* - * String - */ - -Script.prototype.getString = function getString(index, enc) { - const op = this.get(index); - return op ? op.toString(enc) : null; -}; - -Script.prototype.popString = function popString(enc) { - const op = this.pop(); - return op ? op.toString(enc) : null; -}; - -Script.prototype.shiftString = function shiftString(enc) { - const op = this.shift(); - return op ? op.toString(enc) : null; -}; - -Script.prototype.removeString = function removeString(index, enc) { - const op = this.remove(index); - return op ? op.toString(enc) : null; -}; - -Script.prototype.setString = function setString(index, str, enc) { - return this.set(index, Opcode.fromString(str, enc)); -}; - -Script.prototype.pushString = function pushString(str, enc) { - return this.push(Opcode.fromString(str, enc)); -}; - -Script.prototype.unshiftString = function unshiftString(str, enc) { - return this.unshift(Opcode.fromString(str, enc)); -}; - -Script.prototype.insertString = function insertString(index, str, enc) { - return this.insert(index, Opcode.fromString(str, enc)); -}; - -/* - * Small - */ - -Script.prototype.getSmall = function getSmall(index) { - const op = this.get(index); - return op ? op.toSmall() : -1; -}; - -Script.prototype.popSmall = function popSmall() { - const op = this.pop(); - return op ? op.toSmall() : -1; -}; - -Script.prototype.shiftSmall = function shiftSmall() { - const op = this.shift(); - return op ? op.toSmall() : -1; -}; - -Script.prototype.removeSmall = function removeSmall(index) { - const op = this.remove(index); - return op ? op.toSmall() : -1; -}; - -Script.prototype.setSmall = function setSmall(index, num) { - return this.set(index, Opcode.fromSmall(num)); -}; - -Script.prototype.pushSmall = function pushSmall(num) { - return this.push(Opcode.fromSmall(num)); -}; - -Script.prototype.unshiftSmall = function unshiftSmall(num) { - return this.unshift(Opcode.fromSmall(num)); -}; - -Script.prototype.insertSmall = function insertSmall(index, num) { - return this.insert(index, Opcode.fromSmall(num)); -}; - -/* - * Num - */ - -Script.prototype.getNum = function getNum(index, minimal, limit) { - const op = this.get(index); - return op ? op.toNum(minimal, limit) : null; -}; - -Script.prototype.popNum = function popNum(minimal, limit) { - const op = this.pop(); - return op ? op.toNum(minimal, limit) : null; -}; - -Script.prototype.shiftNum = function shiftNum(minimal, limit) { - const op = this.shift(); - return op ? op.toNum(minimal, limit) : null; -}; - -Script.prototype.removeNum = function removeNum(index, minimal, limit) { - const op = this.remove(index); - return op ? op.toNum(minimal, limit) : null; -}; - -Script.prototype.setNum = function setNum(index, num) { - return this.set(index, Opcode.fromNum(num)); -}; - -Script.prototype.pushNum = function pushNum(num) { - return this.push(Opcode.fromNum(num)); -}; - -Script.prototype.unshiftNum = function unshiftNum(num) { - return this.unshift(Opcode.fromNum(num)); -}; - -Script.prototype.insertNum = function insertNum(index, num) { - return this.insert(index, Opcode.fromNum(num)); -}; - -/* - * Int - */ - -Script.prototype.getInt = function getInt(index, minimal, limit) { - const op = this.get(index); - return op ? op.toInt(minimal, limit) : -1; -}; - -Script.prototype.popInt = function popInt(minimal, limit) { - const op = this.pop(); - return op ? op.toInt(minimal, limit) : -1; -}; - -Script.prototype.shiftInt = function shiftInt(minimal, limit) { - const op = this.shift(); - return op ? op.toInt(minimal, limit) : -1; -}; - -Script.prototype.removeInt = function removeInt(index, minimal, limit) { - const op = this.remove(index); - return op ? op.toInt(minimal, limit) : -1; -}; - -Script.prototype.setInt = function setInt(index, num) { - return this.set(index, Opcode.fromInt(num)); -}; - -Script.prototype.pushInt = function pushInt(num) { - return this.push(Opcode.fromInt(num)); -}; - -Script.prototype.unshiftInt = function unshiftInt(num) { - return this.unshift(Opcode.fromInt(num)); -}; - -Script.prototype.insertInt = function insertInt(index, num) { - return this.insert(index, Opcode.fromInt(num)); -}; - -/* - * Bool - */ - -Script.prototype.getBool = function getBool(index) { - const op = this.get(index); - return op ? op.toBool() : false; -}; - -Script.prototype.popBool = function popBool() { - const op = this.pop(); - return op ? op.toBool() : false; -}; - -Script.prototype.shiftBool = function shiftBool() { - const op = this.shift(); - return op ? op.toBool() : false; -}; - -Script.prototype.removeBool = function removeBool(index) { - const op = this.remove(index); - return op ? op.toBool() : false; -}; - -Script.prototype.setBool = function setBool(index, value) { - return this.set(index, Opcode.fromBool(value)); -}; - -Script.prototype.pushBool = function pushBool(value) { - return this.push(Opcode.fromBool(value)); -}; - -Script.prototype.unshiftBool = function unshiftBool(value) { - return this.unshift(Opcode.fromBool(value)); -}; - -Script.prototype.insertBool = function insertBool(index, value) { - return this.insert(index, Opcode.fromBool(value)); -}; - -/* - * Symbol - */ - -Script.prototype.getSym = function getSym(index) { - const op = this.get(index); - return op ? op.toSymbol() : null; -}; - -Script.prototype.popSym = function popSym() { - const op = this.pop(); - return op ? op.toSymbol() : null; -}; - -Script.prototype.shiftSym = function shiftSym() { - const op = this.shift(); - return op ? op.toSymbol() : null; -}; - -Script.prototype.removeSym = function removeSym(index) { - const op = this.remove(index); - return op ? op.toSymbol() : null; -}; - -Script.prototype.setSym = function setSym(index, symbol) { - return this.set(index, Opcode.fromSymbol(symbol)); -}; - -Script.prototype.pushSym = function pushSym(symbol) { - return this.push(Opcode.fromSymbol(symbol)); -}; - -Script.prototype.unshiftSym = function unshiftSym(symbol) { - return this.unshift(Opcode.fromSymbol(symbol)); -}; - -Script.prototype.insertSym = function insertSym(index, symbol) { - return this.insert(index, Opcode.fromSymbol(symbol)); -}; - -/** - * Inject properties from bitcoind test string. - * @private - * @param {String} items - Script string. - * @throws Parse error. - */ - -Script.prototype.fromString = function fromString(code) { - assert(typeof code === 'string'); - - code = code.trim(); - - if (code.length === 0) - return this; - - const items = code.split(/\s+/); - const bw = bio.write(); - - for (const item of items) { - let symbol = item; - - if (symbol.charCodeAt(0) & 32) - symbol = symbol.toUpperCase(); - - if (!/^OP_/.test(symbol)) - symbol = `OP_${symbol}`; - - const value = opcodes[symbol]; - - if (value == null) { - if (item[0] === '\'') { - assert(item[item.length - 1] === '\'', 'Invalid string.'); - const str = item.slice(1, -1); - const op = Opcode.fromString(str); - bw.writeBytes(op.toRaw()); - continue; - } - - if (/^-?\d+$/.test(item)) { - const num = ScriptNum.fromString(item, 10); - const op = Opcode.fromNum(num); - bw.writeBytes(op.toRaw()); - continue; - } - - assert(item.indexOf('0x') === 0, 'Unknown opcode.'); - - const hex = item.substring(2); - const data = Buffer.from(hex, 'hex'); - - assert(data.length === hex.length / 2, 'Invalid hex string.'); - - bw.writeBytes(data); - - continue; - } - - bw.writeU8(value); - } - - return this.fromRaw(bw.render()); -}; - -/** - * Parse a bitcoind test script - * string into a script object. - * @param {String} items - Script string. - * @returns {Script} - * @throws Parse error. - */ - -Script.fromString = function fromString(code) { - return new Script().fromString(code); -}; - -/** - * Verify an input and output script, and a witness if present. - * @param {Script} input - * @param {Witness} witness - * @param {Script} output - * @param {TX} tx - * @param {Number} index - * @param {Amount} value - * @param {VerifyFlags} flags - * @throws {ScriptError} - */ - -Script.verify = function verify(input, witness, output, tx, index, value, flags) { - if (flags == null) - flags = Script.flags.STANDARD_VERIFY_FLAGS; - - if (flags & Script.flags.VERIFY_SIGPUSHONLY) { - if (!input.isPushOnly()) - throw new ScriptError('SIG_PUSHONLY'); - } - - // Setup a stack. - let stack = new Stack(); - - // Execute the input script - input.execute(stack, flags, tx, index, value, 0); - - // Copy the stack for P2SH - let copy; - if (flags & Script.flags.VERIFY_P2SH) - copy = stack.clone(); - - // Execute the previous output script. - output.execute(stack, flags, tx, index, value, 0); - - // Verify the stack values. - if (stack.length === 0 || !stack.getBool(-1)) - throw new ScriptError('EVAL_FALSE'); - - let hadWitness = false; - - if ((flags & Script.flags.VERIFY_WITNESS) && output.isProgram()) { - hadWitness = true; - - // Input script must be empty. - if (input.raw.length !== 0) - throw new ScriptError('WITNESS_MALLEATED'); - - // Verify the program in the output script. - Script.verifyProgram(witness, output, flags, tx, index, value); - - // Force a cleanstack - stack.length = 1; - } - - // If the script is P2SH, execute the real output script - if ((flags & Script.flags.VERIFY_P2SH) && output.isScripthash()) { - // P2SH can only have push ops in the scriptSig - if (!input.isPushOnly()) - throw new ScriptError('SIG_PUSHONLY'); - - // Reset the stack - stack = copy; - - // Stack should not be empty at this point - if (stack.length === 0) - throw new ScriptError('EVAL_FALSE'); - - // Grab the real redeem script - const raw = stack.pop(); - const redeem = Script.fromRaw(raw); - - // Execute the redeem script. - redeem.execute(stack, flags, tx, index, value, 0); - - // Verify the the stack values. - if (stack.length === 0 || !stack.getBool(-1)) - throw new ScriptError('EVAL_FALSE'); - - if ((flags & Script.flags.VERIFY_WITNESS) && redeem.isProgram()) { - hadWitness = true; - - // Input script must be exactly one push of the redeem script. - if (!input.raw.equals(Opcode.fromPush(raw).toRaw())) - throw new ScriptError('WITNESS_MALLEATED_P2SH'); - - // Verify the program in the redeem script. - Script.verifyProgram(witness, redeem, flags, tx, index, value); - - // Force a cleanstack. - stack.length = 1; - } - } - - // Ensure there is nothing left on the stack. - if (flags & Script.flags.VERIFY_CLEANSTACK) { - assert((flags & Script.flags.VERIFY_P2SH) !== 0); - if (stack.length !== 1) - throw new ScriptError('CLEANSTACK'); - } - - // If we had a witness but no witness program, fail. - if (flags & Script.flags.VERIFY_WITNESS) { - assert((flags & Script.flags.VERIFY_P2SH) !== 0); - if (!hadWitness && witness.items.length > 0) - throw new ScriptError('WITNESS_UNEXPECTED'); - } -}; - -/** - * Verify a witness program. This runs after regular script - * execution if a witness program is present. It will convert - * the witness to a stack and execute the program. - * @param {Witness} witness - * @param {Script} output - * @param {VerifyFlags} flags - * @param {TX} tx - * @param {Number} index - * @param {Amount} value - * @throws {ScriptError} - */ - -Script.verifyProgram = function verifyProgram(witness, output, flags, tx, index, value) { - const program = output.getProgram(); - - assert(program, 'verifyProgram called on non-witness-program.'); - assert((flags & Script.flags.VERIFY_WITNESS) !== 0); - - const stack = witness.toStack(); - let redeem; - - if (program.version === 0) { - if (program.data.length === 32) { - if (stack.length === 0) - throw new ScriptError('WITNESS_PROGRAM_WITNESS_EMPTY'); - - const witnessScript = stack.pop(); - - if (!sha256.digest(witnessScript).equals(program.data)) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - redeem = Script.fromRaw(witnessScript); - } else if (program.data.length === 20) { - if (stack.length !== 2) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - redeem = Script.fromPubkeyhash(program.data); - } else { - // Failure on version=0 (bad program data length). - throw new ScriptError('WITNESS_PROGRAM_WRONG_LENGTH'); - } - } else if ((flags & Script.flags.VERIFY_MAST) && program.version === 1) { - Script.verifyMast(program, stack, output, flags, tx, index); - return; - } else { - // Anyone can spend (we can return true here - // if we want to always relay these transactions). - // Otherwise, if we want to act like an "old" - // implementation and only accept them in blocks, - // we can use the regular output script which will - // succeed in a block, but fail in the mempool - // due to VERIFY_CLEANSTACK. - if (flags & Script.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) - throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); - return; - } - - // Witnesses still have push limits. - for (let j = 0; j < stack.length; j++) { - if (stack.get(j).length > consensus.MAX_SCRIPT_PUSH) - throw new ScriptError('PUSH_SIZE'); - } - - // Verify the redeem script. - redeem.execute(stack, flags, tx, index, value, 1); - - // Verify the stack values. - if (stack.length !== 1 || !stack.getBool(-1)) - throw new ScriptError('EVAL_FALSE'); -}; - -/** - * Verify a MAST witness program. - * @param {Program} program - * @param {Stack} stack - * @param {Script} output - * @param {VerifyFlags} flags - * @param {TX} tx - * @param {Number} index - * @param {Amount} value - * @throws {ScriptError} - */ - -Script.verifyMast = function verifyMast(program, stack, output, flags, tx, index, value) { - assert(program.version === 1); - assert((flags & Script.flags.VERIFY_MAST) !== 0); - - if (stack.length < 4) - throw new ScriptError('INVALID_MAST_STACK'); - - const metadata = stack.get(-1); - if (metadata.length < 1 || metadata.length > 5) - throw new ScriptError('INVALID_MAST_STACK'); - - const subscripts = metadata[0]; - if (subscripts === 0 || stack.length < subscripts + 3) - throw new ScriptError('INVALID_MAST_STACK'); - - let ops = subscripts; - let scriptRoot = bio.write(); - scriptRoot.writeU8(subscripts); - - if (metadata[metadata.length - 1] === 0x00) - throw new ScriptError('INVALID_MAST_STACK'); - - let version = 0; - - for (let j = 1; j < metadata.length; j++) - version |= metadata[j] << 8 * (j - 1); - - if (version < 0) - version += 0x100000000; - - if (version > 0) { - if (flags & Script.flags.DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) - throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'); - } - - let mastRoot = bio.write(); - mastRoot.writeU32(version); - - const pathdata = stack.get(-2); - - if (pathdata.length & 0x1f) - throw new ScriptError('INVALID_MAST_STACK'); - - const depth = pathdata.length >>> 5; - - if (depth > 32) - throw new ScriptError('INVALID_MAST_STACK'); - - ops += depth; - if (version === 0) { - if (ops > consensus.MAX_SCRIPT_OPS) - throw new ScriptError('OP_COUNT'); - } - - const path = []; - - for (let j = 0; j < depth; j++) - path.push(pathdata.slice(j * 32, j * 32 + 32)); - - const posdata = stack.get(-3); - - if (posdata.length > 4) - throw new ScriptError('INVALID_MAST_STACK'); - - let pos = 0; - if (posdata.length > 0) { - if (posdata[posdata.length - 1] === 0x00) - throw new ScriptError('INVALID_MAST_STACK'); - - for (let j = 0; j < posdata.length; j++) - pos |= posdata[j] << 8 * j; - - if (pos < 0) - pos += 0x100000000; - } - - if (depth < 32) { - if (pos >= ((1 << depth) >>> 0)) - throw new ScriptError('INVALID_MAST_STACK'); - } - - let scripts = bio.write(); - scripts.writeBytes(output.raw); - - for (let j = 0; j < subscripts; j++) { - const script = stack.get(-(4 + j)); - if (version === 0) { - if ((scripts.offset + script.length) > consensus.MAX_SCRIPT_SIZE) - throw new ScriptError('SCRIPT_SIZE'); - } - scriptRoot.writeBytes(hash256.digest(script)); - scripts.writeBytes(script); - } - - scriptRoot = hash256.digest(scriptRoot.render()); - scriptRoot = merkle.verifyBranch(scriptRoot, path, pos); - - mastRoot.writeBytes(scriptRoot); - mastRoot = hash256.digest(mastRoot.render()); - - if (!mastRoot.equals(program.data)) - throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); - - if (version === 0) { - stack.length -= 3 + subscripts; - - for (let j = 0; j < stack.length; j++) { - if (stack.get(j).length > consensus.MAX_SCRIPT_PUSH) - throw new ScriptError('PUSH_SIZE'); - } - - scripts = scripts.render(); - output = Script.fromRaw(scripts); - output.execute(stack, flags, tx, index, value, 1); - - if (stack.length !== 0) - throw new ScriptError('EVAL_FALSE'); - } -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -Script.prototype.fromReader = function fromReader(br) { - return this.fromRaw(br.readVarBytes()); -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} - */ - -Script.prototype.fromRaw = function fromRaw(data) { - const br = bio.read(data, true); - - this.raw = data; - - while (br.left()) - this.code.push(Opcode.fromReader(br)); - - return this; -}; - -/** - * Create a script from buffer reader. - * @param {BufferReader} br - * @param {String?} enc - Either `"hex"` or `null`. - * @returns {Script} - */ - -Script.fromReader = function fromReader(br) { - return new Script().fromReader(br); -}; - -/** - * Create a script from a serialized buffer. - * @param {Buffer|String} data - Serialized script. - * @param {String?} enc - Either `"hex"` or `null`. - * @returns {Script} - */ - -Script.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Script().fromRaw(data); -}; - -/** - * Test whether an object a Script. - * @param {Object} obj - * @returns {Boolean} - */ - -Script.isScript = function isScript(obj) { - return obj instanceof Script; -}; - /* * Helpers */ diff --git a/lib/script/scripterror.js b/lib/script/scripterror.js index d910c524..b024533c 100644 --- a/lib/script/scripterror.js +++ b/lib/script/scripterror.js @@ -7,44 +7,50 @@ 'use strict'; /** + * Script Error * An error thrown from the scripting system, * potentially pertaining to Script execution. * @alias module:script.ScriptError - * @constructor * @extends Error - * @param {String} code - Error code. - * @param {Opcode} op - Opcode. - * @param {Number?} ip - Instruction pointer. * @property {String} message - Error message. * @property {String} code - Original code passed in. * @property {Number} op - Opcode. * @property {Number} ip - Instruction pointer. */ -function ScriptError(code, op, ip) { - if (!(this instanceof ScriptError)) - return new ScriptError(code, op, ip); +class ScriptError extends Error { + /** + * Create an error. + * @constructor + * @param {String} code - Error code. + * @param {Opcode} op - Opcode. + * @param {Number?} ip - Instruction pointer. + */ - Error.call(this); + constructor(code, op, ip) { + super(); - this.type = 'ScriptError'; - this.code = code; - this.message = code; - this.op = -1; - this.ip = -1; + this.type = 'ScriptError'; + this.code = code; + this.message = code; + this.op = -1; + this.ip = -1; - if (typeof op === 'string') { - this.message = op; - } else if (op) { - this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`; - this.op = op.value; - this.ip = ip; + if (typeof op === 'string') { + this.message = op; + } else if (op) { + this.message = `${code} (op=${op.toSymbol()}, ip=${ip})`; + this.op = op.value; + this.ip = ip; + } + + if (Error.captureStackTrace) + Error.captureStackTrace(this, ScriptError); } +} - if (Error.captureStackTrace) - Error.captureStackTrace(this, ScriptError); -}; - -Object.setPrototypeOf(ScriptError.prototype, Error.prototype); +/* + * Expose + */ module.exports = ScriptError; diff --git a/lib/script/scriptnum.js b/lib/script/scriptnum.js index be1fa28c..c48ccf2d 100644 --- a/lib/script/scriptnum.js +++ b/lib/script/scriptnum.js @@ -20,237 +20,237 @@ const EMPTY_ARRAY = Buffer.alloc(0); * Script Number * @see https://github.com/chjj/n64 * @alias module:script.ScriptNum - * @constructor - * @param {(Number|String|Buffer|Object)?} num - * @param {(String|Number)?} base * @property {Number} hi * @property {Number} lo * @property {Number} sign */ -function ScriptNum(num, base) { - if (!(this instanceof ScriptNum)) - return new ScriptNum(num, base); +class ScriptNum extends I64 { + /** + * Create a script number. + * @constructor + * @param {(Number|String|Buffer|Object)?} num + * @param {(String|Number)?} base + */ - I64.call(this, num, base); -} - -Object.setPrototypeOf(ScriptNum, I64); -Object.setPrototypeOf(ScriptNum.prototype, I64.prototype); - -/** - * Cast to int32. - * @returns {Number} - */ - -ScriptNum.prototype.getInt = function getInt() { - if (this.lt(I64.INT32_MIN)) - return I64.LONG_MIN; - - if (this.gt(I64.INT32_MAX)) - return I64.LONG_MAX; - - return this.toInt(); -}; - -/** - * Serialize script number. - * @returns {Buffer} - */ - -ScriptNum.prototype.toRaw = function toRaw() { - let num = this; - - // Zeroes are always empty arrays. - if (num.isZero()) - return EMPTY_ARRAY; - - // Need to append sign bit. - let neg = false; - if (num.isNeg()) { - num = num.neg(); - neg = true; + constructor(num, base) { + super(num, base); } - // Calculate size. - const size = num.byteLength(); + /** + * Cast to int32. + * @returns {Number} + */ - let offset = 0; + getInt() { + if (this.lt(I64.INT32_MIN)) + return I64.LONG_MIN; - if (num.testn((size * 8) - 1)) - offset = 1; + if (this.gt(I64.INT32_MAX)) + return I64.LONG_MAX; - // Write number. - const data = Buffer.allocUnsafe(size + offset); - - switch (size) { - case 8: - data[7] = (num.hi >>> 24) & 0xff; - case 7: - data[6] = (num.hi >> 16) & 0xff; - case 6: - data[5] = (num.hi >> 8) & 0xff; - case 5: - data[4] = num.hi & 0xff; - case 4: - data[3] = (num.lo >>> 24) & 0xff; - case 3: - data[2] = (num.lo >> 16) & 0xff; - case 2: - data[1] = (num.lo >> 8) & 0xff; - case 1: - data[0] = num.lo & 0xff; + return this.toInt(); } - // Append sign bit. - if (data[size - 1] & 0x80) { - assert(offset === 1); - assert(data.length === size + offset); - data[size] = neg ? 0x80 : 0; - } else if (neg) { - assert(offset === 0); - assert(data.length === size); - data[size - 1] |= 0x80; - } else { - assert(offset === 0); - assert(data.length === size); + /** + * Serialize script number. + * @returns {Buffer} + */ + + toRaw() { + let num = this; + + // Zeroes are always empty arrays. + if (num.isZero()) + return EMPTY_ARRAY; + + // Need to append sign bit. + let neg = false; + if (num.isNeg()) { + num = num.neg(); + neg = true; + } + + // Calculate size. + const size = num.byteLength(); + + let offset = 0; + + if (num.testn((size * 8) - 1)) + offset = 1; + + // Write number. + const data = Buffer.allocUnsafe(size + offset); + + switch (size) { + case 8: + data[7] = (num.hi >>> 24) & 0xff; + case 7: + data[6] = (num.hi >> 16) & 0xff; + case 6: + data[5] = (num.hi >> 8) & 0xff; + case 5: + data[4] = num.hi & 0xff; + case 4: + data[3] = (num.lo >>> 24) & 0xff; + case 3: + data[2] = (num.lo >> 16) & 0xff; + case 2: + data[1] = (num.lo >> 8) & 0xff; + case 1: + data[0] = num.lo & 0xff; + } + + // Append sign bit. + if (data[size - 1] & 0x80) { + assert(offset === 1); + assert(data.length === size + offset); + data[size] = neg ? 0x80 : 0; + } else if (neg) { + assert(offset === 0); + assert(data.length === size); + data[size - 1] |= 0x80; + } else { + assert(offset === 0); + assert(data.length === size); + } + + return data; } - return data; -}; + /** + * Instantiate script number from serialized data. + * @private + * @param {Buffer} data + * @returns {ScriptNum} + */ -/** - * Instantiate script number from serialized data. - * @private - * @param {Buffer} data - * @returns {ScriptNum} - */ + fromRaw(data) { + assert(Buffer.isBuffer(data)); -ScriptNum.prototype.fromRaw = function fromRaw(data) { - assert(Buffer.isBuffer(data)); + // Empty arrays are always zero. + if (data.length === 0) + return this; + + // Read number (9 bytes max). + switch (data.length) { + case 8: + this.hi |= data[7] << 24; + case 7: + this.hi |= data[6] << 16; + case 6: + this.hi |= data[5] << 8; + case 5: + this.hi |= data[4]; + case 4: + this.lo |= data[3] << 24; + case 3: + this.lo |= data[2] << 16; + case 2: + this.lo |= data[1] << 8; + case 1: + this.lo |= data[0]; + break; + default: + for (let i = 0; i < data.length; i++) + this.orb(i, data[i]); + break; + } + + // Remove high bit and flip sign. + if (data[data.length - 1] & 0x80) { + this.setn((data.length * 8) - 1, 0); + this.ineg(); + } - // Empty arrays are always zero. - if (data.length === 0) return this; - - // Read number (9 bytes max). - switch (data.length) { - case 8: - this.hi |= data[7] << 24; - case 7: - this.hi |= data[6] << 16; - case 6: - this.hi |= data[5] << 8; - case 5: - this.hi |= data[4]; - case 4: - this.lo |= data[3] << 24; - case 3: - this.lo |= data[2] << 16; - case 2: - this.lo |= data[1] << 8; - case 1: - this.lo |= data[0]; - break; - default: - for (let i = 0; i < data.length; i++) - this.orb(i, data[i]); - break; } - // Remove high bit and flip sign. - if (data[data.length - 1] & 0x80) { - this.setn((data.length * 8) - 1, 0); - this.ineg(); + /** + * Serialize script number. + * @returns {Buffer} + */ + + encode() { + return this.toRaw(); } - return this; -}; + /** + * Decode and verify script number. + * @private + * @param {Buffer} data + * @param {Boolean?} minimal - Require minimal encoding. + * @param {Number?} limit - Size limit. + * @returns {ScriptNum} + */ -/** - * Serialize script number. - * @returns {Buffer} - */ + decode(data, minimal, limit) { + assert(Buffer.isBuffer(data)); -ScriptNum.prototype.encode = function encode() { - return this.toRaw(); -}; + if (limit != null && data.length > limit) + throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); -/** - * Decode and verify script number. - * @private - * @param {Buffer} data - * @param {Boolean?} minimal - Require minimal encoding. - * @param {Number?} limit - Size limit. - * @returns {ScriptNum} - */ + if (minimal && !ScriptNum.isMinimal(data)) + throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.'); -ScriptNum.prototype.decode = function decode(data, minimal, limit) { - assert(Buffer.isBuffer(data)); + return this.fromRaw(data); + } - if (limit != null && data.length > limit) - throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); + /** + * Inspect script number. + * @returns {String} + */ - if (minimal && !ScriptNum.isMinimal(data)) - throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.'); + inspect() { + return ``; + } - return this.fromRaw(data); -}; + /** + * Test wether a serialized script + * number is in its most minimal form. + * @param {Buffer} data + * @returns {Boolean} + */ -/** - * Inspect script number. - * @returns {String} - */ + static isMinimal(data) { + assert(Buffer.isBuffer(data)); -ScriptNum.prototype.inspect = function inspect() { - return ``; -}; + if (data.length === 0) + return true; -/** - * Test wether a serialized script - * number is in its most minimal form. - * @param {Buffer} data - * @returns {Boolean} - */ + if ((data[data.length - 1] & 0x7f) === 0) { + if (data.length === 1) + return false; -ScriptNum.isMinimal = function isMinimal(data) { - assert(Buffer.isBuffer(data)); + if ((data[data.length - 2] & 0x80) === 0) + return false; + } - if (data.length === 0) return true; - - if ((data[data.length - 1] & 0x7f) === 0) { - if (data.length === 1) - return false; - - if ((data[data.length - 2] & 0x80) === 0) - return false; } - return true; -}; + /** + * Decode and verify script number. + * @param {Buffer} data + * @param {Boolean?} minimal - Require minimal encoding. + * @param {Number?} limit - Size limit. + * @returns {ScriptNum} + */ -/** - * Decode and verify script number. - * @param {Buffer} data - * @param {Boolean?} minimal - Require minimal encoding. - * @param {Number?} limit - Size limit. - * @returns {ScriptNum} - */ + static decode(data, minimal, limit) { + return new this().decode(data, minimal, limit); + } -ScriptNum.decode = function decode(data, minimal, limit) { - return new ScriptNum().decode(data, minimal, limit); -}; + /** + * Test whether object is a script number. + * @param {Object} obj + * @returns {Boolean} + */ -/** - * Test whether object is a script number. - * @param {Object} obj - * @returns {Boolean} - */ - -ScriptNum.isScriptNum = function isScriptNum(obj) { - return obj instanceof ScriptNum; -}; + static isScriptNum(obj) { + return obj instanceof ScriptNum; + } +} /* * Expose diff --git a/lib/script/sigcache.js b/lib/script/sigcache.js index 2269e596..5d3276b8 100644 --- a/lib/script/sigcache.js +++ b/lib/script/sigcache.js @@ -12,135 +12,144 @@ const secp256k1 = require('bcrypto/lib/secp256k1'); /** * Signature cache. * @alias module:script.SigCache - * @constructor - * @param {Number} [size=10000] * @property {Number} size * @property {Hash[]} keys * @property {Object} valid */ -function SigCache(size) { - if (!(this instanceof SigCache)) - return new SigCache(size); +class SigCache { + /** + * Create a signature cache. + * @constructor + * @param {Number} [size=10000] + */ - if (size == null) - size = 10000; + constructor(size) { + if (size == null) + size = 10000; - assert((size >>> 0) === size); + assert((size >>> 0) === size); - this.size = size; - this.keys = []; - this.valid = new Map(); + this.size = size; + this.keys = []; + this.valid = new Map(); + } + + /** + * Resize the sigcache. + * @param {Number} size + */ + + resize(size) { + assert((size >>> 0) === size); + + this.size = size; + this.keys.length = 0; + this.valid.clear(); + } + + /** + * Add item to the sigcache. + * Potentially evict a random member. + * @param {Hash} hash - Sig hash. + * @param {Buffer} sig + * @param {Buffer} key + */ + + add(hash, sig, key) { + if (this.size === 0) + return; + + this.valid.set(hash, new SigCacheEntry(sig, key)); + + if (this.keys.length >= this.size) { + const i = Math.floor(Math.random() * this.keys.length); + const k = this.keys[i]; + this.valid.delete(k); + this.keys[i] = hash; + } else { + this.keys.push(hash); + } + } + + /** + * Test whether the sig exists. + * @param {Hash} hash - Sig hash. + * @param {Buffer} sig + * @param {Buffer} key + * @returns {Boolean} + */ + + has(hash, sig, key) { + const entry = this.valid.get(hash); + + if (!entry) + return false; + + return entry.equals(sig, key); + } + + /** + * Verify a signature, testing + * it against the cache first. + * @param {Buffer} msg + * @param {Buffer} sig + * @param {Buffer} key + * @returns {Boolean} + */ + + verify(msg, sig, key) { + if (this.size === 0) + return secp256k1.verify(msg, sig, key); + + const hash = msg.toString('hex'); + + if (this.has(hash, sig, key)) + return true; + + const result = secp256k1.verify(msg, sig, key); + + if (!result) + return false; + + this.add(hash, sig, key); + + return true; + } } /** - * Resize the sigcache. - * @param {Number} size - */ - -SigCache.prototype.resize = function resize(size) { - assert((size >>> 0) === size); - - this.size = size; - this.keys.length = 0; - this.valid.clear(); -}; - -/** - * Add item to the sigcache. - * Potentially evict a random member. - * @param {Hash} hash - Sig hash. - * @param {Buffer} sig - * @param {Buffer} key - */ - -SigCache.prototype.add = function add(hash, sig, key) { - if (this.size === 0) - return; - - this.valid.set(hash, new SigCacheEntry(sig, key)); - - if (this.keys.length >= this.size) { - const i = Math.floor(Math.random() * this.keys.length); - const k = this.keys[i]; - this.valid.delete(k); - this.keys[i] = hash; - } else { - this.keys.push(hash); - } -}; - -/** - * Test whether the sig exists. - * @param {Hash} hash - Sig hash. - * @param {Buffer} sig - * @param {Buffer} key - * @returns {Boolean} - */ - -SigCache.prototype.has = function has(hash, sig, key) { - const entry = this.valid.get(hash); - - if (!entry) - return false; - - return entry.equals(sig, key); -}; - -/** - * Verify a signature, testing - * it against the cache first. - * @param {Buffer} msg - * @param {Buffer} sig - * @param {Buffer} key - * @returns {Boolean} - */ - -SigCache.prototype.verify = function verify(msg, sig, key) { - if (this.size === 0) - return secp256k1.verify(msg, sig, key); - - const hash = msg.toString('hex'); - - if (this.has(hash, sig, key)) - return true; - - const result = secp256k1.verify(msg, sig, key); - - if (!result) - return false; - - this.add(hash, sig, key); - - return true; -}; - -/** - * Signature cache entry. - * @constructor + * Signature Cache Entry * @ignore - * @param {Buffer} sig - * @param {Buffer} key * @property {Buffer} sig * @property {Buffer} key */ -function SigCacheEntry(sig, key) { - this.sig = Buffer.from(sig); - this.key = Buffer.from(key); +class SigCacheEntry { + /** + * Create a cache entry. + * @constructor + * @param {Buffer} sig + * @param {Buffer} key + */ + + constructor(sig, key) { + this.sig = Buffer.from(sig); + this.key = Buffer.from(key); + } + + /** + * Compare an entry to a sig and key. + * @param {Buffer} sig + * @param {Buffer} key + * @returns {Boolean} + */ + + equals(sig, key) { + return this.sig.equals(sig) && this.key.equals(key); + } } -/** - * Compare an entry to a sig and key. - * @param {Buffer} sig - * @param {Buffer} key - * @returns {Boolean} - */ - -SigCacheEntry.prototype.equals = function equals(sig, key) { - return this.sig.equals(sig) && this.key.equals(key); -}; - /* * Expose */ diff --git a/lib/script/stack.js b/lib/script/stack.js index 18e62f9c..97978522 100644 --- a/lib/script/stack.js +++ b/lib/script/stack.js @@ -12,552 +12,560 @@ const common = require('./common'); const ScriptNum = require('./scriptnum'); /** + * Stack * Represents the stack of a Script during execution. * @alias module:script.Stack - * @constructor - * @param {Buffer[]?} items - Stack items. * @property {Buffer[]} items - Stack items. * @property {Number} length - Size of stack. */ -function Stack(items) { - if (!(this instanceof Stack)) - return new Stack(items); +class Stack { + /** + * Create a stack. + * @constructor + * @param {Buffer[]?} items - Stack items. + */ - this.items = items || []; -} + constructor(items) { + this.items = items || []; + } -/* - * Expose length setter and getter. - */ + /** + * Get length. + * @returns {Number} + */ -Object.defineProperty(Stack.prototype, 'length', { - get() { - return this.items.length; - }, - set(length) { - this.items.length = length; + get length() { return this.items.length; } -}); -/** - * Instantiate a key and value iterator. - * @returns {StackIterator} - */ - -Stack.prototype[Symbol.iterator] = function iterator() { - return this.items[Symbol.iterator](); -}; - -/** - * Instantiate a value-only iterator. - * @returns {StackIterator} - */ - -Stack.prototype.values = function values() { - return this.items.values(); -}; - -/** - * Instantiate a key and value iterator. - * @returns {StackIterator} - */ - -Stack.prototype.entries = function entries() { - return this.items.entries(); -}; - -/** - * Inspect the stack. - * @returns {String} Human-readable stack. - */ - -Stack.prototype.inspect = function inspect() { - return ``; -}; - -/** - * Convert the stack to a string. - * @returns {String} Human-readable stack. - */ - -Stack.prototype.toString = function toString() { - const out = []; - - for (const item of this.items) - out.push(item.toString('hex')); - - return out.join(' '); -}; - -/** - * Format the stack as bitcoind asm. - * @param {Boolean?} decode - Attempt to decode hash types. - * @returns {String} Human-readable script. - */ - -Stack.prototype.toASM = function toASM(decode) { - const out = []; - - for (const item of this.items) - out.push(common.toASM(item, decode)); - - return out.join(' '); -}; - -/** - * Clone the stack. - * @returns {Stack} Cloned stack. - */ - -Stack.prototype.clone = function clone() { - return new Stack(this.items.slice()); -}; - -/** - * Clear the stack. - * @returns {Stack} - */ - -Stack.prototype.clear = function clear() { - this.items.length = 0; - return this; -}; - -/** - * Get a stack item by index. - * @param {Number} index - * @returns {Buffer|null} - */ - -Stack.prototype.get = function get(index) { - if (index < 0) - index += this.items.length; - - if (index < 0 || index >= this.items.length) - return null; - - return this.items[index]; -}; - -/** - * Pop a stack item. - * @see Array#pop - * @returns {Buffer|null} - */ - -Stack.prototype.pop = function pop() { - const item = this.items.pop(); - return item || null; -}; - -/** - * Shift a stack item. - * @see Array#shift - * @returns {Buffer|null} - */ - -Stack.prototype.shift = function shift() { - const item = this.items.shift(); - return item || null; -}; - -/** - * Remove an item. - * @param {Number} index - * @returns {Buffer} - */ - -Stack.prototype.remove = function remove(index) { - if (index < 0) - index += this.items.length; - - if (index < 0 || index >= this.items.length) - return null; - - const items = this.items.splice(index, 1); - - if (items.length === 0) - return null; - - return items[0]; -}; - -/** - * Set stack item at index. - * @param {Number} index - * @param {Buffer} value - * @returns {Buffer} - */ - -Stack.prototype.set = function set(index, item) { - if (index < 0) - index += this.items.length; - - assert(Buffer.isBuffer(item)); - assert(index >= 0 && index <= this.items.length); - - this.items[index] = item; - - return this; -}; - -/** - * Push item onto stack. - * @see Array#push - * @param {Buffer} item - * @returns {Number} Stack size. - */ - -Stack.prototype.push = function push(item) { - assert(Buffer.isBuffer(item)); - this.items.push(item); - return this; -}; - -/** - * Unshift item from stack. - * @see Array#unshift - * @param {Buffer} item - * @returns {Number} - */ - -Stack.prototype.unshift = function unshift(item) { - assert(Buffer.isBuffer(item)); - this.items.unshift(item); - return this; -}; - -/** - * Insert an item. - * @param {Number} index - * @param {Buffer} item - * @returns {Buffer} - */ - -Stack.prototype.insert = function insert(index, item) { - if (index < 0) - index += this.items.length; - - assert(Buffer.isBuffer(item)); - assert(index >= 0 && index <= this.items.length); - - this.items.splice(index, 0, item); - - return this; -}; - -/** - * Erase stack items. - * @param {Number} start - * @param {Number} end - * @returns {Buffer[]} - */ - -Stack.prototype.erase = function erase(start, end) { - if (start < 0) - start = this.items.length + start; - - if (end < 0) - end = this.items.length + end; - - this.items.splice(start, end - start); -}; - -/** - * Swap stack values. - * @param {Number} i1 - Index 1. - * @param {Number} i2 - Index 2. - */ - -Stack.prototype.swap = function swap(i1, i2) { - if (i1 < 0) - i1 = this.items.length + i1; - - if (i2 < 0) - i2 = this.items.length + i2; - - const v1 = this.items[i1]; - const v2 = this.items[i2]; - - this.items[i1] = v2; - this.items[i2] = v1; -}; - -/* - * Data - */ - -Stack.prototype.getData = function getData(index) { - return this.get(index); -}; - -Stack.prototype.popData = function popData() { - return this.pop(); -}; - -Stack.prototype.shiftData = function shiftData() { - return this.shift(); -}; - -Stack.prototype.removeData = function removeData(index) { - return this.remove(index); -}; - -Stack.prototype.setData = function setData(index, data) { - return this.set(index, data); -}; - -Stack.prototype.pushData = function pushData(data) { - return this.push(data); -}; - -Stack.prototype.unshiftData = function unshiftData(data) { - return this.unshift(data); -}; - -Stack.prototype.insertData = function insertData(index, data) { - return this.insert(index, data); -}; - -/* - * Length - */ - -Stack.prototype.getLength = function getLength(index) { - const item = this.get(index); - return item ? item.length : -1; -}; - -/* - * String - */ - -Stack.prototype.getString = function getString(index, enc) { - const item = this.get(index); - return item ? Stack.toString(item, enc) : null; -}; - -Stack.prototype.popString = function popString(enc) { - const item = this.pop(); - return item ? Stack.toString(item, enc) : null; -}; - -Stack.prototype.shiftString = function shiftString(enc) { - const item = this.shift(); - return item ? Stack.toString(item, enc) : null; -}; - -Stack.prototype.removeString = function removeString(index, enc) { - const item = this.remove(index); - return item ? Stack.toString(item, enc) : null; -}; - -Stack.prototype.setString = function setString(index, str, enc) { - return this.set(index, Stack.fromString(str, enc)); -}; - -Stack.prototype.pushString = function pushString(str, enc) { - return this.push(Stack.fromString(str, enc)); -}; - -Stack.prototype.unshiftString = function unshiftString(str, enc) { - return this.unshift(Stack.fromString(str, enc)); -}; - -Stack.prototype.insertString = function insertString(index, str, enc) { - return this.insert(index, Stack.fromString(str, enc)); -}; - -/* - * Num - */ - -Stack.prototype.getNum = function getNum(index, minimal, limit) { - const item = this.get(index); - return item ? Stack.toNum(item, minimal, limit) : null; -}; - -Stack.prototype.popNum = function popNum(minimal, limit) { - const item = this.pop(); - return item ? Stack.toNum(item, minimal, limit) : null; -}; - -Stack.prototype.shiftNum = function shiftNum(minimal, limit) { - const item = this.shift(); - return item ? Stack.toNum(item, minimal, limit) : null; -}; - -Stack.prototype.removeNum = function removeNum(index, minimal, limit) { - const item = this.remove(index); - return item ? Stack.toNum(item, minimal, limit) : null; -}; - -Stack.prototype.setNum = function setNum(index, num) { - return this.set(index, Stack.fromNum(num)); -}; - -Stack.prototype.pushNum = function pushNum(num) { - return this.push(Stack.fromNum(num)); -}; - -Stack.prototype.unshiftNum = function unshiftNum(num) { - return this.unshift(Stack.fromNum(num)); -}; - -Stack.prototype.insertNum = function insertNum(index, num) { - return this.insert(index, Stack.fromNum(num)); -}; - -/* - * Int - */ - -Stack.prototype.getInt = function getInt(index, minimal, limit) { - const item = this.get(index); - return item ? Stack.toInt(item, minimal, limit) : -1; -}; - -Stack.prototype.popInt = function popInt(minimal, limit) { - const item = this.pop(); - return item ? Stack.toInt(item, minimal, limit) : -1; -}; - -Stack.prototype.shiftInt = function shiftInt(minimal, limit) { - const item = this.shift(); - return item ? Stack.toInt(item, minimal, limit) : -1; -}; - -Stack.prototype.removeInt = function removeInt(index, minimal, limit) { - const item = this.remove(index); - return item ? Stack.toInt(item, minimal, limit) : -1; -}; - -Stack.prototype.setInt = function setInt(index, num) { - return this.set(index, Stack.fromInt(num)); -}; - -Stack.prototype.pushInt = function pushInt(num) { - return this.push(Stack.fromInt(num)); -}; - -Stack.prototype.unshiftInt = function unshiftInt(num) { - return this.unshift(Stack.fromInt(num)); -}; - -Stack.prototype.insertInt = function insertInt(index, num) { - return this.insert(index, Stack.fromInt(num)); -}; - -/* - * Bool - */ - -Stack.prototype.getBool = function getBool(index) { - const item = this.get(index); - return item ? Stack.toBool(item) : false; -}; - -Stack.prototype.popBool = function popBool() { - const item = this.pop(); - return item ? Stack.toBool(item) : false; -}; - -Stack.prototype.shiftBool = function shiftBool() { - const item = this.shift(); - return item ? Stack.toBool(item) : false; -}; - -Stack.prototype.removeBool = function removeBool(index) { - const item = this.remove(index); - return item ? Stack.toBool(item) : false; -}; - -Stack.prototype.setBool = function setBool(index, value) { - return this.set(index, Stack.fromBool(value)); -}; - -Stack.prototype.pushBool = function pushBool(value) { - return this.push(Stack.fromBool(value)); -}; - -Stack.prototype.unshiftBool = function unshiftBool(value) { - return this.unshift(Stack.fromBool(value)); -}; - -Stack.prototype.insertBool = function insertBool(index, value) { - return this.insert(index, Stack.fromBool(value)); -}; - -/** - * Test an object to see if it is a Stack. - * @param {Object} obj - * @returns {Boolean} - */ - -Stack.isStack = function isStack(obj) { - return obj instanceof Stack; -}; - -/* - * Encoding - */ - -Stack.toString = function toString(item, enc) { - assert(Buffer.isBuffer(item)); - return item.toString(enc || 'utf8'); -}; - -Stack.fromString = function fromString(str, enc) { - assert(typeof str === 'string'); - return Buffer.from(str, enc || 'utf8'); -}; - -Stack.toNum = function toNum(item, minimal, limit) { - return ScriptNum.decode(item, minimal, limit); -}; - -Stack.fromNum = function fromNum(num) { - assert(ScriptNum.isScriptNum(num)); - return num.encode(); -}; - -Stack.toInt = function toInt(item, minimal, limit) { - const num = Stack.toNum(item, minimal, limit); - return num.getInt(); -}; - -Stack.fromInt = function fromInt(int) { - assert(typeof int === 'number'); - - if (int >= -1 && int <= 16) - return common.small[int + 1]; - - const num = ScriptNum.fromNumber(int); - - return Stack.fromNum(num); -}; - -Stack.toBool = function toBool(item) { - assert(Buffer.isBuffer(item)); - - for (let i = 0; i < item.length; i++) { - if (item[i] !== 0) { - // Cannot be negative zero - if (i === item.length - 1 && item[i] === 0x80) - return false; - return true; + /** + * Set length. + * @param {Number} value + */ + + set length(value) { + this.items.length = value; + } + + /** + * Instantiate a value-only iterator. + * @returns {StackIterator} + */ + + [Symbol.iterator]() { + return this.items[Symbol.iterator](); + } + + /** + * Instantiate a value-only iterator. + * @returns {StackIterator} + */ + + values() { + return this.items.values(); + } + + /** + * Instantiate a key and value iterator. + * @returns {StackIterator} + */ + + entries() { + return this.items.entries(); + } + + /** + * Inspect the stack. + * @returns {String} Human-readable stack. + */ + + inspect() { + return ``; + } + + /** + * Convert the stack to a string. + * @returns {String} Human-readable stack. + */ + + toString() { + const out = []; + + for (const item of this.items) + out.push(item.toString('hex')); + + return out.join(' '); + } + + /** + * Format the stack as bitcoind asm. + * @param {Boolean?} decode - Attempt to decode hash types. + * @returns {String} Human-readable script. + */ + + toASM(decode) { + const out = []; + + for (const item of this.items) + out.push(common.toASM(item, decode)); + + return out.join(' '); + } + + /** + * Clone the stack. + * @returns {Stack} Cloned stack. + */ + + clone() { + return new this.constructor(this.items.slice()); + } + + /** + * Clear the stack. + * @returns {Stack} + */ + + clear() { + this.items.length = 0; + return this; + } + + /** + * Get a stack item by index. + * @param {Number} index + * @returns {Buffer|null} + */ + + get(index) { + if (index < 0) + index += this.items.length; + + if (index < 0 || index >= this.items.length) + return null; + + return this.items[index]; + } + + /** + * Pop a stack item. + * @see Array#pop + * @returns {Buffer|null} + */ + + pop() { + const item = this.items.pop(); + return item || null; + } + + /** + * Shift a stack item. + * @see Array#shift + * @returns {Buffer|null} + */ + + shift() { + const item = this.items.shift(); + return item || null; + } + + /** + * Remove an item. + * @param {Number} index + * @returns {Buffer} + */ + + remove(index) { + if (index < 0) + index += this.items.length; + + if (index < 0 || index >= this.items.length) + return null; + + const items = this.items.splice(index, 1); + + if (items.length === 0) + return null; + + return items[0]; + } + + /** + * Set stack item at index. + * @param {Number} index + * @param {Buffer} value + * @returns {Buffer} + */ + + set(index, item) { + if (index < 0) + index += this.items.length; + + assert(Buffer.isBuffer(item)); + assert(index >= 0 && index <= this.items.length); + + this.items[index] = item; + + return this; + } + + /** + * Push item onto stack. + * @see Array#push + * @param {Buffer} item + * @returns {Number} Stack size. + */ + + push(item) { + assert(Buffer.isBuffer(item)); + this.items.push(item); + return this; + } + + /** + * Unshift item from stack. + * @see Array#unshift + * @param {Buffer} item + * @returns {Number} + */ + + unshift(item) { + assert(Buffer.isBuffer(item)); + this.items.unshift(item); + return this; + } + + /** + * Insert an item. + * @param {Number} index + * @param {Buffer} item + * @returns {Buffer} + */ + + insert(index, item) { + if (index < 0) + index += this.items.length; + + assert(Buffer.isBuffer(item)); + assert(index >= 0 && index <= this.items.length); + + this.items.splice(index, 0, item); + + return this; + } + + /** + * Erase stack items. + * @param {Number} start + * @param {Number} end + * @returns {Buffer[]} + */ + + erase(start, end) { + if (start < 0) + start = this.items.length + start; + + if (end < 0) + end = this.items.length + end; + + this.items.splice(start, end - start); + } + + /** + * Swap stack values. + * @param {Number} i1 - Index 1. + * @param {Number} i2 - Index 2. + */ + + swap(i1, i2) { + if (i1 < 0) + i1 = this.items.length + i1; + + if (i2 < 0) + i2 = this.items.length + i2; + + const v1 = this.items[i1]; + const v2 = this.items[i2]; + + this.items[i1] = v2; + this.items[i2] = v1; + } + + /* + * Data + */ + + getData(index) { + return this.get(index); + } + + popData() { + return this.pop(); + } + + shiftData() { + return this.shift(); + } + + removeData(index) { + return this.remove(index); + } + + setData(index, data) { + return this.set(index, data); + } + + pushData(data) { + return this.push(data); + } + + unshiftData(data) { + return this.unshift(data); + } + + insertData(index, data) { + return this.insert(index, data); + } + + /* + * Length + */ + + getLength(index) { + const item = this.get(index); + return item ? item.length : -1; + } + + /* + * String + */ + + getString(index, enc) { + const item = this.get(index); + return item ? Stack.toString(item, enc) : null; + } + + popString(enc) { + const item = this.pop(); + return item ? Stack.toString(item, enc) : null; + } + + shiftString(enc) { + const item = this.shift(); + return item ? Stack.toString(item, enc) : null; + } + + removeString(index, enc) { + const item = this.remove(index); + return item ? Stack.toString(item, enc) : null; + } + + setString(index, str, enc) { + return this.set(index, Stack.fromString(str, enc)); + } + + pushString(str, enc) { + return this.push(Stack.fromString(str, enc)); + } + + unshiftString(str, enc) { + return this.unshift(Stack.fromString(str, enc)); + } + + insertString(index, str, enc) { + return this.insert(index, Stack.fromString(str, enc)); + } + + /* + * Num + */ + + getNum(index, minimal, limit) { + const item = this.get(index); + return item ? Stack.toNum(item, minimal, limit) : null; + } + + popNum(minimal, limit) { + const item = this.pop(); + return item ? Stack.toNum(item, minimal, limit) : null; + } + + shiftNum(minimal, limit) { + const item = this.shift(); + return item ? Stack.toNum(item, minimal, limit) : null; + } + + removeNum(index, minimal, limit) { + const item = this.remove(index); + return item ? Stack.toNum(item, minimal, limit) : null; + } + + setNum(index, num) { + return this.set(index, Stack.fromNum(num)); + } + + pushNum(num) { + return this.push(Stack.fromNum(num)); + } + + unshiftNum(num) { + return this.unshift(Stack.fromNum(num)); + } + + insertNum(index, num) { + return this.insert(index, Stack.fromNum(num)); + } + + /* + * Int + */ + + getInt(index, minimal, limit) { + const item = this.get(index); + return item ? Stack.toInt(item, minimal, limit) : -1; + } + + popInt(minimal, limit) { + const item = this.pop(); + return item ? Stack.toInt(item, minimal, limit) : -1; + } + + shiftInt(minimal, limit) { + const item = this.shift(); + return item ? Stack.toInt(item, minimal, limit) : -1; + } + + removeInt(index, minimal, limit) { + const item = this.remove(index); + return item ? Stack.toInt(item, minimal, limit) : -1; + } + + setInt(index, num) { + return this.set(index, Stack.fromInt(num)); + } + + pushInt(num) { + return this.push(Stack.fromInt(num)); + } + + unshiftInt(num) { + return this.unshift(Stack.fromInt(num)); + } + + insertInt(index, num) { + return this.insert(index, Stack.fromInt(num)); + } + + /* + * Bool + */ + + getBool(index) { + const item = this.get(index); + return item ? Stack.toBool(item) : false; + } + + popBool() { + const item = this.pop(); + return item ? Stack.toBool(item) : false; + } + + shiftBool() { + const item = this.shift(); + return item ? Stack.toBool(item) : false; + } + + removeBool(index) { + const item = this.remove(index); + return item ? Stack.toBool(item) : false; + } + + setBool(index, value) { + return this.set(index, Stack.fromBool(value)); + } + + pushBool(value) { + return this.push(Stack.fromBool(value)); + } + + unshiftBool(value) { + return this.unshift(Stack.fromBool(value)); + } + + insertBool(index, value) { + return this.insert(index, Stack.fromBool(value)); + } + + /** + * Test an object to see if it is a Stack. + * @param {Object} obj + * @returns {Boolean} + */ + + static isStack(obj) { + return obj instanceof Stack; + } + + /* + * Encoding + */ + + static toString(item, enc) { + assert(Buffer.isBuffer(item)); + return item.toString(enc || 'utf8'); + } + + static fromString(str, enc) { + assert(typeof str === 'string'); + return Buffer.from(str, enc || 'utf8'); + } + + static toNum(item, minimal, limit) { + return ScriptNum.decode(item, minimal, limit); + } + + static fromNum(num) { + assert(ScriptNum.isScriptNum(num)); + return num.encode(); + } + + static toInt(item, minimal, limit) { + const num = Stack.toNum(item, minimal, limit); + return num.getInt(); + } + + static fromInt(int) { + assert(typeof int === 'number'); + + if (int >= -1 && int <= 16) + return common.small[int + 1]; + + const num = ScriptNum.fromNumber(int); + + return Stack.fromNum(num); + } + + static toBool(item) { + assert(Buffer.isBuffer(item)); + + for (let i = 0; i < item.length; i++) { + if (item[i] !== 0) { + // Cannot be negative zero + if (i === item.length - 1 && item[i] === 0x80) + return false; + return true; + } } + + return false; } - return false; -}; - -Stack.fromBool = function fromBool(value) { - assert(typeof value === 'boolean'); - return Stack.fromInt(value ? 1 : 0); -}; + static fromBool(value) { + assert(typeof value === 'boolean'); + return Stack.fromInt(value ? 1 : 0); + } +} /* * Expose diff --git a/lib/script/witness.js b/lib/script/witness.js index 633ea42b..00b9f9ef 100644 --- a/lib/script/witness.js +++ b/lib/script/witness.js @@ -17,532 +17,540 @@ const {encoding} = bio; const scriptTypes = common.types; /** - * Refers to the witness field of segregated witness transactions. + * Witness + * Refers to the witness vector of + * segregated witness transactions. * @alias module:script.Witness - * @constructor - * @param {Buffer[]|NakedWitness} items - Array of - * stack items. + * @extends Stack * @property {Buffer[]} items * @property {Script?} redeem * @property {Number} length */ -function Witness(options) { - if (!(this instanceof Witness)) - return new Witness(options); +class Witness extends Stack { + /** + * Create a witness. + * @alias module:script.Witness + * @constructor + * @param {Buffer[]|Object} items - Array of + * stack items. + * @property {Buffer[]} items + * @property {Script?} redeem + * @property {Number} length + */ - Stack.call(this, []); + constructor(options) { + super(); - if (options) - this.fromOptions(options); + if (options) + this.fromOptions(options); + } + + /** + * Inject properties from options object. + * @private + * @param {Object} options + */ + + fromOptions(options) { + assert(options, 'Witness data is required.'); + + if (Array.isArray(options)) + return this.fromArray(options); + + if (options.items) + return this.fromArray(options.items); + + return this; + } + + /** + * Instantiate witness from options. + * @param {Object} options + * @returns {Witness} + */ + + static fromOptions(options) { + return new this().fromOptions(options); + } + + /** + * Convert witness to an array of buffers. + * @returns {Buffer[]} + */ + + toArray() { + return this.items.slice(); + } + + /** + * Inject properties from an array of buffers. + * @private + * @param {Buffer[]} items + */ + + fromArray(items) { + assert(Array.isArray(items)); + this.items = items; + return this; + } + + /** + * Insantiate witness from an array of buffers. + * @param {Buffer[]} items + * @returns {Witness} + */ + + static fromArray(items) { + return new this().fromArray(items); + } + + /** + * Convert witness to an array of buffers. + * @returns {Buffer[]} + */ + + toItems() { + return this.items.slice(); + } + + /** + * Inject properties from an array of buffers. + * @private + * @param {Buffer[]} items + */ + + fromItems(items) { + assert(Array.isArray(items)); + this.items = items; + return this; + } + + /** + * Insantiate witness from an array of buffers. + * @param {Buffer[]} items + * @returns {Witness} + */ + + static fromItems(items) { + return new this().fromItems(items); + } + + /** + * Convert witness to a stack. + * @returns {Stack} + */ + + toStack() { + return new Stack(this.toArray()); + } + + /** + * Inject properties from a stack. + * @private + * @param {Stack} stack + */ + + fromStack(stack) { + return this.fromArray(stack.items); + } + + /** + * Insantiate witness from a stack. + * @param {Stack} stack + * @returns {Witness} + */ + + static fromStack(stack) { + return new this().fromStack(stack); + } + + /** + * Inspect a Witness object. + * @returns {String} Human-readable script. + */ + + inspect() { + return ``; + } + + /** + * Clone the witness object. + * @returns {Witness} A clone of the current witness object. + */ + + clone() { + return new this.constructor().inject(this); + } + + /** + * Inject properties from witness. + * Used for cloning. + * @private + * @param {Witness} witness + * @returns {Witness} + */ + + inject(witness) { + this.items = witness.items.slice(); + return this; + } + + /** + * Compile witness (NOP). + * @returns {Witness} + */ + + compile() { + return this; + } + + /** + * "Guess" the type of the witness. + * This method is not 100% reliable. + * @returns {ScriptType} + */ + + getInputType() { + if (this.isPubkeyhashInput()) + return scriptTypes.WITNESSPUBKEYHASH; + + if (this.isScripthashInput()) + return scriptTypes.WITNESSSCRIPTHASH; + + return scriptTypes.NONSTANDARD; + } + + /** + * "Guess" the address of the witness. + * This method is not 100% reliable. + * @returns {Address|null} + */ + + getInputAddress() { + return Address.fromWitness(this); + } + + /** + * "Test" whether the witness is a pubkey input. + * Always returns false. + * @returns {Boolean} + */ + + isPubkeyInput() { + return false; + } + + /** + * Get P2PK signature if present. + * Always returns null. + * @returns {Buffer|null} + */ + + getPubkeyInput() { + return null; + } + + /** + * "Guess" whether the witness is a pubkeyhash input. + * This method is not 100% reliable. + * @returns {Boolean} + */ + + isPubkeyhashInput() { + return this.items.length === 2 + && common.isSignatureEncoding(this.items[0]) + && common.isKeyEncoding(this.items[1]); + } + + /** + * Get P2PKH signature and key if present. + * @returns {Array} [sig, key] + */ + + getPubkeyhashInput() { + if (!this.isPubkeyhashInput()) + return [null, null]; + return [this.items[0], this.items[1]]; + } + + /** + * "Test" whether the witness is a multisig input. + * Always returns false. + * @returns {Boolean} + */ + + isMultisigInput() { + return false; + } + + /** + * Get multisig signatures key if present. + * Always returns null. + * @returns {Buffer[]|null} + */ + + getMultisigInput() { + return null; + } + + /** + * "Guess" whether the witness is a scripthash input. + * This method is not 100% reliable. + * @returns {Boolean} + */ + + isScripthashInput() { + return this.items.length > 0 && !this.isPubkeyhashInput(); + } + + /** + * Get P2SH redeem script if present. + * @returns {Buffer|null} + */ + + getScripthashInput() { + if (!this.isScripthashInput()) + return null; + return this.items[this.items.length - 1]; + } + + /** + * "Guess" whether the witness is an unknown/non-standard type. + * This method is not 100% reliable. + * @returns {Boolean} + */ + + isUnknownInput() { + return this.getInputType() === scriptTypes.NONSTANDARD; + } + + /** + * Test the witness against a bloom filter. + * @param {Bloom} filter + * @returns {Boolean} + */ + + test(filter) { + for (const item of this.items) { + if (item.length === 0) + continue; + + if (filter.test(item)) + return true; + } + + return false; + } + + /** + * Grab and deserialize the redeem script from the witness. + * @returns {Script} Redeem script. + */ + + getRedeem() { + if (this.items.length === 0) + return null; + + const redeem = this.items[this.items.length - 1]; + + if (!redeem) + return null; + + return Script.fromRaw(redeem); + } + + /** + * Find a data element in a witness. + * @param {Buffer} data - Data element to match against. + * @returns {Number} Index (`-1` if not present). + */ + + indexOf(data) { + for (let i = 0; i < this.items.length; i++) { + const item = this.items[i]; + if (item.equals(data)) + return i; + } + return -1; + } + + /** + * Calculate size of the witness + * excluding the varint size bytes. + * @returns {Number} + */ + + getSize() { + let size = 0; + + for (const item of this.items) + size += encoding.sizeVarBytes(item); + + return size; + } + + /** + * Calculate size of the witness + * including the varint size bytes. + * @returns {Number} + */ + + getVarSize() { + return encoding.sizeVarint(this.items.length) + this.getSize(); + } + + /** + * Write witness to a buffer writer. + * @param {BufferWriter} bw + */ + + toWriter(bw) { + bw.writeVarint(this.items.length); + + for (const item of this.items) + bw.writeVarBytes(item); + + return bw; + } + + /** + * Encode the witness to a Buffer. + * @param {String} enc - Encoding, either `'hex'` or `null`. + * @returns {Buffer|String} Serialized script. + */ + + toRaw() { + const size = this.getVarSize(); + return this.toWriter(bio.write(size)).render(); + } + + /** + * Convert witness to a hex string. + * @returns {String} + */ + + toJSON() { + return this.toRaw().toString('hex'); + } + + /** + * Inject properties from json object. + * @private + * @param {String} json + */ + + fromJSON(json) { + assert(typeof json === 'string', 'Witness must be a string.'); + return this.fromRaw(Buffer.from(json, 'hex')); + } + + /** + * Insantiate witness from a hex string. + * @param {String} json + * @returns {Witness} + */ + + static fromJSON(json) { + return new this().fromJSON(json); + } + + /** + * Inject properties from buffer reader. + * @private + * @param {BufferReader} br + */ + + fromReader(br) { + const count = br.readVarint(); + + for (let i = 0; i < count; i++) + this.items.push(br.readVarBytes()); + + return this; + } + + /** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + + fromRaw(data) { + return this.fromReader(bio.read(data)); + } + + /** + * Create a witness from a buffer reader. + * @param {BufferReader} br + */ + + static fromReader(br) { + return new this().fromReader(br); + } + + /** + * Create a witness from a serialized buffer. + * @param {Buffer|String} data - Serialized witness. + * @param {String?} enc - Either `"hex"` or `null`. + * @returns {Witness} + */ + + static fromRaw(data, enc) { + if (typeof data === 'string') + data = Buffer.from(data, enc); + return new this().fromRaw(data); + } + + /** + * Inject items from string. + * @private + * @param {String|String[]} items + */ + + fromString(items) { + if (!Array.isArray(items)) { + assert(typeof items === 'string'); + + items = items.trim(); + + if (items.length === 0) + return this; + + items = items.split(/\s+/); + } + + for (const item of items) + this.items.push(Buffer.from(item, 'hex')); + + return this; + } + + /** + * Parse a test script/array + * string into a witness object. _Must_ + * contain only stack items (no non-push + * opcodes). + * @param {String|String[]} items - Script string. + * @returns {Witness} + * @throws Parse error. + */ + + static fromString(items) { + return new this().fromString(items); + } + + /** + * Test an object to see if it is a Witness. + * @param {Object} obj + * @returns {Boolean} + */ + + static isWitness(obj) { + return obj instanceof Witness; + } } -Object.setPrototypeOf(Witness.prototype, Stack.prototype); - -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -Witness.prototype.fromOptions = function fromOptions(options) { - assert(options, 'Witness data is required.'); - - if (Array.isArray(options)) - return this.fromArray(options); - - if (options.items) - return this.fromArray(options.items); - - return this; -}; - -/** - * Instantiate witness from options. - * @param {Object} options - * @returns {Witness} - */ - -Witness.fromOptions = function fromOptions(options) { - return new Witness().fromOptions(options); -}; - -/** - * Convert witness to an array of buffers. - * @returns {Buffer[]} - */ - -Witness.prototype.toArray = function toArray() { - return this.items.slice(); -}; - -/** - * Inject properties from an array of buffers. - * @private - * @param {Buffer[]} items - */ - -Witness.prototype.fromArray = function fromArray(items) { - assert(Array.isArray(items)); - this.items = items; - return this; -}; - -/** - * Insantiate witness from an array of buffers. - * @param {Buffer[]} items - * @returns {Witness} - */ - -Witness.fromArray = function fromArray(items) { - return new Witness().fromArray(items); -}; - -/** - * Convert witness to an array of buffers. - * @returns {Buffer[]} - */ - -Witness.prototype.toItems = function toItems() { - return this.items.slice(); -}; - -/** - * Inject properties from an array of buffers. - * @private - * @param {Buffer[]} items - */ - -Witness.prototype.fromItems = function fromItems(items) { - assert(Array.isArray(items)); - this.items = items; - return this; -}; - -/** - * Insantiate witness from an array of buffers. - * @param {Buffer[]} items - * @returns {Witness} - */ - -Witness.fromItems = function fromItems(items) { - return new Witness().fromItems(items); -}; - -/** - * Convert witness to a stack. - * @returns {Stack} - */ - -Witness.prototype.toStack = function toStack() { - return new Stack(this.toArray()); -}; - -/** - * Inject properties from a stack. - * @private - * @param {Stack} stack - */ - -Witness.prototype.fromStack = function fromStack(stack) { - return this.fromArray(stack.items); -}; - -/** - * Insantiate witness from a stack. - * @param {Stack} stack - * @returns {Witness} - */ - -Witness.fromStack = function fromStack(stack) { - return new Witness().fromStack(stack); -}; - -/** - * Inspect a Witness object. - * @returns {String} Human-readable script. - */ - -Witness.prototype.inspect = function inspect() { - return ``; -}; - -/** - * Clone the witness object. - * @returns {Witness} A clone of the current witness object. - */ - -Witness.prototype.clone = function clone() { - return new Witness().inject(this); -}; - -/** - * Inject properties from witness. - * Used for cloning. - * @private - * @param {Witness} witness - * @returns {Witness} - */ - -Witness.prototype.inject = function inject(witness) { - this.items = witness.items.slice(); - return this; -}; - -/** - * Compile witness (NOP). - * @returns {Witness} - */ - -Witness.prototype.compile = function compile() { - return this; -}; - -/** - * "Guess" the type of the witness. - * This method is not 100% reliable. - * @returns {ScriptType} - */ - -Witness.prototype.getInputType = function getInputType() { - if (this.isPubkeyhashInput()) - return scriptTypes.WITNESSPUBKEYHASH; - - if (this.isScripthashInput()) - return scriptTypes.WITNESSSCRIPTHASH; - - return scriptTypes.NONSTANDARD; -}; - -/** - * "Guess" the address of the witness. - * This method is not 100% reliable. - * @returns {Address|null} - */ - -Witness.prototype.getInputAddress = function getInputAddress() { - return Address.fromWitness(this); -}; - -/** - * "Test" whether the witness is a pubkey input. - * Always returns false. - * @returns {Boolean} - */ - -Witness.prototype.isPubkeyInput = function isPubkeyInput() { - return false; -}; - -/** - * Get P2PK signature if present. - * Always returns null. - * @returns {Buffer|null} - */ - -Witness.prototype.getPubkeyInput = function getPubkeyInput() { - return null; -}; - -/** - * "Guess" whether the witness is a pubkeyhash input. - * This method is not 100% reliable. - * @returns {Boolean} - */ - -Witness.prototype.isPubkeyhashInput = function isPubkeyhashInput() { - return this.items.length === 2 - && common.isSignatureEncoding(this.items[0]) - && common.isKeyEncoding(this.items[1]); -}; - -/** - * Get P2PKH signature and key if present. - * @returns {Array} [sig, key] - */ - -Witness.prototype.getPubkeyhashInput = function getPubkeyhashInput() { - if (!this.isPubkeyhashInput()) - return [null, null]; - return [this.items[0], this.items[1]]; -}; - -/** - * "Test" whether the witness is a multisig input. - * Always returns false. - * @returns {Boolean} - */ - -Witness.prototype.isMultisigInput = function isMultisigInput() { - return false; -}; - -/** - * Get multisig signatures key if present. - * Always returns null. - * @returns {Buffer[]|null} - */ - -Witness.prototype.getMultisigInput = function getMultisigInput() { - return null; -}; - -/** - * "Guess" whether the witness is a scripthash input. - * This method is not 100% reliable. - * @returns {Boolean} - */ - -Witness.prototype.isScripthashInput = function isScripthashInput() { - return this.items.length > 0 && !this.isPubkeyhashInput(); -}; - -/** - * Get P2SH redeem script if present. - * @returns {Buffer|null} - */ - -Witness.prototype.getScripthashInput = function getScripthashInput() { - if (!this.isScripthashInput()) - return null; - return this.items[this.items.length - 1]; -}; - -/** - * "Guess" whether the witness is an unknown/non-standard type. - * This method is not 100% reliable. - * @returns {Boolean} - */ - -Witness.prototype.isUnknownInput = function isUnknownInput() { - return this.getInputType() === scriptTypes.NONSTANDARD; -}; - -/** - * Test the witness against a bloom filter. - * @param {Bloom} filter - * @returns {Boolean} - */ - -Witness.prototype.test = function test(filter) { - for (const item of this.items) { - if (item.length === 0) - continue; - - if (filter.test(item)) - return true; - } - - return false; -}; - -/** - * Grab and deserialize the redeem script from the witness. - * @returns {Script} Redeem script. - */ - -Witness.prototype.getRedeem = function getRedeem() { - if (this.items.length === 0) - return null; - - const redeem = this.items[this.items.length - 1]; - - if (!redeem) - return null; - - return Script.fromRaw(redeem); -}; - -/** - * Find a data element in a witness. - * @param {Buffer} data - Data element to match against. - * @returns {Number} Index (`-1` if not present). - */ - -Witness.prototype.indexOf = function indexOf(data) { - for (let i = 0; i < this.items.length; i++) { - const item = this.items[i]; - if (item.equals(data)) - return i; - } - return -1; -}; - -/** - * Calculate size of the witness - * excluding the varint size bytes. - * @returns {Number} - */ - -Witness.prototype.getSize = function getSize() { - let size = 0; - - for (const item of this.items) - size += encoding.sizeVarBytes(item); - - return size; -}; - -/** - * Calculate size of the witness - * including the varint size bytes. - * @returns {Number} - */ - -Witness.prototype.getVarSize = function getVarSize() { - return encoding.sizeVarint(this.items.length) + this.getSize(); -}; - -/** - * Write witness to a buffer writer. - * @param {BufferWriter} bw - */ - -Witness.prototype.toWriter = function toWriter(bw) { - bw.writeVarint(this.items.length); - - for (const item of this.items) - bw.writeVarBytes(item); - - return bw; -}; - -/** - * Encode the witness to a Buffer. - * @param {String} enc - Encoding, either `'hex'` or `null`. - * @returns {Buffer|String} Serialized script. - */ - -Witness.prototype.toRaw = function toRaw() { - const size = this.getVarSize(); - return this.toWriter(bio.write(size)).render(); -}; - -/** - * Convert witness to a hex string. - * @returns {String} - */ - -Witness.prototype.toJSON = function toJSON() { - return this.toRaw().toString('hex'); -}; - -/** - * Inject properties from json object. - * @private - * @param {String} json - */ - -Witness.prototype.fromJSON = function fromJSON(json) { - assert(typeof json === 'string', 'Witness must be a string.'); - return this.fromRaw(Buffer.from(json, 'hex')); -}; - -/** - * Insantiate witness from a hex string. - * @param {String} json - * @returns {Witness} - */ - -Witness.fromJSON = function fromJSON(json) { - return new Witness().fromJSON(json); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - */ - -Witness.prototype.fromReader = function fromReader(br) { - const count = br.readVarint(); - - for (let i = 0; i < count; i++) - this.items.push(br.readVarBytes()); - - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - */ - -Witness.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(bio.read(data)); -}; - -/** - * Create a witness from a buffer reader. - * @param {BufferReader} br - */ - -Witness.fromReader = function fromReader(br) { - return new Witness().fromReader(br); -}; - -/** - * Create a witness from a serialized buffer. - * @param {Buffer|String} data - Serialized witness. - * @param {String?} enc - Either `"hex"` or `null`. - * @returns {Witness} - */ - -Witness.fromRaw = function fromRaw(data, enc) { - if (typeof data === 'string') - data = Buffer.from(data, enc); - return new Witness().fromRaw(data); -}; - -/** - * Inject items from string. - * @private - * @param {String|String[]} items - */ - -Witness.prototype.fromString = function fromString(items) { - if (!Array.isArray(items)) { - assert(typeof items === 'string'); - - items = items.trim(); - - if (items.length === 0) - return this; - - items = items.split(/\s+/); - } - - for (const item of items) - this.items.push(Buffer.from(item, 'hex')); - - return this; -}; - -/** - * Parse a test script/array - * string into a witness object. _Must_ - * contain only stack items (no non-push - * opcodes). - * @param {String|String[]} items - Script string. - * @returns {Witness} - * @throws Parse error. - */ - -Witness.fromString = function fromString(items) { - return new Witness().fromString(items); -}; - -/** - * Test an object to see if it is a Witness. - * @param {Object} obj - * @returns {Boolean} - */ - -Witness.isWitness = function isWitness(obj) { - return obj instanceof Witness; -}; - /* * Expose */