script: a better scriptnum implementation.
This commit is contained in:
parent
1f5fd553de
commit
772025a4a1
@ -7,385 +7,179 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const ScriptError = require('./common').ScriptError;
|
const {I64} = require('../utils/int64');
|
||||||
|
const {ScriptError} = require('./common');
|
||||||
const EMPTY_ARRAY = Buffer.alloc(0);
|
const EMPTY_ARRAY = Buffer.alloc(0);
|
||||||
|
|
||||||
/**
|
function ScriptNum(num, base, limit) {
|
||||||
* ScriptNum
|
|
||||||
* @alias module:script.ScriptNum
|
|
||||||
* @constructor
|
|
||||||
* @ignore
|
|
||||||
* @param {Number} value
|
|
||||||
*/
|
|
||||||
|
|
||||||
function ScriptNum(value) {
|
|
||||||
if (!(this instanceof ScriptNum))
|
if (!(this instanceof ScriptNum))
|
||||||
return new ScriptNum(value);
|
return new ScriptNum(num, base, limit);
|
||||||
|
|
||||||
assert(!value || value <= 0xffffffffffff, 'Number exceeds 2^48-1.');
|
I64.call(this, num, base, limit);
|
||||||
|
|
||||||
this.value = value || 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptNum.prototype.clone = function clone() {
|
Object.setPrototypeOf(ScriptNum, I64);
|
||||||
return new ScriptNum(this.value);
|
Object.setPrototypeOf(ScriptNum.prototype, I64.prototype);
|
||||||
|
|
||||||
|
ScriptNum.prototype.toInt = function toInt() {
|
||||||
|
if (this.lt(I64.UINT32_MIN))
|
||||||
|
return I64.LONG_MIN;
|
||||||
|
|
||||||
|
if (this.gt(I64.UINT32_MAX))
|
||||||
|
return I64.LONG_MAX;
|
||||||
|
|
||||||
|
return this.lo;
|
||||||
};
|
};
|
||||||
|
|
||||||
ScriptNum.prototype.add = function add(num) {
|
ScriptNum.prototype.toRaw = function toRaw() {
|
||||||
return this.clone().iadd(num);
|
let num = this;
|
||||||
};
|
|
||||||
|
|
||||||
ScriptNum.prototype.sub = function sub(num) {
|
// Zeroes are always empty arrays.
|
||||||
return this.clone().isub(num);
|
if (num.isZero())
|
||||||
};
|
return EMPTY_ARRAY;
|
||||||
|
|
||||||
ScriptNum.prototype.mul = function mul(num) {
|
// Need to append sign bit.
|
||||||
return this.clone().imul(num);
|
let neg = false;
|
||||||
};
|
if (num.isNeg()) {
|
||||||
|
num = num.neg();
|
||||||
ScriptNum.prototype.div = function div(num) {
|
neg = true;
|
||||||
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 iaddn(value) {
|
|
||||||
this.value += value;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
ScriptNum.prototype.isubn = function isubn(value) {
|
|
||||||
this.value -= value;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
ScriptNum.prototype.imuln = function imuln(value) {
|
|
||||||
this.value *= value;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
ScriptNum.prototype.idivn = function idivn(value) {
|
|
||||||
this.value = Math.floor(this.value / value);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
ScriptNum.prototype.iushln = function iushln(value) {
|
|
||||||
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.toString = function toString(base) {
|
|
||||||
if (!base)
|
|
||||||
base = 10;
|
|
||||||
|
|
||||||
if (base === 10 || base === 'dec')
|
|
||||||
return this.value.toString(10);
|
|
||||||
|
|
||||||
if (base === 16 || base === 'hex') {
|
|
||||||
let str = this.value.toString(16);
|
|
||||||
if (str.length % 2 !== 0)
|
|
||||||
str = '0' + str;
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Base ${base} not supported.`);
|
// Calculate size.
|
||||||
};
|
const size = num.byteLength();
|
||||||
|
|
||||||
ScriptNum.prototype.toJSON = function toJSON() {
|
let offset = 0;
|
||||||
return this.toString(16);
|
|
||||||
};
|
|
||||||
|
|
||||||
ScriptNum.prototype.fromString = function fromString(str, base) {
|
if (num.testn((size * 8) - 1))
|
||||||
if (!base)
|
offset = 1;
|
||||||
base = 10;
|
|
||||||
|
|
||||||
let negative = false;
|
// Write number.
|
||||||
|
const data = Buffer.allocUnsafe(size + offset);
|
||||||
|
|
||||||
if (str[0] === '-') {
|
switch (size) {
|
||||||
assert(str.length > 1, 'Non-numeric string passed.');
|
case 8:
|
||||||
str = str.substring(1);
|
data[7] = (num.hi >>> 24) & 0xff;
|
||||||
negative = true;
|
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 {
|
} else {
|
||||||
assert(str.length > 0, 'Non-numeric string passed.');
|
assert(offset === 0);
|
||||||
|
assert(data.length === size);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.value = 0;
|
return data;
|
||||||
|
|
||||||
if (base === 10 || base === 'dec') {
|
|
||||||
let nonzero = 0;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
let 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 (negative)
|
|
||||||
this.value = -this.value;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (base === 16 || base === 'hex') {
|
|
||||||
let nonzero = 0;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
let 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 (negative)
|
|
||||||
this.value = -this.value;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Base ${base} not supported.`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ScriptNum.fromString = function fromString(str, base) {
|
ScriptNum.prototype.fromRaw = function fromRaw(data) {
|
||||||
return new ScriptNum(0).fromString(str, base);
|
assert(Buffer.isBuffer(data));
|
||||||
|
|
||||||
|
// Empty arrays are always zero.
|
||||||
|
if (data.length === 0)
|
||||||
|
return this;
|
||||||
|
|
||||||
|
// Read number (9 bytes max).
|
||||||
|
switch (data.length) {
|
||||||
|
case 9:
|
||||||
|
// Note: this shift overflows to
|
||||||
|
// zero in modern bitcoin core.
|
||||||
|
this.lo |= data[8];
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove high bit and flip sign.
|
||||||
|
if (data[data.length - 1] & 0x80) {
|
||||||
|
this.setn((data.length * 8) - 1, 0);
|
||||||
|
this.ineg();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
ScriptNum.prototype.fromRaw = function fromRaw(data, minimal, limit) {
|
ScriptNum.prototype.decode = function decode(data, minimal, limit) {
|
||||||
|
assert(Buffer.isBuffer(data));
|
||||||
|
|
||||||
if (minimal == null)
|
if (minimal == null)
|
||||||
minimal = true;
|
minimal = true;
|
||||||
|
|
||||||
if (limit == null)
|
if (limit == null)
|
||||||
limit = 4;
|
limit = 4;
|
||||||
|
|
||||||
// We can't handle more than 6 bytes.
|
// We can't handle more than 9 bytes.
|
||||||
assert(limit <= 6, 'Number exceeds 48 bits.');
|
assert(limit >= 4 && limit <= 9, 'Bad script number size limit.');
|
||||||
|
|
||||||
// Max size is 4 bytes by default, 6 bytes max.
|
// Max size is 4 bytes by default, 9 bytes max.
|
||||||
if (data.length > limit)
|
if (data.length > limit)
|
||||||
throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.');
|
throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.');
|
||||||
|
|
||||||
// Empty arrays are always zero.
|
|
||||||
if (data.length === 0) {
|
|
||||||
this.value = 0;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure minimal serialization.
|
// Ensure minimal serialization.
|
||||||
if (minimal) {
|
if (minimal && !ScriptNum.isMinimal(data))
|
||||||
if ((data[data.length - 1] & 0x7f) === 0) {
|
throw new ScriptError('UNKNOWN_ERROR', 'Non-minimal script number.');
|
||||||
if (data.length === 1 || !(data[data.length - 2] & 0x80)) {
|
|
||||||
throw new ScriptError(
|
|
||||||
'UNKNOWN_ERROR',
|
|
||||||
'Non-minimally encoded Script number.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value = 0;
|
return this.fromRaw(data);
|
||||||
|
|
||||||
// Read number (6 bytes max).
|
|
||||||
switch (data.length) {
|
|
||||||
case 6:
|
|
||||||
this.value += data[5] * 0x10000000000;
|
|
||||||
case 5:
|
|
||||||
this.value += data[4] * 0x100000000;
|
|
||||||
case 4:
|
|
||||||
this.value += data[3] * 0x1000000;
|
|
||||||
case 3:
|
|
||||||
this.value += data[2] * 0x10000;
|
|
||||||
case 2:
|
|
||||||
this.value += data[1] * 0x100;
|
|
||||||
case 1:
|
|
||||||
this.value += data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove high bit and flip sign.
|
|
||||||
if (data[data.length - 1] & 0x80) {
|
|
||||||
switch (data.length) {
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
case 4:
|
|
||||||
this.value &= ~(0x80 << (8 * (data.length - 1)));
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
this.value -= 0x8000000000;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
this.value -= 0x800000000000;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.value = -this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ScriptNum.fromRaw = function fromRaw(data, minimal, limit) {
|
ScriptNum.prototype.inspect = function inspect() {
|
||||||
return new ScriptNum(0).fromRaw(data, minimal, limit);
|
return `<ScriptNum: ${this.toString(10)}>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
ScriptNum.prototype.toRaw = function toRaw() {
|
ScriptNum.isMinimal = function isMinimal(data) {
|
||||||
let value = this.value;
|
assert(Buffer.isBuffer(data));
|
||||||
|
|
||||||
// Zeroes are always empty arrays.
|
if (data.length === 0)
|
||||||
if (value === 0)
|
return true;
|
||||||
return EMPTY_ARRAY;
|
|
||||||
|
|
||||||
// Need to append sign bit.
|
if ((data[data.length - 1] & 0x7f) === 0) {
|
||||||
let negative = false;
|
if (data.length === 1)
|
||||||
if (value < 0) {
|
return false;
|
||||||
negative = true;
|
|
||||||
value = -value;
|
if (!(data[data.length - 2] & 0x80))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gauge buffer size.
|
return true;
|
||||||
let offset, size;
|
|
||||||
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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write number.
|
|
||||||
const data = Buffer.allocUnsafe(size + offset);
|
|
||||||
|
|
||||||
switch (size) {
|
|
||||||
case 6:
|
|
||||||
data[5] = (value / 0x10000000000 | 0) & 0xff;
|
|
||||||
case 5:
|
|
||||||
data[4] = (value / 0x100000000 | 0) & 0xff;
|
|
||||||
case 4:
|
|
||||||
data[3] = (value >>> 24) & 0xff;
|
|
||||||
case 3:
|
|
||||||
data[2] = (value >> 16) & 0xff;
|
|
||||||
case 2:
|
|
||||||
data[1] = (value >> 8) & 0xff;
|
|
||||||
case 1:
|
|
||||||
data[0] = value & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append sign bit.
|
|
||||||
if (data[size - 1] & 0x80)
|
|
||||||
data[size] = negative ? 0x80 : 0;
|
|
||||||
else if (negative)
|
|
||||||
data[size - 1] |= 0x80;
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
ScriptNum.decode = function decode(data, minimal, limit) {
|
||||||
* Expose
|
return new ScriptNum().decode(data, minimal, limit);
|
||||||
*/
|
};
|
||||||
|
|
||||||
module.exports = ScriptNum;
|
ScriptNum.isScriptNum = function isScriptNum(obj) {
|
||||||
|
return obj instanceof ScriptNum;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user