260 lines
5.0 KiB
JavaScript
260 lines
5.0 KiB
JavaScript
/*!
|
|
* scriptnum.js - script number object for bcoin.
|
|
* Copyright (c) 2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
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
|
|
* @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);
|
|
|
|
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;
|
|
}
|
|
|
|
// 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}
|
|
*/
|
|
|
|
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();
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Serialize script number.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
ScriptNum.prototype.encode = function encode() {
|
|
return this.toRaw();
|
|
};
|
|
|
|
/**
|
|
* Decode and verify script number.
|
|
* @private
|
|
* @param {Buffer} data
|
|
* @param {Boolean?} minimal - Require minimal encoding.
|
|
* @param {Number?} limit - Size limit.
|
|
* @returns {ScriptNum}
|
|
*/
|
|
|
|
ScriptNum.prototype.decode = function 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.fromRaw(data);
|
|
};
|
|
|
|
/**
|
|
* Inspect script number.
|
|
* @returns {String}
|
|
*/
|
|
|
|
ScriptNum.prototype.inspect = function inspect() {
|
|
return `<ScriptNum: ${this.toString(10)}>`;
|
|
};
|
|
|
|
/**
|
|
* Test wether a serialized script
|
|
* number is in its most minimal form.
|
|
* @param {Buffer} data
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
ScriptNum.isMinimal = function 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}
|
|
*/
|
|
|
|
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}
|
|
*/
|
|
|
|
ScriptNum.isScriptNum = function isScriptNum(obj) {
|
|
return obj instanceof ScriptNum;
|
|
};
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = ScriptNum;
|