UNPKG

libnexa-ts

Version:

A pure and powerful Nexa SDK library.

262 lines (222 loc) 8.2 kB
import BN from "bn.js"; import ValidationUtils from "../utils/validation.utils"; import BufferUtils from "../utils/buffer.utils"; import type { BNOptions } from "../common/interfaces"; export default class BNExtended extends BN { public static Zero = new BNExtended(0); public static One = new BNExtended(1); public static Minus1 = new BNExtended(-1); public static fromNumber(num: number): BNExtended { ValidationUtils.validateArgument(typeof num === 'number', "num"); return new BNExtended(num); } public static fromBigInt(num: bigint): BNExtended { ValidationUtils.validateArgument(typeof num === 'bigint', "num"); return new BNExtended(num.toString()); } public static fromString(str: string, base?: number | "hex"): BNExtended { ValidationUtils.validateArgument(typeof str === 'string', "str"); return new BNExtended(str, base); } public static fromBuffer(buf: Uint8Array, opts?: BNOptions): BNExtended { ValidationUtils.validateArgument(BufferUtils.isBuffer(buf), "buf"); if (opts?.endian === 'little') { buf = BufferUtils.reverse(buf); } return new BNExtended(BufferUtils.bufferToHex(buf), 16); } /** * Create a BN from a "ScriptNum": * This is analogous to the constructor for CScriptNum in nexad. Many ops in * nexad's script interpreter use CScriptNum, which is not really a proper * bignum. Instead, an error is thrown if trying to input a number bigger than * 4 bytes. We copy that behavior here. A third argument, `size`, is provided to * extend the hard limit of 4 bytes, as some usages require more than 4 bytes. */ public static fromScriptNumBuffer(buf: Uint8Array, fRequireMinimal?: boolean, size?: number): BNExtended { let nMaxNumSize = size || 4; ValidationUtils.validateArgument(buf.length <= nMaxNumSize, 'script number overflow'); if (fRequireMinimal && buf.length > 0) { // Check that the number is encoded with the minimum possible // number of bytes. // // If the most-significant-byte - excluding the sign bit - is zero // then we're not minimal. Note how this test also rejects the // negative-zero encoding, 0x80. if ((buf[buf.length - 1] & 0x7f) === 0) { // One exception: if there's more than one byte and the most // significant bit of the second-most-significant-byte is set // it would conflict with the sign bit. An example of this case // is +-255, which encode to 0xff00 and 0xff80 respectively. // (big-endian). if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) { throw new Error('non-minimally encoded script number'); } } } return BNExtended.fromSM(buf, { endian: 'little' }); } // Override arithmetic methods to ensure they return BNExtended instances public override add(b: BN): BNExtended { const result = super.add(b).toString(); return new BNExtended(result); } public override sub(b: BN): BNExtended { const result = super.sub(b).toString(); return new BNExtended(result); } public override mul(b: BN): BNExtended { const result = super.mul(b).toString(); return new BNExtended(result); } public override mod(b: BN): BNExtended { const result = super.mod(b).toString(); return new BNExtended(result); } public override umod(b: BN): BNExtended { const result = super.umod(b).toString(); return new BNExtended(result); } public override invm(b: BN): BNExtended { const result = super.invm(b).toString(); return new BNExtended(result); } public override neg(): BNExtended { const result = super.neg().toString(); return new BNExtended(result); } public override toNumber(): number { return parseInt(this.toString(10), 10); } public toBigInt(): bigint { return BigInt(this.toString(10)) } public toByteArray(opts?: BN.Endianness | BNOptions, length?: number): Uint8Array { if (typeof opts === 'string') { return Uint8Array.from(super.toArray(opts, length)); } let hex = this.toString(16, 2); let buf = BufferUtils.hexToBuffer(hex); if (opts?.size) { let natlen = hex.length / 2; if (natlen > opts.size) { buf = BNExtended.trim(buf, natlen); } else if (natlen < opts.size) { buf = BNExtended.pad(buf, natlen, opts.size); } } if (opts?.endian === 'little') { buf = BufferUtils.reverse(buf); } return buf; } /** * The corollary to the above, with the notable exception that we do not throw * an error if the output is larger than four bytes. (Which can happen if * performing a numerical operation that results in an overflow to more than 4 * bytes). */ public toScriptNumBuffer(): Uint8Array { return this.toSM({ endian: 'little' }); } public toScriptBigNumBuffer(): Uint8Array { return this.toSM({ endian: 'little', bignum: true }); } public getSize(): number { const bin = this.toString(2).replace('-', ''); const numBits = bin.length + 1; return numBits / 8; } public safeAdd(bigNumToAdd: BNExtended, maxSize: number): BNExtended { const sum = this.add(bigNumToAdd); this.checkOperationForOverflow(bigNumToAdd, sum, maxSize); return sum; } public safeSub(bigNumToSubtract: BNExtended, maxSize: number): BNExtended { const difference = this.sub(bigNumToSubtract); this.checkOperationForOverflow(bigNumToSubtract, difference, maxSize); return difference; } public safeMul(bigNumToMultiply: BNExtended, maxSize: number): BNExtended { const product = this.mul(bigNumToMultiply); this.checkOperationForOverflow(bigNumToMultiply, product, maxSize); return product; } private checkOperationForOverflow(operand: BNExtended, result: BNExtended, maxSize: number): void { if (this.getSize() > maxSize || operand.getSize() > maxSize || result.getSize() > 8) { throw new Error('overflow'); } } private toSMBigEndian(): Uint8Array { let buf; if (this.cmp(BNExtended.Zero) === -1) { buf = this.neg().toByteArray(); if (buf[0] & 0x80) { buf = BufferUtils.concat([Uint8Array.from([0x80]), buf]); } else { buf[0] = buf[0] | 0x80; } } else { buf = this.toByteArray(); if (buf[0] & 0x80) { buf = BufferUtils.concat([Uint8Array.from([0x00]), buf]); } } if (buf.length === 1 && buf[0] === 0) { buf = Uint8Array.from([]); } return buf; } private toBigNumSMBigEndian(): Uint8Array { let buf; if (this.cmp(BNExtended.Zero) === -1) { buf = this.neg().toByteArray(); buf = BufferUtils.concat([Uint8Array.from([0x80]), buf]); } else { buf = this.toByteArray(); buf = BufferUtils.concat([Uint8Array.from([0x00]), buf]); } return buf; } private toSM(opts: BNOptions): Uint8Array { let buf = opts?.bignum ? this.toBigNumSMBigEndian() : this.toSMBigEndian(); if (opts?.endian === 'little') { buf = BufferUtils.reverse(buf); } return buf; } /** * Instantiate a BigNumber from a "signed magnitude buffer" * (a buffer where the most significant bit represents the sign (0 = positive, -1 = negative)) */ private static fromSM(buf: Uint8Array, opts?: BNOptions): BNExtended { if (buf.length === 0) { return this.fromBuffer(Uint8Array.from([0])); } if (opts?.endian === 'little') { buf = BufferUtils.reverse(buf); } let ret; if (buf[0] & 0x80) { buf[0] = buf[0] & 0x7f; ret = this.fromBuffer(buf); ret.neg().copy(ret); } else { ret = this.fromBuffer(buf); } return ret; } private static trim(buf: Uint8Array, natlen: number): Uint8Array { return buf.subarray(natlen - buf.length, natlen); } private static pad(buf: Uint8Array, natlen: number, size: number): Uint8Array { let rbuf = new Uint8Array(size); for (let i = 0; i < buf.length; i++) { rbuf[rbuf.length - 1 - i] = buf[buf.length - 1 - i]; } for (let i = 0; i < size - natlen; i++) { rbuf[i] = 0; } return rbuf; } }