UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

1,250 lines 52.9 kB
/** * 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