UNPKG

hsd

Version:
251 lines (203 loc) 5 kB
/*! * scriptnum.js - script number object for hsd. * Copyright (c) 2017, Christopher Jeffrey (MIT License). * https://github.com/handshake-org/hsd */ 'use strict'; const assert = require('bsert'); const {I64} = require('n64'); const ScriptError = require('./scripterror'); /* * Constants */ const EMPTY_ARRAY = Buffer.alloc(0); /** * Script Number * @see https://github.com/chjj/n64 * @alias module:script.ScriptNum * @property {Number} hi * @property {Number} lo * @property {Number} sign */ class ScriptNum extends I64 { /** * Create a script number. * @constructor * @param {(Number|String|Buffer|Object)?} [num] * @param {(String|Number)?} [base] */ constructor(num, base) { super(num, base); } /** * Cast to int32. * @returns {Number} */ 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} */ encode() { 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; } /** * Instantiate script number from serialized data. * @private * @param {Buffer} data * @returns {ScriptNum} */ _decode(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(); } return this; } /** * Decode and verify script number. * @private * @param {Buffer} data * @param {Boolean?} [minimal] - Require minimal encoding. * @param {Number?} [limit] - Size limit. * @returns {ScriptNum} */ decode(data, minimal, limit) { assert(Buffer.isBuffer(data)); if (limit != null && data.length > limit) throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); if (minimal && !ScriptNum.isMinimal(data)) throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.'); return this._decode(data); } /** * Inspect script number. * @returns {String} */ inspect() { return `<ScriptNum: ${this.toString(10)}>`; } /** * Test wether a serialized script * number is in its most minimal form. * @param {Buffer} data * @returns {Boolean} */ static isMinimal(data) { assert(Buffer.isBuffer(data)); 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} */ static decode(data, minimal, limit) { return new this().decode(data, minimal, limit); } /** * Test whether object is a script number. * @param {Object} obj * @returns {Boolean} */ static isScriptNum(obj) { return obj instanceof ScriptNum; } } /* * Expose */ module.exports = ScriptNum;