@bsv/sdk
Version:
BSV Blockchain Software Development Kit
1,250 lines • 52.9 kB
JavaScript
/**
* JavaScript numbers are only precise up to 53 bits. Since Bitcoin relies on
* 256-bit cryptography, this BigNumber class enables operations on larger
* numbers.
*
* @class BigNumber
*/
export default class BigNumber {
/**
* @privateinitializer
*/
static zeros = [
'', '0', '00', '000', '0000', '00000', '000000', '0000000', '00000000',
'000000000', '0000000000', '00000000000', '000000000000', '0000000000000',
'00000000000000', '000000000000000', '0000000000000000', '00000000000000000',
'000000000000000000', '0000000000000000000', '00000000000000000000',
'000000000000000000000', '0000000000000000000000', '00000000000000000000000',
'000000000000000000000000', '0000000000000000000000000'
];
/**
* @privateinitializer
*/
static groupSizes = [
0, 0, 25, 16, 12, 11, 10, 9, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
];
/**
* @privateinitializer
*/
static groupBases = [
0, 0, 33554432, 43046721, 16777216, 48828125, 60466176, 40353607, 16777216,
43046721, 10000000, 19487171, 35831808, 62748517, 7529536, 11390625,
16777216, 24137569, 34012224, 47045881, 64000000, 4084101, 5153632, 6436343,
7962624, 9765625, 11881376, 14348907, 17210368, 20511149, 24300000,
28629151, 33554432, 39135393, 45435424, 52521875, 60466176
];
/**
* The word size of big number chunks.
*
* @property wordSize
*
* @example
* console.log(BigNumber.wordSize); // output: 26
*/
static wordSize = 26;
static WORD_SIZE_BIGINT = BigInt(BigNumber.wordSize);
static WORD_MASK = (1n << BigNumber.WORD_SIZE_BIGINT) - 1n;
static MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
static MIN_SAFE_INTEGER_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
static MAX_IMULN_ARG = 0x4000000 - 1;
static MAX_NUMBER_CONSTRUCTOR_MAG_BIGINT = (1n << 53n) - 1n;
_magnitude;
_sign;
_nominalWordLength;
/**
* Reduction context of the big number.
*
* @property red
*/
red;
/**
* Negative flag. Indicates whether the big number is a negative number.
* - If 0, the number is positive.
* - If 1, the number is negative.
*
* @property negative
*/
get negative() {
return this._sign;
}
/**
* Sets the negative flag. Only 0 (positive) or 1 (negative) are allowed.
*/
set negative(val) {
this.assert(val === 0 || val === 1, 'Negative property must be 0 or 1');
const newSign = val === 1 ? 1 : 0;
if (this._magnitude === 0n) {
this._sign = 0;
}
else {
this._sign = newSign;
}
}
get _computedWordsArray() {
if (this._magnitude === 0n)
return [0];
const arr = [];
let temp = this._magnitude;
while (temp > 0n) {
arr.push(Number(temp & BigNumber.WORD_MASK));
temp >>= BigNumber.WORD_SIZE_BIGINT;
}
return arr.length > 0 ? arr : [0];
}
/**
* Array of numbers, where each number represents a part of the value of the big number.
*
* @property words
*/
get words() {
const computed = this._computedWordsArray;
if (this._nominalWordLength <= computed.length) {
return computed;
}
const paddedWords = new Array(this._nominalWordLength).fill(0);
for (let i = 0; i < computed.length; i++) {
paddedWords[i] = computed[i];
}
return paddedWords;
}
/**
* Sets the words array representing the value of the big number.
*/
set words(newWords) {
const oldSign = this._sign;
let newMagnitude = 0n;
const len = newWords.length > 0 ? newWords.length : 1;
for (let i = len - 1; i >= 0; i--) {
const wordVal = newWords[i] === undefined ? 0 : newWords[i];
newMagnitude = (newMagnitude << BigNumber.WORD_SIZE_BIGINT) | BigInt(wordVal & Number(BigNumber.WORD_MASK));
}
this._magnitude = newMagnitude;
this._sign = oldSign;
this._nominalWordLength = len;
this.normSign();
}
/**
* Length of the words array.
*
* @property length
*/
get length() {
return Math.max(1, this._nominalWordLength);
}
/**
* Checks whether a value is an instance of BigNumber. Regular JS numbers fail this check.
*
* @method isBN
* @param num - The value to be checked.
* @returns - Returns a boolean value determining whether or not the checked num parameter is a BigNumber.
*/
static isBN(num) {
if (num instanceof BigNumber)
return true;
return (num !== null &&
typeof num === 'object' &&
num.constructor?.wordSize === BigNumber.wordSize &&
Array.isArray(num.words));
}
/**
* Returns the bigger value between two BigNumbers
*
* @method max
* @param left - The first BigNumber to be compared.
* @param right - The second BigNumber to be compared.
* @returns - Returns the bigger BigNumber between left and right.
*/
static max(left, right) { return left.cmp(right) > 0 ? left : right; }
/**
* Returns the smaller value between two BigNumbers
*
* @method min
* @param left - The first BigNumber to be compared.
* @param right - The second BigNumber to be compared.
* @returns - Returns the smaller value between left and right.
*/
static min(left, right) { return left.cmp(right) < 0 ? left : right; }
/**
* @constructor
*
* @param number - The number (various types accepted) to construct a BigNumber from. Default is 0.
* @param base - The base of number provided. By default is 10.
* @param endian - The endianness provided. By default is 'big endian'.
*/
constructor(number = 0, base = 10, endian = 'be') {
this._magnitude = 0n;
this._sign = 0;
this._nominalWordLength = 1;
this.red = null;
if (number === undefined)
number = 0;
if (number === null) {
this._initializeState(0n, 0);
return;
}
if (typeof number === 'bigint') {
this._initializeState(number < 0n ? -number : number, number < 0n ? 1 : 0);
this.normSign();
return;
}
let effectiveBase = base;
let effectiveEndian = endian;
if (base === 'le' || base === 'be') {
effectiveEndian = base;
effectiveBase = 10;
}
if (typeof number === 'number') {
this.initNumber(number, effectiveEndian);
return;
}
if (Array.isArray(number)) {
this.initArray(number, effectiveEndian);
return;
}
if (typeof number === 'string') {
if (effectiveBase === 'hex')
effectiveBase = 16;
this.assert(typeof effectiveBase === 'number' && effectiveBase === (effectiveBase | 0) && effectiveBase >= 2 && effectiveBase <= 36, 'Base must be an integer between 2 and 36');
const originalNumberStr = number.toString().replace(/\s+/g, '');
let start = 0;
let sign = 0;
if (originalNumberStr.startsWith('-')) {
start++;
sign = 1;
}
else if (originalNumberStr.startsWith('+')) {
start++;
}
const numStr = originalNumberStr.substring(start);
if (numStr.length === 0) {
this._initializeState(0n, (sign === 1 && originalNumberStr.startsWith('-')) ? 1 : 0);
this.normSign();
return;
}
if (effectiveBase === 16) {
let tempMagnitude;
if (effectiveEndian === 'le') {
const bytes = [];
let hexStr = numStr;
if (hexStr.length % 2 !== 0)
hexStr = '0' + hexStr;
for (let i = 0; i < hexStr.length; i += 2) {
const byteHex = hexStr.substring(i, i + 2);
const byteVal = parseInt(byteHex, 16);
if (isNaN(byteVal))
throw new Error('Invalid character in ' + hexStr);
bytes.push(byteVal);
}
this.initArray(bytes, 'le');
this._sign = sign;
this.normSign();
return;
}
else {
try {
tempMagnitude = BigInt('0x' + numStr);
}
catch (e) {
throw new Error('Invalid character in ' + numStr);
}
}
this._initializeState(tempMagnitude, sign);
this.normSign();
}
else {
try {
this._parseBaseString(numStr, effectiveBase);
this._sign = sign;
this.normSign();
if (effectiveEndian === 'le') {
const currentSign = this._sign;
this.initArray(this.toArray('be'), 'le');
this._sign = currentSign;
this.normSign();
}
}
catch (err) {
const error = err;
if (error.message.includes('Invalid character in string') ||
error.message.includes('Invalid digit for base') ||
error.message.startsWith('Invalid character:')) {
throw new Error('Invalid character');
}
throw error;
}
}
}
else if (number !== 0) {
this.assert(false, 'Unsupported input type for BigNumber constructor');
}
else {
this._initializeState(0n, 0);
}
}
_bigIntToStringInBase(num, base) {
if (num === 0n)
return '0';
if (base < 2 || base > 36)
throw new Error('Base must be between 2 and 36');
const digits = '0123456789abcdefghijklmnopqrstuvwxyz';
let result = '';
let currentNum = num > 0n ? num : -num;
const bigBase = BigInt(base);
while (currentNum > 0n) {
result = digits[Number(currentNum % bigBase)] + result;
currentNum /= bigBase;
}
return result;
}
_parseBaseString(numberStr, base) {
if (numberStr.length === 0) {
this._magnitude = 0n;
this._finishInitialization();
return;
}
this._magnitude = 0n;
const bigBase = BigInt(base);
let groupSize = BigNumber.groupSizes[base];
let groupBaseBigInt = BigInt(BigNumber.groupBases[base]);
if (groupSize === 0 || groupBaseBigInt === 0n) {
groupSize = Math.floor(Math.log(0x3ffffff) / Math.log(base));
if (groupSize === 0)
groupSize = 1;
groupBaseBigInt = bigBase ** BigInt(groupSize);
}
let currentPos = 0;
const totalLen = numberStr.length;
let firstChunkLen = totalLen % groupSize;
if (firstChunkLen === 0 && totalLen > 0)
firstChunkLen = groupSize;
if (firstChunkLen > 0) {
const chunkStr = numberStr.substring(currentPos, currentPos + firstChunkLen);
this._magnitude = BigInt(this._parseBaseWord(chunkStr, base));
currentPos += firstChunkLen;
}
while (currentPos < totalLen) {
const chunkStr = numberStr.substring(currentPos, currentPos + groupSize);
const wordVal = BigInt(this._parseBaseWord(chunkStr, base));
this._magnitude = this._magnitude * groupBaseBigInt + wordVal;
currentPos += groupSize;
}
this._finishInitialization();
}
_parseBaseWord(str, base) {
let r = 0;
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
let digitVal;
if (charCode >= 48 && charCode <= 57)
digitVal = charCode - 48;
else if (charCode >= 65 && charCode <= 90)
digitVal = charCode - 65 + 10;
else if (charCode >= 97 && charCode <= 122)
digitVal = charCode - 97 + 10;
else
throw new Error('Invalid character: ' + str[i]);
if (digitVal >= base)
throw new Error('Invalid character');
r = r * base + digitVal;
}
return r;
}
_initializeState(magnitude, sign) {
this._magnitude = magnitude;
this._sign = (magnitude === 0n) ? 0 : sign;
this._finishInitialization();
}
_finishInitialization() {
if (this._magnitude === 0n) {
this._nominalWordLength = 1;
this._sign = 0;
}
else {
const bitLen = this._magnitude.toString(2).length;
this._nominalWordLength = Math.max(1, Math.ceil(bitLen / BigNumber.wordSize));
}
}
assert(val, msg = 'Assertion failed') { if (!val)
throw new Error(msg); }
initNumber(number, endian = 'be') {
this.assert(BigInt(Math.abs(number)) <= BigNumber.MAX_NUMBER_CONSTRUCTOR_MAG_BIGINT, 'The number is larger than 2 ^ 53 (unsafe)');
this.assert(number % 1 === 0, 'Number must be an integer for BigNumber conversion');
this._initializeState(BigInt(Math.abs(number)), number < 0 ? 1 : 0);
if (endian === 'le') {
const currentSign = this._sign;
const beBytes = this.toArray('be');
this.initArray(beBytes, 'le');
this._sign = currentSign;
this.normSign();
}
return this;
}
initArray(bytes, endian) {
if (bytes.length === 0) {
this._initializeState(0n, 0);
return this;
}
let magnitude = 0n;
if (endian === 'be') {
for (let i = 0; i < bytes.length; i++)
magnitude = (magnitude << 8n) | BigInt(bytes[i] & 0xff);
}
else {
for (let i = bytes.length - 1; i >= 0; i--)
magnitude = (magnitude << 8n) | BigInt(bytes[i] & 0xff);
}
this._initializeState(magnitude, 0);
return this;
}
copy(dest) { dest._magnitude = this._magnitude; dest._sign = this._sign; dest._nominalWordLength = this._nominalWordLength; dest.red = this.red; }
static move(dest, src) { dest._magnitude = src._magnitude; dest._sign = src._sign; dest._nominalWordLength = src._nominalWordLength; dest.red = src.red; }
clone() { const r = new BigNumber(0n); this.copy(r); return r; }
expand(size) {
this.assert(size >= 0, 'Expand size must be non-negative');
this._nominalWordLength = Math.max(this._nominalWordLength, size, 1);
return this;
}
strip() { this._finishInitialization(); return this.normSign(); }
normSign() { if (this._magnitude === 0n)
this._sign = 0; return this; }
inspect() { return (this.red !== null ? '<BN-R: ' : '<BN: ') + this.toString(16) + '>'; }
_getMinimalHex() {
if (this._magnitude === 0n)
return '0';
return this._magnitude.toString(16);
}
/**
* Converts the BigNumber instance to a string representation.
*
* @method toString
* @param base - The base for representing number. Default is 10. Other accepted values are 16 and 'hex'.
* @param padding - Represents the minimum number of digits to represent the BigNumber as a string. Default is 1.
* @returns The string representation of the BigNumber instance
*/
toString(base = 10, padding = 1) {
if (base === 16 || base === 'hex') {
// For toString('hex', N), N is the 'multiple-of-N characters' rule from bn.js tests
// For toString(16, P) where P=1 (default) or P=0, it means minimal hex.
let hexStr = this._getMinimalHex(); // e.g., "f", "123", "0"
if (padding > 1) { // N-multiple rule for characters
// Ensure hexStr is even length if not "0" to represent full bytes before applying multiple rule
if (hexStr !== '0' && hexStr.length % 2 !== 0) {
hexStr = '0' + hexStr;
}
while (hexStr.length % padding !== 0) {
hexStr = '0' + hexStr;
}
}
// If padding is 0 or 1, hexStr (minimal) is used as is.
// "0" is always "0" unless toHex("") specific case.
// Single digit hex like "f" is not "0f" by default from toString(16).
return (this.isNeg() ? '-' : '') + hexStr;
}
if (typeof base !== 'number' || base < 2 || base > 36 || base % 1 !== 0)
throw new Error('Base should be an integer between 2 and 36');
return this.toBaseString(base, padding);
}
toBaseString(base, padding) {
if (this._magnitude === 0n) {
let out = '0';
if (padding > 1) {
while (out.length < padding)
out = '0' + out;
}
return out;
}
let groupSize = BigNumber.groupSizes[base];
let groupBaseBigInt = BigInt(BigNumber.groupBases[base]);
if (groupSize === 0 || groupBaseBigInt === 0n) {
groupSize = Math.floor(Math.log(Number.MAX_SAFE_INTEGER) / Math.log(base));
if (groupSize === 0)
groupSize = 1;
groupBaseBigInt = BigInt(base) ** BigInt(groupSize);
}
let out = '';
let tempMag = this._magnitude;
while (tempMag > 0n) {
const remainder = tempMag % groupBaseBigInt;
tempMag /= groupBaseBigInt;
const chunkStr = this._bigIntToStringInBase(remainder, base);
if (tempMag > 0n) {
const zerosToPrepend = groupSize - chunkStr.length;
if (zerosToPrepend > 0 && zerosToPrepend < BigNumber.zeros.length) {
out = BigNumber.zeros[zerosToPrepend] + chunkStr + out;
}
else if (zerosToPrepend > 0) {
out = '0'.repeat(zerosToPrepend) + chunkStr + out;
}
else {
out = chunkStr + out;
}
}
else {
out = chunkStr + out;
}
}
if (padding > 0) {
while (out.length < padding)
out = '0' + out;
}
return (this._sign === 1 ? '-' : '') + out;
}
/**
* Converts the BigNumber instance to a JavaScript number.
* Please note that JavaScript numbers are only precise up to 53 bits.
*
* @method toNumber
* @throws If the BigNumber instance cannot be safely stored in a JavaScript number
* @returns The JavaScript number representation of the BigNumber instance.
*/
toNumber() {
const val = this._getSignedValue();
if (val > BigNumber.MAX_SAFE_INTEGER_BIGINT || val < BigNumber.MIN_SAFE_INTEGER_BIGINT)
throw new Error('Number can only safely store up to 53 bits');
return Number(val);
}
/**
* Converts the BigNumber instance to a JSON-formatted string.
*
* @method toJSON
* @returns The JSON string representation of the BigNumber instance.
*/
toJSON() {
const hex = this._getMinimalHex();
return (this.isNeg() ? '-' : '') + hex;
}
toArrayLikeGeneric(res, isLE) {
let tempMag = this._magnitude;
let position = isLE ? 0 : res.length - 1;
const increment = isLE ? 1 : -1;
for (let k = 0; k < res.length; ++k) {
if (tempMag === 0n && position >= 0 && position < res.length) {
res[position] = 0;
}
else if (position >= 0 && position < res.length) {
res[position] = Number(tempMag & 0xffn);
}
else {
break;
}
tempMag >>= 8n;
position += increment;
}
}
/**
* Converts the BigNumber instance to an array of bytes.
*
* @method toArray
* @param endian - Endianness of the output array, defaults to 'be'.
* @param length - Optional length of the output array.
* @returns Array of bytes representing the BigNumber.
*/
toArray(endian = 'be', length) {
this.strip();
const actualByteLength = this.byteLength();
const reqLength = length ?? Math.max(1, actualByteLength);
this.assert(actualByteLength <= reqLength, 'byte array longer than desired length');
this.assert(reqLength > 0, 'Requested array length <= 0');
const res = new Array(reqLength).fill(0);
if (this._magnitude === 0n && reqLength > 0)
return res;
if (this._magnitude === 0n && reqLength === 0)
return [];
this.toArrayLikeGeneric(res, endian === 'le');
return res;
}
/**
* Calculates the number of bits required to represent the BigNumber.
*
* @method bitLength
* @returns The bit length of the BigNumber.
*/
bitLength() { if (this._magnitude === 0n)
return 0; return this._magnitude.toString(2).length; }
/**
* Converts a BigNumber to an array of bits.
*
* @method toBitArray
* @param num - The BigNumber to convert.
* @returns An array of bits.
*/
static toBitArray(num) {
const len = num.bitLength();
if (len === 0)
return [];
const w = new Array(len);
const mag = num._magnitude;
for (let bit = 0; bit < len; bit++) {
w[bit] = ((mag >> BigInt(bit)) & 1n) !== 0n ? 1 : 0;
}
return w;
}
/**
* Instance version of {@link toBitArray}.
*/
toBitArray() { return BigNumber.toBitArray(this); }
/**
* Returns the number of trailing zero bits in the big number.
*
* @method zeroBits
* @returns Returns the number of trailing zero bits
* in the binary representation of the big number.
*
* @example
* const bn = new BigNumber('8'); // binary: 1000
* const zeroBits = bn.zeroBits(); // 3
*/
zeroBits() {
if (this._magnitude === 0n)
return 0;
let c = 0;
let t = this._magnitude;
while ((t & 1n) === 0n && t !== 0n) {
c++;
t >>= 1n;
}
return c;
}
/**
* Calculates the number of bytes required to represent the BigNumber.
*
* @method byteLength
* @returns The byte length of the BigNumber.
*/
byteLength() { if (this._magnitude === 0n)
return 0; return Math.ceil(this.bitLength() / 8); }
_getSignedValue() { return this._sign === 1 ? -this._magnitude : this._magnitude; }
_setValueFromSigned(sVal) {
if (sVal < 0n) {
this._magnitude = -sVal;
this._sign = 1;
}
else {
this._magnitude = sVal;
this._sign = 0;
}
this._finishInitialization();
this.normSign();
}
toTwos(width) {
this.assert(width >= 0);
const Bw = BigInt(width);
let v = this._getSignedValue();
if (this._sign === 1 && this._magnitude !== 0n)
v = (1n << Bw) + v;
const m = (1n << Bw) - 1n;
v &= m;
const r = new BigNumber(0n);
r._initializeState(v, 0);
return r;
}
fromTwos(width) {
this.assert(width >= 0);
const Bw = BigInt(width);
const m = this._magnitude;
if (width > 0 && ((m >> (Bw - 1n)) & 1n) !== 0n && this._sign === 0) {
const sVal = m - (1n << Bw);
const r = new BigNumber(0n);
r._setValueFromSigned(sVal);
return r;
}
return this.clone();
}
isNeg() { return this._sign === 1 && this._magnitude !== 0n; }
neg() { return this.clone().ineg(); }
ineg() { if (this._magnitude !== 0n)
this._sign = this._sign === 1 ? 0 : 1; return this; }
_iuop(num, op) {
const newMag = op(this._magnitude, num._magnitude);
const isXor = op === ((a, b) => a ^ b);
let targetNominalLength = this._nominalWordLength;
if (isXor)
targetNominalLength = Math.max(this.length, num.length);
this._magnitude = newMag;
this._finishInitialization();
if (isXor)
this._nominalWordLength = Math.max(this._nominalWordLength, targetNominalLength);
return this.strip();
}
iuor(num) { return this._iuop(num, (a, b) => a | b); }
iuand(num) { return this._iuop(num, (a, b) => a & b); }
iuxor(num) { return this._iuop(num, (a, b) => a ^ b); }
_iop(num, op) { this.assert(this._sign === 0 && num._sign === 0); return this._iuop(num, op); }
ior(num) { return this._iop(num, (a, b) => a | b); }
iand(num) { return this._iop(num, (a, b) => a & b); }
ixor(num) { return this._iop(num, (a, b) => a ^ b); }
_uop_new(num, opName) { if (this.length >= num.length)
return this.clone()[opName](num); return num.clone()[opName](this); }
or(num) { this.assert(this._sign === 0 && num._sign === 0); return this._uop_new(num, 'iuor'); }
uor(num) { return this._uop_new(num, 'iuor'); }
and(num) { this.assert(this._sign === 0 && num._sign === 0); return this._uop_new(num, 'iuand'); }
uand(num) { return this._uop_new(num, 'iuand'); }
xor(num) { this.assert(this._sign === 0 && num._sign === 0); return this._uop_new(num, 'iuxor'); }
uxor(num) { return this._uop_new(num, 'iuxor'); }
inotn(width) {
this.assert(typeof width === 'number' && width >= 0);
const Bw = BigInt(width);
const m = (1n << Bw) - 1n;
this._magnitude = (~this._magnitude) & m;
const wfw = width === 0 ? 1 : Math.ceil(width / BigNumber.wordSize);
this._nominalWordLength = Math.max(1, wfw);
this.strip();
this._nominalWordLength = Math.max(this._nominalWordLength, Math.max(1, wfw));
return this;
}
notn(width) { return this.clone().inotn(width); }
setn(bit, val) { this.assert(typeof bit === 'number' && bit >= 0); const Bb = BigInt(bit); if (val === 1 || val === true)
this._magnitude |= (1n << Bb);
else
this._magnitude &= ~(1n << Bb); const wnb = Math.floor(bit / BigNumber.wordSize) + 1; this._nominalWordLength = Math.max(this._nominalWordLength, wnb); this._finishInitialization(); return this.strip(); }
iadd(num) { this._setValueFromSigned(this._getSignedValue() + num._getSignedValue()); return this; }
add(num) { const r = new BigNumber(0n); r._setValueFromSigned(this._getSignedValue() + num._getSignedValue()); return r; }
isub(num) { this._setValueFromSigned(this._getSignedValue() - num._getSignedValue()); return this; }
sub(num) { const r = new BigNumber(0n); r._setValueFromSigned(this._getSignedValue() - num._getSignedValue()); return r; }
mul(num) {
const r = new BigNumber(0n);
r._magnitude = this._magnitude * num._magnitude;
r._sign = r._magnitude === 0n ? 0 : (this._sign ^ num._sign);
r._nominalWordLength = this.length + num.length;
r.red = null;
return r.normSign();
}
imul(num) {
this._magnitude *= num._magnitude;
this._sign = this._magnitude === 0n ? 0 : (this._sign ^ num._sign);
this._nominalWordLength = this.length + num.length;
this.red = null;
return this.normSign();
}
imuln(num) { this.assert(typeof num === 'number', 'Assertion failed'); this.assert(Math.abs(num) <= BigNumber.MAX_IMULN_ARG, 'Assertion failed'); this._setValueFromSigned(this._getSignedValue() * BigInt(num)); return this; }
muln(num) { return this.clone().imuln(num); }
sqr() {
const r = new BigNumber(0n);
r._magnitude = this._magnitude * this._magnitude;
r._sign = 0;
r._nominalWordLength = this.length * 2;
r.red = null;
return r;
}
isqr() {
this._magnitude *= this._magnitude;
this._sign = 0;
this._nominalWordLength = this.length * 2;
this.red = null;
return this;
}
pow(num) {
this.assert(num._sign === 0, 'Exponent for pow must be non-negative');
if (num.isZero())
return new BigNumber(1n);
const res = new BigNumber(1n);
const currentBase = this.clone();
const exp = num.clone();
const baseIsNegative = currentBase.isNeg();
const expIsOdd = exp.isOdd();
if (baseIsNegative)
currentBase.ineg();
while (!exp.isZero()) {
if (exp.isOdd()) {
res.imul(currentBase);
}
currentBase.isqr();
exp.iushrn(1);
}
if (baseIsNegative && expIsOdd) {
res.ineg();
}
return res;
}
iushln(bits) { this.assert(typeof bits === 'number' && bits >= 0); if (bits === 0)
return this; this._magnitude <<= BigInt(bits); this._finishInitialization(); return this.strip(); }
ishln(bits) { this.assert(this._sign === 0, 'ishln requires positive number'); return this.iushln(bits); }
iushrn(bits, hint, extended) {
this.assert(typeof bits === 'number' && bits >= 0);
if (bits === 0) {
if (extended != null)
extended._initializeState(0n, 0);
return this;
}
if (extended != null) {
const m = (1n << BigInt(bits)) - 1n;
const sOut = this._magnitude & m;
extended._initializeState(sOut, 0);
}
this._magnitude >>= BigInt(bits);
this._finishInitialization();
return this.strip();
}
ishrn(bits, hint, extended) {
this.assert(this._sign === 0, 'ishrn requires positive number');
return this.iushrn(bits, hint, extended);
}
shln(bits) { return this.clone().ishln(bits); }
ushln(bits) { return this.clone().iushln(bits); }
shrn(bits) { return this.clone().ishrn(bits); }
ushrn(bits) { return this.clone().iushrn(bits); }
testn(bit) {
this.assert(typeof bit === 'number' && bit >= 0);
return ((this._magnitude >> BigInt(bit)) & 1n) !== 0n;
}
imaskn(bits) {
this.assert(typeof bits === 'number' && bits >= 0);
this.assert(this._sign === 0, 'imaskn works only with positive numbers');
const Bb = BigInt(bits);
const m = Bb === 0n ? 0n : (1n << Bb) - 1n;
this._magnitude &= m;
const wfm = bits === 0 ? 1 : Math.max(1, Math.ceil(bits / BigNumber.wordSize));
this._nominalWordLength = wfm;
this._finishInitialization();
this._nominalWordLength = Math.max(this._nominalWordLength, wfm);
return this.strip();
}
maskn(bits) { return this.clone().imaskn(bits); }
iaddn(num) { this.assert(typeof num === 'number'); this.assert(Math.abs(num) <= BigNumber.MAX_IMULN_ARG, 'num is too large'); this._setValueFromSigned(this._getSignedValue() + BigInt(num)); return this; }
_iaddn(num) { return this.iaddn(num); }
isubn(num) { this.assert(typeof num === 'number'); this.assert(Math.abs(num) <= BigNumber.MAX_IMULN_ARG, 'Assertion failed'); this._setValueFromSigned(this._getSignedValue() - BigInt(num)); return this; }
addn(num) { return this.clone().iaddn(num); }
subn(num) { return this.clone().isubn(num); }
iabs() { this._sign = 0; return this; }
abs() { return this.clone().iabs(); }
divmod(num, mode, positive) {
this.assert(!num.isZero(), 'Division by zero');
if (this.isZero()) {
const z = new BigNumber(0n);
return { div: mode !== 'mod' ? z : null, mod: mode !== 'div' ? z : null };
}
const tV = this._getSignedValue();
const nV = num._getSignedValue();
let dV = null;
let mV = null;
if (mode !== 'mod')
dV = tV / nV;
if (mode !== 'div') {
mV = tV % nV;
if (positive === true && mV < 0n)
mV += nV < 0n ? -nV : nV;
}
const rd = dV !== null ? new BigNumber(0n) : null;
if (rd !== null && dV !== null)
rd._setValueFromSigned(dV);
const rm = mV !== null ? new BigNumber(0n) : null;
if (rm !== null && mV !== null)
rm._setValueFromSigned(mV);
return { div: rd, mod: rm };
}
div(num) {
return this.divmod(num, 'div', false).div;
}
mod(num) {
return this.divmod(num, 'mod', false).mod;
}
umod(num) {
return this.divmod(num, 'mod', true).mod;
}
divRound(num) {
this.assert(!num.isZero());
const tV = this._getSignedValue();
const nV = num._getSignedValue();
let d = tV / nV;
const m = tV % nV;
if (m === 0n) {
const r = new BigNumber(0n);
r._setValueFromSigned(d);
return r;
}
const absM = m < 0n ? -m : m;
const absNV = nV < 0n ? -nV : nV;
if (absM * 2n >= absNV) {
if ((tV > 0n && nV > 0n) || (tV < 0n && nV < 0n)) {
d += 1n;
}
else {
d -= 1n;
}
}
const r = new BigNumber(0n);
r._setValueFromSigned(d);
return r;
}
modrn(numArg) {
this.assert(numArg !== 0, 'Division by zero in modrn');
const absDivisor = BigInt(Math.abs(numArg));
if (absDivisor === 0n)
throw new Error('Division by zero in modrn');
const remainderMag = this._magnitude % absDivisor;
return numArg < 0 ? Number(-remainderMag) : Number(remainderMag);
}
idivn(num) {
this.assert(num !== 0);
this.assert(Math.abs(num) <= BigNumber.MAX_IMULN_ARG, 'num is too large');
this._setValueFromSigned(this._getSignedValue() / BigInt(num));
return this;
}
divn(num) { return this.clone().idivn(num); }
egcd(p) {
this.assert(p._sign === 0, 'p must not be negative');
this.assert(!p.isZero(), 'p must not be zero');
let uV = this._getSignedValue();
let vV = p._magnitude;
let a = 1n;
let pa = 0n;
let b = 0n;
let pb = 1n;
while (vV !== 0n) {
const q = uV / vV;
let t = vV;
vV = uV % vV;
uV = t;
t = pa;
pa = a - q * pa;
a = t;
t = pb;
pb = b - q * pb;
b = t;
}
const ra = new BigNumber(0n);
ra._setValueFromSigned(a);
const rb = new BigNumber(0n);
rb._setValueFromSigned(b);
const rg = new BigNumber(0n);
rg._initializeState(uV < 0n ? -uV : uV, 0);
return { a: ra, b: rb, gcd: rg };
}
gcd(num) {
let u = this._magnitude;
let v = num._magnitude;
if (u === 0n) {
const r = new BigNumber(0n);
r._setValueFromSigned(v);
return r.iabs();
}
if (v === 0n) {
const r = new BigNumber(0n);
r._setValueFromSigned(u);
return r.iabs();
}
while (v !== 0n) {
const t = u % v;
u = v;
v = t;
}
const res = new BigNumber(0n);
res._initializeState(u, 0);
return res;
}
invm(num) {
this.assert(!num.isZero() && num._sign === 0, 'Modulus for invm must be positive and non-zero');
const eg = this.egcd(num);
if (!eg.gcd.eqn(1)) {
throw new Error('Inverse does not exist (numbers are not coprime).');
}
return eg.a.umod(num);
}
isEven() { return this._magnitude % 2n === 0n; }
isOdd() { return this._magnitude % 2n === 1n; }
andln(num) { this.assert(num >= 0); return Number(this._magnitude & BigInt(num)); }
bincn(bit) { this.assert(typeof bit === 'number' && bit >= 0); const BVal = 1n << BigInt(bit); this._setValueFromSigned(this._getSignedValue() + BVal); return this; }
isZero() { return this._magnitude === 0n; }
cmpn(num) { this.assert(Math.abs(num) <= BigNumber.MAX_IMULN_ARG, 'Number is too big'); const tV = this._getSignedValue(); const nV = BigInt(num); if (tV < nV)
return -1; if (tV > nV)
return 1; return 0; }
cmp(num) { const tV = this._getSignedValue(); const nV = num._getSignedValue(); if (tV < nV)
return -1; if (tV > nV)
return 1; return 0; }
ucmp(num) { if (this._magnitude < num._magnitude)
return -1; if (this._magnitude > num._magnitude)
return 1; return 0; }
gtn(num) { return this.cmpn(num) === 1; }
gt(num) { return this.cmp(num) === 1; }
gten(num) { return this.cmpn(num) >= 0; }
gte(num) { return this.cmp(num) >= 0; }
ltn(num) { return this.cmpn(num) === -1; }
lt(num) { return this.cmp(num) === -1; }
lten(num) { return this.cmpn(num) <= 0; }
lte(num) { return this.cmp(num) <= 0; }
eqn(num) { return this.cmpn(num) === 0; }
eq(num) { return this.cmp(num) === 0; }
toRed(ctx) { this.assert(this.red == null, 'Already a number in reduction context'); this.assert(this._sign === 0, 'toRed works only with positives'); return ctx.convertTo(this).forceRed(ctx); }
fromRed() { this.assert(this.red, 'fromRed works only with numbers in reduction context'); return this.red.convertFrom(this); }
forceRed(ctx) { this.red = ctx; return this; }
redAdd(num) { this.assert(this.red, 'redAdd works only with red numbers'); return this.red.add(this, num); }
redIAdd(num) { this.assert(this.red, 'redIAdd works only with red numbers'); return this.red.iadd(this, num); }
redSub(num) { this.assert(this.red, 'redSub works only with red numbers'); return this.red.sub(this, num); }
redISub(num) { this.assert(this.red, 'redISub works only with red numbers'); return this.red.isub(this, num); }
redShl(num) { this.assert(this.red, 'redShl works only with red numbers'); return this.red.shl(this, num); }
redMul(num) { this.assert(this.red, 'redMul works only with red numbers'); this.red.verify2(this, num); return this.red.mul(this, num); }
redIMul(num) { this.assert(this.red, 'redIMul works only with red numbers'); this.red.verify2(this, num); return this.red.imul(this, num); }
redSqr() { this.assert(this.red, 'redSqr works only with red numbers'); this.red.verify1(this); return this.red.sqr(this); }
redISqr() { this.assert(this.red, 'redISqr works only with red numbers'); this.red.verify1(this); return this.red.isqr(this); }
redSqrt() { this.assert(this.red, 'redSqrt works only with red numbers'); this.red.verify1(this); return this.red.sqrt(this); }
redInvm() { this.assert(this.red, 'redInvm works only with red numbers'); this.red.verify1(this); return this.red.invm(this); }
redNeg() { this.assert(this.red, 'redNeg works only with red numbers'); this.red.verify1(this); return this.red.neg(this); }
redPow(num) { this.assert(this.red != null && num.red == null, 'redPow(normalNum)'); this.red.verify1(this); return this.red.pow(this, num); }
/**
* Creates a BigNumber from a hexadecimal string.
*
* @static
* @method fromHex
* @param hex - The hexadecimal string to create a BigNumber from.
* @param endian - Optional endianness for parsing the hex string.
* @returns Returns a BigNumber created from the hexadecimal input string.
*
* @example
* const exampleHex = 'a1b2c3';
* const bigNumber = BigNumber.fromHex(exampleHex);
*/
static fromHex(hex, endian) {
let eE = 'be';
if (endian === 'little' || endian === 'le')
eE = 'le';
return new BigNumber(hex, 16, eE);
}
/**
* Converts this BigNumber to a hexadecimal string.
*
* @method toHex
* @param length - The minimum length of the hex string
* @returns Returns a string representing the hexadecimal value of this BigNumber.
*
* @example
* const bigNumber = new BigNumber(255)
* const hex = bigNumber.toHex()
*/
toHex(byteLength = 0) {
if (this.isZero() && byteLength === 0)
return '';
let hexStr = this._getMinimalHex(); // Raw hex: "0", "f", "10", "123"
// Ensure even length for non-zero values (byte alignment)
if (hexStr !== '0' && hexStr.length % 2 !== 0) {
hexStr = '0' + hexStr;
}
// Pad to minimum character length (byteLength * 2)
const minChars = byteLength * 2;
while (hexStr.length < minChars) {
hexStr = '0' + hexStr;
}
return (this.isNeg() ? '-' : '') + hexStr;
}
/**
* Creates a BigNumber from a JSON-serialized string.
*
* @static
* @method fromJSON
* @param str - The JSON-serialized string to create a BigNumber from.
* @returns Returns a BigNumber created from the JSON input string.
*/
static fromJSON(str) { return new BigNumber(str, 16); }
/**
* Creates a BigNumber from a number.
*
* @static
* @method fromNumber
* @param n - The number to create a BigNumber from.
* @returns Returns a BigNumber equivalent to the input number.
*/
static fromNumber(n) { return new BigNumber(n); }
/**
* Creates a BigNumber from a string, considering an optional base.
*
* @static
* @method fromString
* @param str - The string to create a BigNumber from.
* @param base - The base used for conversion. If not provided, base 10 is assumed.
* @returns Returns a BigNumber equivalent to the string after conversion from the specified base.
*/
static fromString(str, base) { return new BigNumber(str, base); }
/**
* Creates a BigNumber from a signed magnitude number.
*
* @static
* @method fromSm
* @param bytes - The signed magnitude number to convert to a BigNumber.
* @param endian - Defines endianess. If not provided, big endian is assumed.
* @returns Returns a BigNumber equivalent to the signed magnitude number interpreted with specified endianess.
*/
static fromSm(bytes, endian = 'big') {
if (bytes.length === 0)
return new BigNumber(0n);
let sign = 0;
let hex = '';
if (endian === 'little') {
const last = bytes.length - 1;
let firstByte = bytes[last];
if ((firstByte & 0x80) !== 0) {
sign = 1;
firstByte &= 0x7f;
}
hex += (firstByte < 16 ? '0' : '') + firstByte.toString(16);
for (let i = last - 1; i >= 0; i--) {
const b = bytes[i];
hex += (b < 16 ? '0' : '') + b.toString(16);
}
}
else {
let firstByte = bytes[0];
if ((firstByte & 0x80) !== 0) {
sign = 1;
firstByte &= 0x7f;
}
hex += (firstByte < 16 ? '0' : '') + firstByte.toString(16);
for (let i = 1; i < bytes.length; i++) {
const b = bytes[i];
hex += (b < 16 ? '0' : '') + b.toString(16);
}
}
const mag = hex === '' ? 0n : BigInt('0x' + hex);
const r = new BigNumber(0n);
r._initializeState(mag, sign);
return r;
}
/**
* Converts this BigNumber to a signed magnitude number.
*
* @method toSm
* @param endian - Defines endianess. If not provided, big endian is assumed.
* @returns Returns an array equivalent to this BigNumber interpreted as a signed magnitude with specified endianess.
*/
toSm(endian = 'big') {
if (this._magnitude === 0n) {
return this._sign === 1 ? [0x80] : [];
}
let hex = this._getMinimalHex();
if (hex.length % 2 !== 0)
hex = '0' + hex;
const byteLen = hex.length / 2;
const bytes = new Array(byteLen);
for (let i = 0, j = 0; i < hex.length; i += 2) {
bytes[j++] = parseInt(hex.slice(i, i + 2), 16);
}
if (this._sign === 1) {
if ((bytes[0] & 0x80) !== 0)
bytes.unshift(0x80);
else
bytes[0] |= 0x80;
}
else if ((bytes[0] & 0x80) !== 0) {
bytes.unshift(0x00);
}
return endian === 'little' ? bytes.reverse() : bytes;
}
/**
* Creates a BigNumber from a number representing the "bits" value in a block header.
*
* @static
* @method fromBits
* @param bits - The number representing the bits value in a block header.
* @param strict - If true, an error is thrown if the number has negative bit set.
* @returns Returns a BigNumber equivalent to the "bits" value in a block header.
* @throws Will throw an error if `strict` is `true` and the number has negative bit set.
*/
static fromBits(bits, strict = false) {
const nSize = bits >>> 24;
const nWordCompact = bits & 0x007fffff;
const isNegativeFromBit = (bits & 0x00800000) !== 0;
if (strict && isNegativeFromBit) {
throw new Error('negative bit set');
}
if (nSize === 0 && nWordCompact === 0) {
if (isNegativeFromBit && strict)
throw new Error('negative bit set for zero value');
return new BigNumber(0n);
}
const bn = new BigNumber(nWordCompact);
// This logic comes from original bn.js `fromCompact`
if (nSize <= 3) {
bn.iushrn((3 - nSize) * 8);
}
else {
bn.iushln((nSize - 3) * 8);
}
if (isNegativeFromBit) {
bn.ineg();
}
return bn;
}
/**
* Converts this BigNumber to a number representing the "bits" value in a block header.
*
* @method toBits
* @returns Returns a number equivalent to the "bits" value in a block header.
*/
toBits() {
this.strip();
if (this.isZero() && !this.isNeg())
return 0;
const isActualNegative = this.isNeg();
const bnAbs = this.abs(); // Work with absolute value for magnitude
// Get byte array of absolute value
let mB = bnAbs.toArray('be'); // Minimal byte array
// Remove leading zeros from byte array, if any (toArray('be') might already do this if no length specified)
let firstNonZeroIdx = 0;
while (firstNonZeroIdx < mB.length - 1 && mB[firstNonZeroIdx] === 0) { // Keep last byte if it's [0]
firstNonZeroIdx++;
}
mB = mB.slice(firstNonZeroIdx);
let nSize = mB.length;
if (nSize === 0 && !bnAbs.isZero()) { // Should not happen if bnAbs is truly non-zero and toArray is correct
mB = [0]; // Should not be needed if toArray works for small numbers
nSize = 1;
}
if (bnAbs.isZero()) { // if original was, e.g., -0, bnAbs is 0.
nSize = 0; // Size for 0 is 0, unless it's negative 0 to be encoded
mB = [];
}
let nWordNum;
if (nSize === 0) {
nWordNum = 0;
}
else if (nSize <= 3) {
nWordNum = 0;
for (let i = 0; i < nSize; i++) {
nWordNum = (nWordNum << 8) | mB[i];
}
}
else { // nSize > 3
nWordNum = (mB[0] << 16) | (mB[1] << 8) | mB[2];
}
if ((nWordNum & 0x00800000) !== 0 && nSize <= 0xff) { // MSB of 3-byte mantissa is set
nWordNum >>>= 8; // Shift mantissa over by one byte
nSize++; // Increase size component by one
}
let b = (nSize << 24) | nWordNum;
if (isActualNegative)
b |= 0x00800000;
return b >>> 0;
}
/**
* Creates a BigNumber from the format used in Bitcoin scripts.
*
* @static
* @method fromScriptNum
* @param num - The number in the format used in Bitcoin scripts.
* @param requireMinimal - If true, non-minimally encoded values will throw an error.
* @param maxNumSize - The maximum allowed size for the number.
* @returns Returns a BigNumber equivalent to the number used in a Bitcoin script.
*/
static fromScriptNum(num, requireMinimal = false, maxNumSize) {
if (maxNumSize !== undefined && num.length > maxNumSize)
throw new Error('script number overflow');
if (num.length === 0)
return new BigNumber(0n);
if (requireMinimal) {
if ((num[num.length - 1] & 0x7f) === 0) {
if (num.length <= 1 || (num[num.length - 2] & 0x80) === 0) {
throw new Error('non-minimally encoded script number');
}
}
}
return BigNumber.fromSm(num, 'little');
}
/**
* Converts this BigNumber to a number in the format used in Bitcoin scripts.
*
* @method toScriptNum
* @returns Returns the equivalent to this BigNumber as a Bitcoin script number.
*/
toScriptNum() { return this.toSm('little'); }
/**
* Compute the multiplicative inverse of the current BigNumber in the modulus field specified by `p`.
* The multiplicative inverse is a number which when multiplied w