/*! * stack.js - stack object for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var bcoin = require('../env'); var constants = bcoin.constants; var opcodes = constants.opcodes; var ScriptError = bcoin.errors.ScriptError; /** * Represents the stack of a Script during execution. * @exports 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); this.items = items || []; } Stack.prototype.__defineGetter__('length', function() { return this.items.length; }); Stack.prototype.__defineSetter__('length', function(length) { return this.items.length = length; }); /** * 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() { return bcoin.witness.format(this.items); }; /** * 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) { return bcoin.script.formatASM(this.items, decode); }; /** * Pop the redeem script off the stack and deserialize it. * @returns {Script|null} The redeem script. */ Stack.prototype.getRedeem = function getRedeem() { var redeem = this.items[this.items.length - 1]; if (!redeem) return; return new bcoin.script(redeem); }; /** * Clone the stack. * @returns {Stack} Cloned stack. */ Stack.prototype.clone = function clone() { return new Stack(this.items.slice()); }; /** * Get total size of the stack, including the alt stack. * @param {Array} alt - Alt stack. * @returns {Number} */ Stack.prototype.getSize = function getSize(alt) { return this.items.length + alt.length; }; /** * Push item onto stack. * @see Array#push * @param {Buffer} item * @returns {Number} Stack size. */ Stack.prototype.push = function push(item) { return this.items.push(item); }; /** * Unshift item from stack. * @see Array#unshift * @param {Buffer} item * @returns {Number} */ Stack.prototype.unshift = function unshift(item) { return this.items.unshift(item); }; /** * Slice out part of the stack items. * @param {Number} start * @param {Number} end * @see Array#slice * @returns {Buffer[]} */ Stack.prototype.slice = function slice(start, end) { return this.items.slice(start, end); }; /** * Splice stack items. * @see Array#splice * @param {Number} index * @param {Number} remove * @param {Buffer?} insert * @returns {Buffer[]} */ Stack.prototype.splice = function splice(i, remove, insert) { if (insert === undefined) return this.items.splice(i, remove); return this.items.splice(i, remove, insert); }; /** * Pop a stack item. * @see Array#pop * @returns {Buffer|null} */ Stack.prototype.pop = function pop() { return this.items.pop(); }; /** * Shift a stack item. * @see Array#shift * @returns {Buffer|null} */ Stack.prototype.shift = function shift() { return this.items.shift(); }; /** * Get a stack item by index. * @param {Number} index * @returns {Buffer|null} */ Stack.prototype.get = function get(i) { return this.items[i]; }; /** * Get a stack item relative to * the top of the stack. * @example * stack.top(-1); * @param {Number} index * @returns {Buffer|null} */ Stack.prototype.top = function top(i) { return this.items[this.items.length + i]; }; /** * Clear the stack. */ Stack.prototype.clear = function clear() { this.items.length = 0; }; /** * Set stack item at index. * @param {Number} index * @param {Buffer} value * @returns {Buffer} */ Stack.prototype.set = function set(i, value) { return this.items[i] = value; }; /** * Swap stack values. * @private * @param {Number} i1 - Index 1. * @param {Number} i2 - Index 2. */ Stack.prototype._swap = function _swap(i1, i2) { var v1, v2; i1 = this.items.length + i1; i2 = this.items.length + i2; v1 = this.items[i1]; v2 = this.items[i2]; this.items[i1] = v2; this.items[i2] = v1; }; /** * Perform the OP_TOALTSTACK operation. * @param {Array} alt - Alt stack. * @throws {ScriptError} */ Stack.prototype.toalt = function toalt(alt) { if (this.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_TOALTSTACK); alt.push(this.pop()); }; /** * Perform the OP_FROMALTSTACK operation. * @param {Array} alt - Alt stack. * @throws {ScriptError} */ Stack.prototype.fromalt = function fromalt(alt) { if (alt.length === 0) throw new ScriptError('INVALID_ALTSTACK_OPERATION', opcodes.OP_FROMALTSTACK); this.push(alt.pop()); }; /** * Perform the OP_IFDUP operation. * @throws {ScriptError} */ Stack.prototype.ifdup = function ifdup() { if (this.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_IFDUP); if (bcoin.script.bool(this.top(-1))) this.push(this.top(-1)); }; /** * Perform the OP_DEPTH operation. * @throws {ScriptError} */ Stack.prototype.depth = function depth() { this.push(bcoin.script.array(this.length)); }; /** * Perform the OP_DROP operation. * @throws {ScriptError} */ Stack.prototype.drop = function drop() { if (this.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_DROP); this.pop(); }; /** * Perform the OP_DUP operation. * @throws {ScriptError} */ Stack.prototype.dup = function dup() { if (this.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_DUP); this.push(this.top(-1)); }; /** * Perform the OP_NIP operation. * @throws {ScriptError} */ Stack.prototype.nip = function nip() { if (this.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_NIP); this.splice(this.length - 2, 1); }; /** * Perform the OP_OVER operation. * @throws {ScriptError} */ Stack.prototype.over = function over() { if (this.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_OVER); this.push(this.top(-2)); }; /** * Perform the OP_PICK operation. * @param {VerifyFlags} flags * @throws {ScriptError} */ Stack.prototype.pick = function pick(flags) { return this._pickroll(opcodes.OP_PICK, flags); }; /** * Perform the OP_ROLL operation. * @param {VerifyFlags} flags * @throws {ScriptError} */ Stack.prototype.roll = function roll(flags) { return this._pickroll(opcodes.OP_ROLL, flags); }; /** * Perform a pick or roll. * @private * @param {Number} op * @param {VerifyFlags} flags * @throws {ScriptError} */ Stack.prototype._pickroll = function pickroll(op, flags) { var val, n; if (this.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', op); val = this.pop(); n = bcoin.script.num(val, flags).toNumber(); if (n < 0 || n >= this.length) throw new ScriptError('INVALID_STACK_OPERATION', op); val = this.top(-n - 1); if (op === opcodes.OP_ROLL) this.splice(this.length - n - 1, 1); this.push(val); }; /** * Perform the OP_ROT operation. * @throws {ScriptError} */ Stack.prototype.rot = function rot() { if (this.length < 3) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_ROT); this._swap(-3, -2); this._swap(-2, -1); }; /** * Perform the OP_SWAP operation. * @throws {ScriptError} */ Stack.prototype.swap = function swap() { if (this.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_SWAP); this._swap(-2, -1); }; /** * Perform the OP_TUCK operation. * @throws {ScriptError} */ Stack.prototype.tuck = function tuck() { if (this.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_TUCK); this.splice(this.length - 2, 0, this.top(-1)); }; /** * Perform the OP_2DROP operation. * @throws {ScriptError} */ Stack.prototype.drop2 = function drop2() { if (this.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2DROP); this.pop(); this.pop(); }; /** * Perform the OP_2DUP operation. * @throws {ScriptError} */ Stack.prototype.dup2 = function dup2() { var v1, v2; if (this.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2DUP); v1 = this.top(-2); v2 = this.top(-1); this.push(v1); this.push(v2); }; /** * Perform the OP_3DUP operation. * @throws {ScriptError} */ Stack.prototype.dup3 = function dup3() { var v1, v2, v3; if (this.length < 3) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_3DUP); v1 = this.top(-3); v2 = this.top(-2); v3 = this.top(-1); this.push(v1); this.push(v2); this.push(v3); }; /** * Perform the OP_2OVER operation. * @throws {ScriptError} */ Stack.prototype.over2 = function over2() { var v1, v2; if (this.length < 4) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2OVER); v1 = this.top(-4); v2 = this.top(-3); this.push(v1); this.push(v2); }; /** * Perform the OP_2ROT operation. * @throws {ScriptError} */ Stack.prototype.rot2 = function rot2() { var v1, v2; if (this.length < 6) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2ROT); v1 = this.top(-6); v2 = this.top(-5); this.splice(this.length - 6, 2); this.push(v1); this.push(v2); }; /** * Perform the OP_2SWAP operation. * @throws {ScriptError} */ Stack.prototype.swap2 = function swap2() { if (this.length < 4) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2SWAP); this._swap(-4, -2); this._swap(-3, -1); }; /** * Perform the OP_SIZE operation. * @throws {ScriptError} */ Stack.prototype.size = function size() { if (this.length < 1) throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_SIZE); this.push(bcoin.script.array(this.top(-1).length)); }; /** * Test an object to see if it is a Stack. * @param {Object} obj * @returns {Boolean} */ Stack.isStack = function isStack(obj) { return obj && Array.isArray(obj.items) && typeof obj.swap2 === 'function'; }; /* * Expose */ module.exports = Stack;