From bf070233699a2dd46e56aba7479086fa80f6ed7d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 8 Dec 2016 18:36:12 -0800 Subject: [PATCH] script: add cscriptnum implementation. --- lib/script/scriptnum.js | 420 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 lib/script/scriptnum.js diff --git a/lib/script/scriptnum.js b/lib/script/scriptnum.js new file mode 100644 index 00000000..e2a62473 --- /dev/null +++ b/lib/script/scriptnum.js @@ -0,0 +1,420 @@ +/*! + * scriptnum.js - script number for bcoin + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var assert = require('assert'); +var util = require('../utils/util'); +var ScriptError = require('../btc/errors').ScriptError; +var constants = require('../protocol/constants'); +var STACK_FALSE = new Buffer(0); + +/** + * ScriptNum + * @constructor + * @param {Number} value + */ + +function ScriptNum(value) { + if (!(this instanceof ScriptNum)) + return new ScriptNum(value); + + assert(!value || value <= 0xffffffffffff, 'Number exceeds 2^48-1.'); + + this.value = value || 0; +} + +ScriptNum.prototype.clone = function clone() { + return new ScriptNum(this.value); +}; + +ScriptNum.prototype.add = function add(num) { + return this.clone().iadd(num); +}; + +ScriptNum.prototype.sub = function sub(num) { + return this.clone().isub(num); +}; + +ScriptNum.prototype.mul = function mul(num) { + return this.clone().imul(num); +}; + +ScriptNum.prototype.div = function div(num) { + return this.clone().idiv(num); +}; + +ScriptNum.prototype.iadd = function iadd(num) { + return this.iaddn(num.value); +}; + +ScriptNum.prototype.isub = function isub(num) { + return this.isubn(num.value); +}; + +ScriptNum.prototype.imul = function imul(num) { + return this.imuln(num.value); +}; + +ScriptNum.prototype.idiv = function idiv(num) { + return this.idivn(num.value); +}; + +ScriptNum.prototype.addn = function addn(value) { + return this.clone().iaddn(value); +}; + +ScriptNum.prototype.subn = function subn(value) { + return this.clone().isubn(value); +}; + +ScriptNum.prototype.muln = function muln(value) { + return this.clone().imuln(value); +}; + +ScriptNum.prototype.divn = function divn(value) { + return this.clone().idivn(value); +}; + +ScriptNum.prototype.ushln = function ushln(value) { + return this.clone().iushln(value); +}; + +ScriptNum.prototype.ushrn = function ushrn(value) { + return this.clone().iushrn(value); +}; + +ScriptNum.prototype.iaddn = function addn(value) { + this.value += value; + return this; +}; + +ScriptNum.prototype.isubn = function subn(value) { + this.value -= value; + return this; +}; + +ScriptNum.prototype.imuln = function muln(value) { + assert(false); + this.value *= value; + return this; +}; + +ScriptNum.prototype.idivn = function divn(value) { + this.value = Math.floor(this.value / value); + return this; +}; + +ScriptNum.prototype.iushln = function iushln(value) { + assert(value === 1); + this.value *= Math.pow(2, value); + return this; +}; + +ScriptNum.prototype.iushrn = function iushrn(value) { + this.value = Math.floor(this.value / Math.pow(2, value)); + return this; +}; + +ScriptNum.prototype.cmp = function cmp(num) { + return this.cmpn(num.value); +}; + +ScriptNum.prototype.cmpn = function cmpn(value) { + if (this.value === value) + return 0; + return this.value < value ? -1 : 1; +}; + +ScriptNum.prototype.neg = function neg() { + return this.clone().ineg(); +}; + +ScriptNum.prototype.ineg = function ineg() { + this.value = -this.value; + return this; +}; + +ScriptNum.prototype.toNumber = function toNumber() { + return this.value; +}; + +ScriptNum.prototype.fromString = function fromString(str, base) { + var nonzero = 0; + var neg = false; + var i, ch; + + if (str[0] === '-') { + assert(str.length > 1, 'Non-numeric string passed.'); + str = str.substring(1); + neg = true; + } else { + assert(str.length > 0, 'Non-numeric string passed.'); + } + + this.value = 0; + + if (base === 10 || base === 'dec') { + for (i = 0; i < str.length; i++) { + ch = str[i]; + + if (nonzero === 0 && ch === '0') + continue; + + if (!(ch >= '0' && ch <= '9')) + throw new Error('Parse error.'); + + ch = ch.charCodeAt(0) - 48; + + nonzero++; + assert(nonzero <= 15, 'Number exceeds 2^48-1.'); + + this.value *= 10; + this.value += ch; + } + + if (neg) + this.value = -this.value; + + return this; + } + + if (base === 16 || base === 'hex') { + for (i = 0; i < str.length; i++) { + ch = str[i]; + + if (nonzero === 0 && ch === '0') + continue; + + if (ch >= '0' && ch <= '9') { + ch = ch.charCodeAt(0); + ch -= 48; + } else if (ch >= 'a' && ch <= 'f') { + ch = ch.charCodeAt(0); + ch -= 87; + } else if (ch >= 'A' && ch <= 'F') { + ch = ch.charCodeAt(0); + ch -= 55; + } else { + throw new Error('Parse error.'); + } + + nonzero++; + assert(nonzero <= 12, 'Number exceeds 2^48-1.'); + + this.value *= 16; + this.value += ch; + } + + if (neg) + this.value = -this.value; + + return this; + } + + assert(false, 'Base ' + base + ' not supported.'); +}; + +ScriptNum.fromString = function fromString(str, base) { + return new ScriptNum(0).fromString(str, base); +}; + +ScriptNum.prototype.fromRaw = function fromRaw(value, flags, size) { + var sign; + + assert(Buffer.isBuffer(value)); + + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; + + if (size == null) + size = 4; + + assert(size <= 6, 'Number exceeds 48 bits.'); + + if (value.length === 0) { + this.value = 0; + return this; + } + + if (value.length > size) + throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); + + if (flags & constants.flags.VERIFY_MINIMALDATA) { + // If the low bits on the last byte are unset, + // fail if the value's second to last byte does + // not have the high bit set. A number can't + // justify having the last byte's low bits unset + // unless they ran out of space for the sign bit + // in the second to last bit. We also fail on [0] + // to avoid negative zero (also avoids positive + // zero). + if ((value[value.length - 1] & 0x7f) === 0) { + if (value.length === 1 || !(value[value.length - 2] & 0x80)) { + throw new ScriptError( + 'UNKNOWN_ERROR', + 'Non-minimally encoded Script number.'); + } + } + } + + this.value = 0; + + switch (value.length) { + case 6: + this.value += value[5] * 0x10000000000; + case 5: + this.value += value[4] * 0x100000000; + case 4: + this.value += value[3] * 0x1000000; + case 3: + this.value += value[2] * 0x10000; + case 2: + this.value += value[1] * 0x100; + case 1: + this.value += value[0]; + break; + default: + assert(false); + break; + } + + // If the input vector's most significant byte is + // 0x80, remove it from the result's msb and return + // a negative. + // Equivalent to: + // -(result & ~(0x80 << (8 * (value.length - 1)))) + if (value[value.length - 1] & 0x80) { + switch (value.length) { + case 1: + case 2: + case 3: + case 4: + sign = 0x80 << (8 * (value.length - 1)); + this.value &= ~sign; + this.value = -this.value; + break; + case 5: + case 6: + sign = 0x80 * Math.pow(2, 8 * (value.length - 1)); + if (this.value >= sign) + this.value -= sign; + this.value = -this.value; + break; + default: + assert(false); + break; + } + } + + return this; +}; + +ScriptNum.fromRaw = function fromRaw(value, flags, size) { + return new ScriptNum(0).fromRaw(value, flags, size); +}; + +ScriptNum.prototype.toRaw = function toRaw() { + var value = this.value; + var neg = false; + var result, offset, size; + + if (value === 0) + return STACK_FALSE; + + // If the most significant byte is >= 0x80 + // and the value is positive, push a new + // zero-byte to make the significant + // byte < 0x80 again. + + // If the most significant byte is >= 0x80 + // and the value is negative, push a new + // 0x80 byte that will be popped off when + // converting to an integral. + + // If the most significant byte is < 0x80 + // and the value is negative, add 0x80 to + // it, since it will be subtracted and + // interpreted as a negative when + // converting to an integral. + + if (value < 0) { + neg = true; + value = -value; + } + + if (value <= 0xff) { + offset = (value & 0x80) ? 1 : 0; + size = 1; + } else if (value <= 0xffff) { + offset = (value & 0x8000) ? 1 : 0; + size = 2; + } else if (value <= 0xffffff) { + offset = (value & 0x800000) ? 1 : 0; + size = 3; + } else if (value <= 0xffffffff) { + offset = (value & 0x80000000) ? 1 : 0; + size = 4; + } else if (value <= 0xffffffffff) { + offset = value >= 0x8000000000 ? 1 : 0; + size = 5; + } else if (value <= 0xffffffffffff) { + offset = value >= 0x800000000000 ? 1 : 0; + size = 6; + } else { + throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); + } + + result = new Buffer(size + offset); + + switch (size) { + case 6: + result[5] = (value / 0x10000000000 | 0) & 0xff; + case 5: + result[4] = (value / 0x100000000 | 0) & 0xff; + case 4: + result[3] = (value >>> 24) & 0xff; + case 3: + result[2] = (value >> 16) & 0xff; + case 2: + result[1] = (value >> 8) & 0xff; + case 1: + result[0] = value & 0xff; + break; + default: + assert(false); + break; + } + + if (result[size - 1] & 0x80) + result[result.length - 1] = neg ? 0x80 : 0; + else if (neg) + result[size - 1] |= 0x80; + + return result; +}; + +/* + * Helpers + */ + +function isDecimal(obj) { + return typeof obj === 'string' + && obj.length > 0 + && /^\-?[0-9]+$/i.test(obj); +} + +function isHex(obj) { + return typeof obj === 'string' + && obj.length > 0 + && /^\-?[0-9a-f]+$/i.test(obj); +} + +/* + * Expose + */ + +module.exports = ScriptNum;