From fa5b5bca7e8e7538605734f3969d492aabb703f4 Mon Sep 17 00:00:00 2001 From: Jaco Date: Tue, 16 Nov 2021 19:27:19 +0100 Subject: [PATCH] Additional bigint utilities (#1194) * Additional bigint utilities * Adjust tests * biTo{Hex, U8a} * Adjust * Ordering * biSqrt * Fix toU8a * Extensible BigInt * Class TS issues.... * Move type def to types * Update packages/util/src/bi/sqrt.spec.ts * max/min * min/max tests --- packages/util/src/bi/class.spec.ts | 69 +++++++++++++++++++++++ packages/util/src/bi/class.ts | 22 ++++++++ packages/util/src/bi/consts.ts | 26 +++++++++ packages/util/src/bi/index.ts | 16 ++++++ packages/util/src/bi/max.spec.ts | 30 ++++++++++ packages/util/src/bi/max.ts | 22 ++++++++ packages/util/src/bi/min.spec.ts | 24 ++++++++ packages/util/src/bi/min.ts | 22 ++++++++ packages/util/src/bi/sqrt.spec.ts | 42 ++++++++++++++ packages/util/src/bi/sqrt.ts | 41 ++++++++++++++ packages/util/src/bi/toBigInt.spec.ts | 67 ++++++++++++++++++++++ packages/util/src/bi/toBigInt.ts | 31 ++++++++++ packages/util/src/bi/toHex.spec.ts | 48 ++++++++++++++++ packages/util/src/bi/toHex.ts | 33 +++++++++++ packages/util/src/bi/toU8a.spec.ts | 54 ++++++++++++++++++ packages/util/src/bi/toU8a.ts | 81 +++++++++++++++++++++++++++ packages/util/src/bn/toBn.ts | 27 ++++----- packages/util/src/bundle.ts | 1 + packages/util/src/is/bigInt.ts | 2 +- packages/util/src/is/index.ts | 1 + packages/util/src/is/toBigInt.spec.ts | 20 +++++++ packages/util/src/is/toBigInt.ts | 10 ++++ packages/util/src/types.ts | 24 ++++++++ packages/util/src/u8a/toBigInt.ts | 8 +-- 24 files changed, 703 insertions(+), 18 deletions(-) create mode 100644 packages/util/src/bi/class.spec.ts create mode 100644 packages/util/src/bi/class.ts create mode 100644 packages/util/src/bi/consts.ts create mode 100644 packages/util/src/bi/index.ts create mode 100644 packages/util/src/bi/max.spec.ts create mode 100644 packages/util/src/bi/max.ts create mode 100644 packages/util/src/bi/min.spec.ts create mode 100644 packages/util/src/bi/min.ts create mode 100644 packages/util/src/bi/sqrt.spec.ts create mode 100644 packages/util/src/bi/sqrt.ts create mode 100644 packages/util/src/bi/toBigInt.spec.ts create mode 100644 packages/util/src/bi/toBigInt.ts create mode 100644 packages/util/src/bi/toHex.spec.ts create mode 100644 packages/util/src/bi/toHex.ts create mode 100644 packages/util/src/bi/toU8a.spec.ts create mode 100644 packages/util/src/bi/toU8a.ts create mode 100644 packages/util/src/is/toBigInt.spec.ts create mode 100644 packages/util/src/is/toBigInt.ts diff --git a/packages/util/src/bi/class.spec.ts b/packages/util/src/bi/class.spec.ts new file mode 100644 index 000000000..3c94ba016 --- /dev/null +++ b/packages/util/src/bi/class.spec.ts @@ -0,0 +1,69 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { isBigInt } from '../is'; +import { BigIntClass } from '.'; + +describe('BigIntClass', (): void => { + it('has the correct valueOf', (): void => { + expect( + typeof new BigIntClass(0).valueOf() + ).toEqual('bigint'); + }); + + it('has the correct instanceof', (): void => { + expect( + isBigInt(new BigIntClass(0)) + ).toEqual(true); + }); + + it('has a top-level toString method', (): void => { + const test = new BigIntClass(16); + + expect(test.toString()).toEqual('16'); + expect(test.toString(16)).toEqual('10'); + }); + + it('cane be added together', (): void => { + expect(new BigIntClass(16) + new BigIntClass(10)).toEqual(26n); + }); + + it('has the correct type for inherited', (): void => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + class Test extends BigIntClass implements BigInt { + } + + expect(isBigInt(new Test(0x16))).toEqual(true); + }); + + it('inhertited class has own methods', (): void => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + class Test extends BigIntClass { + toHex (): string { + return `0x${(this as unknown as bigint).toString(16)}`; + } + } + + expect(new Test(0x16).toHex()).toEqual('0x16'); + }); + + it('inhertited class still acts like bigint', (): void => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + class Test extends BigIntClass { + // nothing + } + + const test = new Test(0x16n); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(test.toString(16)).toEqual('16'); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(test * 2n).toEqual(44n); + }); +}); diff --git a/packages/util/src/bi/class.ts b/packages/util/src/bi/class.ts new file mode 100644 index 000000000..440d174e3 --- /dev/null +++ b/packages/util/src/bi/class.ts @@ -0,0 +1,22 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { IBigIntConstructor } from '../types'; + +// See https://stackoverflow.com/questions/58207487/extend-object-wrappers-for-modern-primitives + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +export class BigIntImpl extends BigInt implements BigInt { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + constructor (value: string | number | bigint | boolean): bigint { + const self = Object(BigInt(value)) as bigint; + + Object.setPrototypeOf(self, new.target.prototype); + + return self; + } +} + +export const BigIntClass = BigIntImpl as IBigIntConstructor; diff --git a/packages/util/src/bi/consts.ts b/packages/util/src/bi/consts.ts new file mode 100644 index 000000000..c2c86d66a --- /dev/null +++ b/packages/util/src/bi/consts.ts @@ -0,0 +1,26 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +/** + * @name BI_MILLION + * @summary BigInt constant for 1,000,000. + */ +export const BI_MILLION = BigInt(1_000_000); + +/** +* @name BI_BILLION +* @summary BigInt constant for 1,000,000,000. +*/ +export const BI_BILLION = BigInt(1_000_000_000); + +/** +* @name BI_QUINTILL +* @summary BigInt constant for 1,000,000,000,000,000,000. +*/ +export const BI_QUINTILL = BI_BILLION * BI_BILLION; + +/** +* @name BI_MAX_INTEGER +* @summary BigInt constant for MAX_SAFE_INTEGER +*/ +export const BI_MAX_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); diff --git a/packages/util/src/bi/index.ts b/packages/util/src/bi/index.ts new file mode 100644 index 000000000..d37ac6a8a --- /dev/null +++ b/packages/util/src/bi/index.ts @@ -0,0 +1,16 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +/** + * @summary Utility methods to convert to and from `bigint` objects + */ + +export * from './consts'; + +export { BigIntClass } from './class'; +export { biSqrt } from './sqrt'; +export { biToBigInt } from './toBigInt'; +export { biMax } from './max'; +export { biMin } from './min'; +export { biToHex } from './toHex'; +export { biToU8a } from './toU8a'; diff --git a/packages/util/src/bi/max.spec.ts b/packages/util/src/bi/max.spec.ts new file mode 100644 index 000000000..19494ae07 --- /dev/null +++ b/packages/util/src/bi/max.spec.ts @@ -0,0 +1,30 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { biMax } from '.'; + +describe('biMax', (): void => { + it('finds maximum', (): void => { + expect( + biMax(1n, 2n, 3n) + ).toEqual(3n); + }); + + it('finds maximum', (): void => { + expect( + biMax(2n, 3n, 1n) + ).toEqual(3n); + }); + + it('returns a single item', (): void => { + expect( + biMax(1n) + ).toEqual(1n); + }); + + it('fails when no items are available', (): void => { + expect( + () => biMax() + ).toThrow(/Must provide one or more bigint arguments/); + }); +}); diff --git a/packages/util/src/bi/max.ts b/packages/util/src/bi/max.ts new file mode 100644 index 000000000..4d604f03a --- /dev/null +++ b/packages/util/src/bi/max.ts @@ -0,0 +1,22 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { assert } from '../assert'; + +/** + * @name biMax + * @summary Finds and returns the highest value in an array of bigint. + */ +export function biMax (...items: bigint[]): bigint { + assert(items.length >= 1, 'Must provide one or more bigint arguments'); + + let result = items[0]; + + for (let i = 1; i < items.length; i++) { + if (items[i] > result) { + result = items[i]; + } + } + + return result; +} diff --git a/packages/util/src/bi/min.spec.ts b/packages/util/src/bi/min.spec.ts new file mode 100644 index 000000000..8fef630c0 --- /dev/null +++ b/packages/util/src/bi/min.spec.ts @@ -0,0 +1,24 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { biMin } from '.'; + +describe('biMin', (): void => { + it('finds BN minimum', (): void => { + expect( + biMin(2n, 1n, 3n) + ).toEqual(1n); + }); + + it('returns a single item', (): void => { + expect( + biMin(1n) + ).toEqual(1n); + }); + + it('fails when no items are available', (): void => { + expect( + () => biMin() + ).toThrow(/Must provide one or more bigint arguments/); + }); +}); diff --git a/packages/util/src/bi/min.ts b/packages/util/src/bi/min.ts new file mode 100644 index 000000000..57e2bc3e8 --- /dev/null +++ b/packages/util/src/bi/min.ts @@ -0,0 +1,22 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { assert } from '../assert'; + +/** + * @name biMin + * @summary Finds and returns the lowest value in an array of bigint. + */ +export function biMin (...items: bigint[]): bigint { + assert(items.length >= 1, 'Must provide one or more bigint arguments'); + + let result = items[0]; + + for (let i = 1; i < items.length; i++) { + if (items[i] < result) { + result = items[i]; + } + } + + return result; +} diff --git a/packages/util/src/bi/sqrt.spec.ts b/packages/util/src/bi/sqrt.spec.ts new file mode 100644 index 000000000..33981dcc2 --- /dev/null +++ b/packages/util/src/bi/sqrt.spec.ts @@ -0,0 +1,42 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { BN } from '../bn'; +import { biSqrt } from '.'; + +const TESTS: [string | number | BN | bigint, string | number][] = [ + [0, 0], + [1, 1], + [4, 2], + [256 * 256, 256], + [Number.MAX_SAFE_INTEGER, 94906265], + [new BN(Number.MAX_SAFE_INTEGER).iaddn(256), 94906265], + [12345678n * 12345679n, 12345678], + // test cases below from https://github.com/Aisse-258/bigint-isqrt/blob/f5254b9750841959022461c1353437a07a08f501/test/sqrt-test.js + [ + '54866395443885995655625', + '234235768925' + ], + [ + '82120471531550314555681345949499512621827274120673745141541602816614526075010755373654280259022317599142038423759320355177481886719814621305828811322920076213800348341464996337890625', + '9062034624274524065844376014975805577107171799890766992670739972241112960081909332275390625' + ], + [ + '2068290204957779940494571454815902632050207752627007664813877111611972735314294907791993008455381265730624848057015476004143868408297250386512042711005577373327434315310235583583151773358353534031397385969275475421659036494173765903706963544628565027679444680637920371210258368358706248446698622704138943704561842220533563976838506992543814273403403362954987589637283890281226038798503294069403370486523697009383229578343444337394508531937972775639593763364402389410142290440075517598007930025487176173228232153685095699851764630884360483051570513804913157899314303269924424908606245851172031453725690942288677622973423417902662711606364188470325930406288190754356367655606430407552275285889968860367977288564364154999338284288438279464648663835830140826171534470717958048887869721791862539529444820484648908879364324577951008050851801647916673254009490596379549192296404841454780884358889449367135382779230574781333008561195244620152128505091883408484094800737142399855337799331037607433934520934135669364954550499058123797911762228642423567268540444910163468496805547450495819213412636594319148417857753712526066272727725420886898680432958409892237191284583904066400736417812258327303739879128916456683685232170600928808798462776174857486589598776587550843543720784829419478239563366491666155716208452087857080883906200829769222868347286071365156305396955420561718593130305703074908891135467193627721682044757187980688062367562451981939375400543212890625', + '45478458691536369397924851088318871329614555665525810477032568598360481742967756704524034986984134271202405190906906195123751965012732122548540879928668891416468847696038707003466943811913626146981446753434347552196868717612248915137033581428310504778371173112125038060138252719589122657221314190626986951627417768579641354255445988772645883238873672997347648890137913979299598785628581961589739331483895853398160785485300758458294999124984266041998145956341386549681176624661615465976970855592467853824812945820721933491609237311130118125761849911376495970746282571994312288380577879004620824004760109618074670810797544446334767103935754106103208789391060008291027361311679876439695844637933763721093782805837690830230712890625' + ] +]; + +describe('biSqrt', (): void => { + it('fails on < 0 roots', (): void => { + expect( + () => biSqrt(-1n) + ).toThrow(/negative numbers is not supported/); + }); + + TESTS.forEach(([value, expected], index): void => { + it(`calcs for test #${index}`, (): void => { + expect(biSqrt(value) === BigInt(expected)).toEqual(true); + }); + }); +}); diff --git a/packages/util/src/bi/sqrt.ts b/packages/util/src/bi/sqrt.ts new file mode 100644 index 000000000..b1671b0c7 --- /dev/null +++ b/packages/util/src/bi/sqrt.ts @@ -0,0 +1,41 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { BN } from '../bn'; +import type { ToBigInt, ToBn } from '../types'; + +import { assert } from '../assert'; +import { BI_MAX_INTEGER } from './consts'; +import { biToBigInt } from './toBigInt'; + +const SQRT_MAX_SAFE_INTEGER = 94906265n; + +/** + * @name biSqrt + * @summary Calculates the integer square root of a bigint + */ +export function biSqrt (value: ExtToBn | BN | bigint | string | number | null): bigint { + const n = biToBigInt(value); + + assert(n >= 0n, 'square root of negative numbers is not supported'); + + // https://stackoverflow.com/questions/53683995/javascript-big-integer-square-root/ + // shortcut <= 2^53 - 1 to use the JS utils + if (n <= BI_MAX_INTEGER) { + return BigInt(Math.floor(Math.sqrt(Number(n)))); + } + + // Use sqrt(MAX_SAFE_INTEGER) as starting point. since we already know the + // output will be larger than this, we expect this to be a safe start + let x0 = SQRT_MAX_SAFE_INTEGER; + + while (true) { + const x1 = ((n / x0) + x0) >> 1n; + + if (x0 === x1 || (x0 === (x1 - 1n))) { + return x0; + } + + x0 = x1; + } +} diff --git a/packages/util/src/bi/toBigInt.spec.ts b/packages/util/src/bi/toBigInt.spec.ts new file mode 100644 index 000000000..e17cde2b7 --- /dev/null +++ b/packages/util/src/bi/toBigInt.spec.ts @@ -0,0 +1,67 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { BN } from '../bn'; +import { biToBigInt } from '.'; + +describe('biToBigInt', (): void => { + it('converts null values to 0x00', (): void => { + expect( + biToBigInt(null) + ).toEqual(0n); + }); + + it('converts 0x values to 0x00', (): void => { + expect( + biToBigInt('0x') + ).toEqual(0n); + }); + + it('converts BN values to bigint', (): void => { + expect( + biToBigInt(new BN(128)) + ).toEqual(128n); + }); + + it('converts BigInt values to bigint', (): void => { + expect( + biToBigInt(128821n) + ).toEqual(128821n); + }); + + it('converts number values to bigint', (): void => { + expect( + biToBigInt(128) + ).toEqual(128n); + }); + + it('converts string to bigint', (): void => { + expect( + biToBigInt('123') + ).toEqual(123n); + }); + + it('converts hex to bigint', (): void => { + expect( + biToBigInt('0x0123') + ).toEqual(0x123n); + }); + + it('converts Compact to bigint (via toBn)', (): void => { + expect( + biToBigInt({ + something: 'test', + toBn: () => new BN(1234) + }) + ).toEqual(1234n); + }); + + it('converts Compact to bigint (via toBigInt)', (): void => { + expect( + biToBigInt({ + something: 'test', + toBigInt: () => 1234n + }) + ).toEqual(1234n); + }); +}); diff --git a/packages/util/src/bi/toBigInt.ts b/packages/util/src/bi/toBigInt.ts new file mode 100644 index 000000000..0d8d537ae --- /dev/null +++ b/packages/util/src/bi/toBigInt.ts @@ -0,0 +1,31 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { BN } from '../bn/bn'; +import type { HexString, ToBigInt, ToBn } from '../types'; + +import { hexToBigInt } from '../hex/toBigInt'; +import { isBn } from '../is/bn'; +import { isHex } from '../is/hex'; +import { isToBigInt } from '../is/toBigInt'; +import { isToBn } from '../is/toBn'; + +/** + * @name biToBigInt + * @summary Creates a bigInt value from a BN, bigint, string (base 10 or hex) or number input. + */ +export function biToBigInt (value?: HexString | ExtToBn | BN | bigint | string | number | null): bigint { + return typeof value === 'bigint' + ? value + : !value + ? BigInt(0) + : isHex(value) + ? hexToBigInt(value.toString()) + : isBn(value) + ? BigInt(value.toString()) + : isToBigInt(value) + ? value.toBigInt() + : isToBn(value) + ? BigInt(value.toBn().toString()) + : BigInt(value); +} diff --git a/packages/util/src/bi/toHex.spec.ts b/packages/util/src/bi/toHex.spec.ts new file mode 100644 index 000000000..23dc8a0d2 --- /dev/null +++ b/packages/util/src/bi/toHex.spec.ts @@ -0,0 +1,48 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { biToHex } from '.'; + +describe('biToHex', (): void => { + it('converts null values to 0x00', (): void => { + expect( + biToHex(null) + ).toBe('0x00'); + }); + + it('converts values to a prefixed hex representation', (): void => { + expect( + biToHex(128) + ).toBe('0x80'); + }); + + it('converts values to a prefixed hex representation (bitLength)', (): void => { + expect( + biToHex(128n, { bitLength: 16 }) + ).toBe('0x0080'); + }); + + it('converts values to a prefixed hex representation (bitLength + le)', (): void => { + expect( + biToHex(128, { bitLength: 16, isLe: true }) + ).toBe('0x8000'); + }); + + it('converts values to a prefixed hex representation (LE)', (): void => { + expect( + biToHex(128, { bitLength: 16, isLe: true }) + ).toBe('0x8000'); + }); + + it('handles negative numbers', (): void => { + expect( + biToHex(-1234, { isNegative: true }) + ).toBe('0xfb2e'); + }); + + it('handles negative numbers (with bitLength)', (): void => { + expect( + biToHex(-1234, { bitLength: 32, isNegative: true }) + ).toBe('0xfffffb2e'); + }); +}); diff --git a/packages/util/src/bi/toHex.ts b/packages/util/src/bi/toHex.ts new file mode 100644 index 000000000..0c026738b --- /dev/null +++ b/packages/util/src/bi/toHex.ts @@ -0,0 +1,33 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { BN } from '../bn/bn'; +import type { HexString, ToBigInt, ToBn, ToBnOptions } from '../types'; + +import { objectSpread } from '../object/spread'; +import { u8aToHex } from '../u8a'; +import { biToU8a } from './toU8a'; + +interface Options extends ToBnOptions { + bitLength?: number; +} + +const ZERO_STR = '0x00'; + +/** + * @name biToHex + * @summary Creates a hex value from a bigint object. + */ +export function biToHex (value?: ExtToBn | BN | bigint | number | null, options?: Options): HexString { + if (!value) { + return ZERO_STR; + } + + return u8aToHex( + biToU8a(value, objectSpread( + // We spread here, the default for hex values is BE (JSONRPC via substrate) + { isLe: false, isNegative: false }, + options + )) + ); +} diff --git a/packages/util/src/bi/toU8a.spec.ts b/packages/util/src/bi/toU8a.spec.ts new file mode 100644 index 000000000..169780053 --- /dev/null +++ b/packages/util/src/bi/toU8a.spec.ts @@ -0,0 +1,54 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { biToU8a } from '.'; + +describe('biToU8a', (): void => { + it('converts null values to 0x00', (): void => { + expect( + biToU8a(null) + ).toEqual(new Uint8Array()); + }); + + it('converts null values to 0x00000000 (bitLength)', (): void => { + expect( + biToU8a(null, { bitLength: 32 }) + ).toEqual(new Uint8Array([0, 0, 0, 0])); + }); + + it('converts values to a prefixed hex representation', (): void => { + expect( + biToU8a(0x123456n, { isLe: false }) + ).toEqual(new Uint8Array([0x12, 0x34, 0x56])); + }); + + it('converts values to a prefixed hex representation (bitLength)', (): void => { + expect( + biToU8a(0x123456n, { bitLength: 32, isLe: false }) + ).toEqual(new Uint8Array([0x00, 0x12, 0x34, 0x56])); + }); + + it('converts using little endian (as set)', (): void => { + expect( + biToU8a(0x123456n, { bitLength: 32, isLe: true }) + ).toEqual(new Uint8Array([0x56, 0x34, 0x12, 0x00])); + }); + + it('converts negative numbers', (): void => { + expect( + biToU8a(-1234n, { isNegative: true }) + ).toEqual(new Uint8Array([46, 251])); + }); + + it('converts negative numbers (BE)', (): void => { + expect( + biToU8a(-1234n, { isLe: false, isNegative: true }) + ).toEqual(new Uint8Array([251, 46])); + }); + + it('converts negative numbers (bitLength)', (): void => { + expect( + biToU8a(-1234n, { bitLength: 32, isNegative: true }) + ).toEqual(new Uint8Array([46, 251, 255, 255])); + }); +}); diff --git a/packages/util/src/bi/toU8a.ts b/packages/util/src/bi/toU8a.ts new file mode 100644 index 000000000..01ddad9f3 --- /dev/null +++ b/packages/util/src/bi/toU8a.ts @@ -0,0 +1,81 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { BN } from '../bn/bn'; +import type { ToBigInt, ToBn, ToBnOptions } from '../types'; + +import { objectSpread } from '../object/spread'; +import { biToBigInt } from './toBigInt'; + +interface Options extends ToBnOptions { + bitLength?: number; +} + +const DIV = 256n; + +function createEmpty ({ bitLength = 0 }: Options): Uint8Array { + return bitLength === -1 + ? new Uint8Array() + : new Uint8Array(Math.ceil(bitLength / 8)); +} + +function toU8a (value: bigint, { isLe, isNegative }: Options): Uint8Array { + const arr: number[] = []; + + if (isNegative) { + value = (value + 1n) * -1n; + } + + while (value !== 0n) { + const mod = value % DIV; + const val = Number( + isNegative + ? mod ^ 0xffn + : mod + ); + + if (isLe) { + arr.push(val); + } else { + arr.unshift(val); + } + + value = (value - mod) / DIV; + } + + return Uint8Array.from(arr); +} + +/** + * @name biToU8a + * @summary Creates a Uint8Array object from a bigint. + */ +export function biToU8a (value?: ExtToBn | BN | bigint | number | null, options?: Options): Uint8Array { + const opts: Options = objectSpread( + { bitLength: -1, isLe: true, isNegative: false }, + options + ); + + const valueBi = biToBigInt(value); + + if (valueBi === 0n) { + return createEmpty(opts); + } + + const u8a = toU8a(valueBi, opts); + + if (opts.bitLength === -1) { + return u8a; + } + + const byteLength = Math.ceil((opts.bitLength || 0) / 8); + const output = new Uint8Array(byteLength); + + if (opts.isNegative) { + output.fill(0xff); + } + + output.set(u8a, opts.isLe ? 0 : byteLength - u8a.length); + + return output; +} diff --git a/packages/util/src/bn/toBn.ts b/packages/util/src/bn/toBn.ts index f497877aa..27f276097 100644 --- a/packages/util/src/bn/toBn.ts +++ b/packages/util/src/bn/toBn.ts @@ -1,11 +1,12 @@ // Copyright 2017-2021 @polkadot/util authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { HexString, ToBn } from '../types'; +import type { HexString, ToBigInt, ToBn } from '../types'; import { hexToBn } from '../hex/toBn'; import { isBigInt } from '../is/bigInt'; import { isHex } from '../is/hex'; +import { isToBigInt } from '../is/toBigInt'; import { isToBn } from '../is/toBn'; import { BN } from './bn'; @@ -25,18 +26,18 @@ import { BN } from './bn'; * bnToBn(new BN(0x1234)); // => BN(0x1234) * ``` */ -export function bnToBn (value?: HexString | ExtToBn | BN | bigint | string | number | null): BN { - if (!value) { - return new BN(0); - } else if (isHex(value)) { - return hexToBn(value.toString()); - } else if (isBigInt(value)) { - return new BN(value.toString()); - } - +export function bnToBn (value?: HexString | ExtToBn | BN | bigint | string | number | null): BN { return BN.isBN(value) ? value - : isToBn(value) - ? value.toBn() - : new BN(value); + : !value + ? new BN(0) + : isHex(value) + ? hexToBn(value.toString()) + : isBigInt(value) + ? new BN(value.toString()) + : isToBn(value) + ? value.toBn() + : isToBigInt(value) + ? new BN(value.toBigInt().toString()) + : new BN(value); } diff --git a/packages/util/src/bundle.ts b/packages/util/src/bundle.ts index 31490bb73..6009f785e 100644 --- a/packages/util/src/bundle.ts +++ b/packages/util/src/bundle.ts @@ -8,6 +8,7 @@ export { packageInfo } from './packageInfo'; export * from './array'; export * from './assert'; +export * from './bi'; export * from './bn'; export * from './buffer'; export * from './compact'; diff --git a/packages/util/src/is/bigInt.ts b/packages/util/src/is/bigInt.ts index 4820a4676..4d3ac588a 100644 --- a/packages/util/src/is/bigInt.ts +++ b/packages/util/src/is/bigInt.ts @@ -16,5 +16,5 @@ * ``` */ export function isBigInt (value: unknown): value is bigint { - return typeof value === 'bigint'; + return typeof value === 'bigint' || value instanceof BigInt; } diff --git a/packages/util/src/is/index.ts b/packages/util/src/is/index.ts index 870e9ebb4..0ace71039 100644 --- a/packages/util/src/is/index.ts +++ b/packages/util/src/is/index.ts @@ -24,6 +24,7 @@ export { isObject } from './object'; export { isObservable } from './observable'; export { isString } from './string'; export { isTestChain } from './testChain'; +export { isToBigInt } from './toBigInt'; export { isToBn } from './toBn'; export { isU8a } from './u8a'; export { isUndefined } from './undefined'; diff --git a/packages/util/src/is/toBigInt.spec.ts b/packages/util/src/is/toBigInt.spec.ts new file mode 100644 index 000000000..bbb821a76 --- /dev/null +++ b/packages/util/src/is/toBigInt.spec.ts @@ -0,0 +1,20 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { isToBigInt } from '.'; + +describe('isToBigInt', (): void => { + it('is false on no value', (): void => { + expect(isToBigInt()).toEqual(false); + }); + + it('is false on non-compact value', (): void => { + expect(isToBigInt(123)).toEqual(false); + }); + + it('is true when compact-like signature is found', (): void => { + expect(isToBigInt({ + toBigInt: () => BigInt(1) + })).toEqual(true); + }); +}); diff --git a/packages/util/src/is/toBigInt.ts b/packages/util/src/is/toBigInt.ts new file mode 100644 index 000000000..3b24ec9d0 --- /dev/null +++ b/packages/util/src/is/toBigInt.ts @@ -0,0 +1,10 @@ +// Copyright 2017-2021 @polkadot/util authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ToBigInt } from '../types'; + +import { isFunction } from './function'; + +export function isToBigInt (value?: unknown): value is ToBigInt { + return !!value && isFunction((value as ToBigInt).toBigInt); +} diff --git a/packages/util/src/types.ts b/packages/util/src/types.ts index 0342f0b7d..393ba7a9c 100644 --- a/packages/util/src/types.ts +++ b/packages/util/src/types.ts @@ -9,6 +9,10 @@ export interface Constructor { new(...value: any[]): T; } +export interface ToBigInt { + toBigInt: () => bigint; +} + export interface ToBn { toBn: () => BN; } @@ -61,3 +65,23 @@ export type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' export type HexString = `0x${string}`; export type U8aLike = HexString | number[] | Buffer | Uint8Array | AnyString; + +export interface IBigIntConstructor { + new (value: string | number | bigint | boolean): bigint; + + /** + * Interprets the low bits of a BigInt as a 2's-complement signed integer. + * All higher bits are discarded. + * @param bits The number of low bits to use + * @param int The BigInt whose bits to extract + */ + asIntN (bits: number, int: bigint): bigint; + + /** + * Interprets the low bits of a BigInt as an unsigned integer. + * All higher bits are discarded. + * @param bits The number of low bits to use + * @param int The BigInt whose bits to extract + */ + asUintN (bits: number, int: bigint): bigint; +} diff --git a/packages/util/src/u8a/toBigInt.ts b/packages/util/src/u8a/toBigInt.ts index dcf66217d..1d4e13408 100644 --- a/packages/util/src/u8a/toBigInt.ts +++ b/packages/util/src/u8a/toBigInt.ts @@ -5,8 +5,8 @@ import type { ToBnOptions } from '../types'; import { objectSpread } from '../object/spread'; -const U8_MULT = 256n; -const U16_MUL = U8_MULT * U8_MULT; +const U8_MAX = 256n; +const U16_MAX = U8_MAX * U8_MAX; function xor (input: Uint8Array): Uint8Array { const result = new Uint8Array(input.length); @@ -33,11 +33,11 @@ function toBigInt (input: Uint8Array): bigint { let result = BigInt(0); for (let i = 0; i < length; i += 2) { - result = (result * U16_MUL) + BigInt(dvI.getUint16(i)); + result = (result * U16_MAX) + BigInt(dvI.getUint16(i)); } if (mod) { - result = (result * U8_MULT) + BigInt(dvI.getUint8(length)); + result = (result * U8_MAX) + BigInt(dvI.getUint8(length)); } return result;