UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

1,319 lines (1,162 loc) 49.8 kB
// @ts-nocheck import ReductionContext from './ReductionContext.js' /** * 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 */ public static readonly zeros: string[] = [ '', '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 readonly groupSizes: number[] = [ 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 readonly groupBases: number[] = [ 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 readonly wordSize: number = 26 private static readonly WORD_SIZE_BIGINT: bigint = BigInt(BigNumber.wordSize) private static readonly WORD_MASK: bigint = (1n << BigNumber.WORD_SIZE_BIGINT) - 1n private static readonly MAX_SAFE_INTEGER_BIGINT: bigint = BigInt(Number.MAX_SAFE_INTEGER) private static readonly MIN_SAFE_INTEGER_BIGINT: bigint = BigInt(Number.MIN_SAFE_INTEGER) private static readonly MAX_IMULN_ARG: number = 0x4000000 - 1 private static readonly MAX_NUMBER_CONSTRUCTOR_MAG_BIGINT: bigint = (1n << 53n) - 1n private _magnitude: bigint private _sign: 0 | 1 private _nominalWordLength: number /** * Reduction context of the big number. * * @property red */ public red: ReductionContext | null /** * 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 */ public get negative (): number { return this._sign } /** * Sets the negative flag. Only 0 (positive) or 1 (negative) are allowed. */ public set negative (val: number) { 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 } } private get _computedWordsArray (): number[] { if (this._magnitude === 0n) return [0] const arr: number[] = [] 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 */ public get words (): number[] { 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. */ public set words (newWords: number[]) { 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 */ public get length (): number { 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: any): boolean { 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: BigNumber, right: BigNumber): BigNumber { 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: BigNumber, right: BigNumber): BigNumber { 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: number | string | number[] | bigint | undefined = 0, base: number | 'be' | 'le' | 'hex' = 10, endian: 'be' | 'le' = '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: number | 'hex' = base let effectiveEndian: 'be' | 'le' = 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: bigint if (effectiveEndian === 'le') { const bytes: number[] = []; 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 as Error 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) } } private _bigIntToStringInBase (num: bigint, base: number): string { 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 } private _parseBaseString (numberStr: string, base: number): void { 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() } private _parseBaseWord (str: string, base: number): number { 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 } private _initializeState (magnitude: bigint, sign: 0 | 1): void { this._magnitude = magnitude this._sign = (magnitude === 0n) ? 0 : sign this._finishInitialization() } private _finishInitialization (): void { 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)) } } private assert (val: unknown, msg: string = 'Assertion failed'): void { if (!(val as boolean)) throw new Error(msg) } private initNumber (number: number, endian: 'be' | 'le' = 'be'): this { 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 } private initArray (bytes: number[], endian: 'be' | 'le'): this { 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: BigNumber): void { dest._magnitude = this._magnitude; dest._sign = this._sign; dest._nominalWordLength = this._nominalWordLength; dest.red = this.red } static move (dest: BigNumber, src: BigNumber): void { dest._magnitude = src._magnitude; dest._sign = src._sign; dest._nominalWordLength = src._nominalWordLength; dest.red = src.red } clone (): BigNumber { const r = new BigNumber(0n); this.copy(r); return r } expand (size: number): this { this.assert(size >= 0, 'Expand size must be non-negative') this._nominalWordLength = Math.max(this._nominalWordLength, size, 1) return this } strip (): this { this._finishInitialization(); return this.normSign() } normSign (): this { if (this._magnitude === 0n) this._sign = 0; return this } inspect (): string { return (this.red !== null ? '<BN-R: ' : '<BN: ') + this.toString(16) + '>' } private _getMinimalHex (): string { 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: number | 'hex' = 10, padding: number = 1): string { 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) } private toBaseString (base: number, padding: number): string { 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 (): number { 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 (): string { const hex = this._getMinimalHex() return (this.isNeg() ? '-' : '') + hex } private toArrayLikeGeneric (res: number[], isLE: boolean): void { 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: 'le' | 'be' = 'be', length?: number): number[] { 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 (): number { 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: BigNumber): Array<0 | 1> { const len = num.bitLength() if (len === 0) return [] const w = new Array<0 | 1>(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 (): Array<0 | 1> { 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 (): number { 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 (): number { if (this._magnitude === 0n) return 0; return Math.ceil(this.bitLength() / 8) } private _getSignedValue (): bigint { return this._sign === 1 ? -this._magnitude : this._magnitude } private _setValueFromSigned (sVal: bigint): void { if (sVal < 0n) { this._magnitude = -sVal this._sign = 1 } else { this._magnitude = sVal this._sign = 0 } this._finishInitialization() this.normSign() } toTwos (width: number): BigNumber { 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: number): BigNumber { 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 (): boolean { return this._sign === 1 && this._magnitude !== 0n } neg (): BigNumber { return this.clone().ineg() } ineg (): this { if (this._magnitude !== 0n) this._sign = this._sign === 1 ? 0 : 1; return this } private _iuop (num: BigNumber, op: (a: bigint, b: bigint) => bigint): this { const newMag = op(this._magnitude, num._magnitude) const isXor = op === ((a: bigint, b: bigint) => 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: BigNumber): this { return this._iuop(num, (a, b) => a | b) } iuand (num: BigNumber): this { return this._iuop(num, (a, b) => a & b) } iuxor (num: BigNumber): this { return this._iuop(num, (a, b) => a ^ b) } private _iop (num: BigNumber, op: (a: bigint, b: bigint) => bigint): this { this.assert(this._sign === 0 && num._sign === 0); return this._iuop(num, op) } ior (num: BigNumber): this { return this._iop(num, (a, b) => a | b) } iand (num: BigNumber): this { return this._iop(num, (a, b) => a & b) } ixor (num: BigNumber): this { return this._iop(num, (a, b) => a ^ b) } private _uop_new (num: BigNumber, opName: 'iuor' | 'iuand' | 'iuxor'): BigNumber { if (this.length >= num.length) return this.clone()[opName](num); return num.clone()[opName](this) } or (num: BigNumber): BigNumber { this.assert(this._sign === 0 && num._sign === 0); return this._uop_new(num, 'iuor') } uor (num: BigNumber): BigNumber { return this._uop_new(num, 'iuor') } and (num: BigNumber): BigNumber { this.assert(this._sign === 0 && num._sign === 0); return this._uop_new(num, 'iuand') } uand (num: BigNumber): BigNumber { return this._uop_new(num, 'iuand') } xor (num: BigNumber): BigNumber { this.assert(this._sign === 0 && num._sign === 0); return this._uop_new(num, 'iuxor') } uxor (num: BigNumber): BigNumber { return this._uop_new(num, 'iuxor') } inotn (width: number): this { 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: number): BigNumber { return this.clone().inotn(width) } setn (bit: number, val: any): this { 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: BigNumber): this { this._setValueFromSigned(this._getSignedValue() + num._getSignedValue()); return this } add (num: BigNumber): BigNumber { const r = new BigNumber(0n); r._setValueFromSigned(this._getSignedValue() + num._getSignedValue()); return r } isub (num: BigNumber): this { this._setValueFromSigned(this._getSignedValue() - num._getSignedValue()); return this } sub (num: BigNumber): BigNumber { const r = new BigNumber(0n); r._setValueFromSigned(this._getSignedValue() - num._getSignedValue()); return r } mul (num: BigNumber): BigNumber { const r = new BigNumber(0n) r._magnitude = this._magnitude * num._magnitude r._sign = r._magnitude === 0n ? 0 : ((this._sign ^ num._sign) as 0 | 1) r._nominalWordLength = this.length + num.length r.red = null return r.normSign() } imul (num: BigNumber): this { this._magnitude *= num._magnitude this._sign = this._magnitude === 0n ? 0 : ((this._sign ^ num._sign) as 0 | 1) this._nominalWordLength = this.length + num.length this.red = null return this.normSign() } imuln (num: number): this { 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: number): BigNumber { return this.clone().imuln(num) } sqr (): BigNumber { 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 { this._magnitude *= this._magnitude this._sign = 0 this._nominalWordLength = this.length * 2 this.red = null return this } pow (num: BigNumber): BigNumber { 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: number): this { this.assert(typeof bits === 'number' && bits >= 0); if (bits === 0) return this; this._magnitude <<= BigInt(bits); this._finishInitialization(); return this.strip() } ishln (bits: number): this { this.assert(this._sign === 0, 'ishln requires positive number'); return this.iushln(bits) } iushrn (bits: number, hint?: number, extended?: BigNumber): this { 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: number, hint?: number, extended?: BigNumber): this { this.assert(this._sign === 0, 'ishrn requires positive number') return this.iushrn(bits, hint, extended) } shln (bits: number): BigNumber { return this.clone().ishln(bits) } ushln (bits: number): BigNumber { return this.clone().iushln(bits) } shrn (bits: number): BigNumber { return this.clone().ishrn(bits) } ushrn (bits: number): BigNumber { return this.clone().iushrn(bits) } testn (bit: number): boolean { this.assert(typeof bit === 'number' && bit >= 0) return ((this._magnitude >> BigInt(bit)) & 1n) !== 0n } imaskn (bits: number): this { 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: number): BigNumber { return this.clone().imaskn(bits) } iaddn (num: number): this { 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: number): this { return this.iaddn(num) } isubn (num: number): this { 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: number): BigNumber { return this.clone().iaddn(num) } subn (num: number): BigNumber { return this.clone().isubn(num) } iabs (): this { this._sign = 0; return this } abs (): BigNumber { return this.clone().iabs() } divmod (num: BigNumber, mode?: 'div' | 'mod', positive?: boolean): any { 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: bigint | null = null let mV: bigint | null = 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: BigNumber): BigNumber { return this.divmod(num, 'div', false).div as BigNumber } mod (num: BigNumber): BigNumber { return this.divmod(num, 'mod', false).mod as BigNumber } umod (num: BigNumber): BigNumber { return this.divmod(num, 'mod', true).mod as BigNumber } divRound (num: BigNumber): BigNumber { 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: number): number { 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: number): this { 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: number): BigNumber { return this.clone().idivn(num) } egcd (p: BigNumber): { a: BigNumber, b: BigNumber, gcd: BigNumber } { 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: BigNumber): BigNumber { 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: BigNumber): BigNumber { 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 (): boolean { return this._magnitude % 2n === 0n } isOdd (): boolean { return this._magnitude % 2n === 1n } andln (num: number): number { this.assert(num >= 0); return Number(this._magnitude & BigInt(num)) } bincn (bit: number): this { this.assert(typeof bit === 'number' && bit >= 0); const BVal = 1n << BigInt(bit); this._setValueFromSigned(this._getSignedValue() + BVal); return this } isZero (): boolean { return this._magnitude === 0n } cmpn (num: number): 1 | 0 | -1 { 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: BigNumber): 1 | 0 | -1 { const tV = this._getSignedValue(); const nV = num._getSignedValue(); if (tV < nV) return -1; if (tV > nV) return 1; return 0 } ucmp (num: BigNumber): 1 | 0 | -1 { if (this._magnitude < num._magnitude) return -1; if (this._magnitude > num._magnitude) return 1; return 0 } gtn (num: number): boolean { return this.cmpn(num) === 1 } gt (num: BigNumber): boolean { return this.cmp(num) === 1 } gten (num: number): boolean { return this.cmpn(num) >= 0 } gte (num: BigNumber): boolean { return this.cmp(num) >= 0 } ltn (num: number): boolean { return this.cmpn(num) === -1 } lt (num: BigNumber): boolean { return this.cmp(num) === -1 } lten (num: number): boolean { return this.cmpn(num) <= 0 } lte (num: BigNumber): boolean { return this.cmp(num) <= 0 } eqn (num: number): boolean { return this.cmpn(num) === 0 } eq (num: BigNumber): boolean { return this.cmp(num) === 0 } toRed (ctx: ReductionContext): BigNumber { 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 (): BigNumber { this.assert(this.red, 'fromRed works only with numbers in reduction context'); return this.red.convertFrom(this) } forceRed (ctx: ReductionContext): this { this.red = ctx; return this } redAdd (num: BigNumber): BigNumber { this.assert(this.red, 'redAdd works only with red numbers'); return this.red.add(this, num) } redIAdd (num: BigNumber): BigNumber { this.assert(this.red, 'redIAdd works only with red numbers'); return this.red.iadd(this, num) } redSub (num: BigNumber): BigNumber { this.assert(this.red, 'redSub works only with red numbers'); return this.red.sub(this, num) } redISub (num: BigNumber): BigNumber { this.assert(this.red, 'redISub works only with red numbers'); return this.red.isub(this, num) } redShl (num: number): BigNumber { this.assert(this.red, 'redShl works only with red numbers'); return this.red.shl(this, num) } redMul (num: BigNumber): BigNumber { this.assert(this.red, 'redMul works only with red numbers'); this.red.verify2(this, num); return this.red.mul(this, num) } redIMul (num: BigNumber): BigNumber { this.assert(this.red, 'redIMul works only with red numbers'); this.red.verify2(this, num); return this.red.imul(this, num) } redSqr (): BigNumber { this.assert(this.red, 'redSqr works only with red numbers'); this.red.verify1(this); return this.red.sqr(this) } redISqr (): BigNumber { this.assert(this.red, 'redISqr works only with red numbers'); this.red.verify1(this); return this.red.isqr(this) } redSqrt (): BigNumber { this.assert(this.red, 'redSqrt works only with red numbers'); this.red.verify1(this); return this.red.sqrt(this) } redInvm (): BigNumber { this.assert(this.red, 'redInvm works only with red numbers'); this.red.verify1(this); return this.red.invm(this) } redNeg (): BigNumber { this.assert(this.red, 'redNeg works only with red numbers'); this.red.verify1(this); return this.red.neg(this) } redPow (num: BigNumber): BigNumber { 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: string, endian?: 'le' | 'be' | 'little' | 'big'): BigNumber { let eE: 'le' | 'be' = '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: number = 0): string { 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: string): BigNumber { 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: number): BigNumber { 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: string, base?: number | 'hex'): BigNumber { 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: number[], endian: 'big' | 'little' = 'big'): BigNumber { if (bytes.length === 0) return new BigNumber(0n) let sign: 0 | 1 = 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' | 'little' = 'big'): number[] { 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: number, strict: boolean = false): BigNumber { 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 (): number { 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: number[], requireMinimal: boolean = false, maxNumSize?: number ): BigNumber { 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 (): number[] { 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 with the current BigNumber gives '1' in the modulus field. * * @method _invmp * @param p - The `BigNumber` specifying the modulus field. * @returns The multiplicative inverse `BigNumber` in the modulus field specified by `p`. */ _invmp (p: BigNumber): BigNumber { this.assert(p._sign === 0, 'p must not be negative for _invmp') this.assert(!p.isZero(), 'p must not be zero for _invmp') const aBN: BigNumber = this.umod(p) let aVal = aBN._magnitude let bVal = p._magnitude let x1Val = 1n let x2Val = 0n const modulus = p._magnitude while (aVal > 1n && bVal > 1n) { let i = 0; while (((aVal >> BigInt(i)) & 1n) === 0n) i++ if (i > 0) { aVal >>= BigInt(i) for (let k = 0; k < i; ++k) { if ((x1Val & 1n) !== 0n) x1Val += modulus; x1Val >>= 1n } } let j = 0; while (((bVal >> BigInt(j)) & 1n) === 0n) j++ if (j > 0) { bVal >>= BigInt(j) for (let k = 0; k < j; ++k) { if ((x2Val & 1n) !== 0n) x2Val += modulus; x2Val >>= 1n } } if (aVal >= bVal) { aVal -= bVal; x1Val -= x2Val } else { bVal -= aVal; x2Val -= x1Val } } let resultVal: bigint if (aVal === 1n) resultVal = x1Val else if (bVal === 1n) resultVal = x2Val else if (aVal === 0n && bVal === 1n) resultVal = x2Val else if (bVal === 0n && aVal === 1n) resultVal = x1Val else throw new Error('_invmp: GCD is not 1, inverse does not exist. aVal=' + aVal + ', bVal=' + bVal) resultVal %= modulus if (resultVal < 0n) resultVal += modulus const resultBN = new BigNumber(0n) resultBN._initializeState(resultVal, 0) return resultBN } /** * Performs multiplication between the BigNumber instance and a given BigNumber. * It chooses the multiplication method based on the lengths of the numbers to optimize execution time. * * @method mulTo * @param num - The BigNumber multiply with. * @param out - The BigNumber where to store the result. * @returns The BigNumber resulting from the multiplication operation. */ mulTo (num: BigNumber, out: BigNumber): BigNumber { out._magnitude = this._magnitude * num._magnitude out._sign = out._magnitude === 0n ? 0 : ((this._sign ^ num._sign) as 0 | 1) out._nominalWordLength = this.length + num.length out.red = null out.normSign() return out } }