diff --git a/packages/bns/package.json b/packages/bns/package.json index b862754d..f974dff9 100755 --- a/packages/bns/package.json +++ b/packages/bns/package.json @@ -40,9 +40,7 @@ "dependencies": { "@stacks/common": "^3.0.0", "@stacks/network": "^3.2.0", - "@stacks/transactions": "^3.2.0", - "@types/bn.js": "^4.11.6", - "bn.js": "^4.12.0" + "@stacks/transactions": "^3.2.0" }, "devDependencies": { "@types/jest": "^26.0.22", diff --git a/packages/bns/src/utils.ts b/packages/bns/src/utils.ts index f0499514..56bb230d 100644 --- a/packages/bns/src/utils.ts +++ b/packages/bns/src/utils.ts @@ -1,8 +1,6 @@ import { Buffer } from '@stacks/common'; import { bufferCV, uintCV, hash160 } from '@stacks/transactions'; -import BN from 'bn.js'; - export function decodeFQN(fqdn: string): { name: string; namespace: string; @@ -30,6 +28,6 @@ export function decodeFQN(fqdn: string): { export const bufferCVFromString = (string: string) => bufferCV(Buffer.from(string)); -export const uintCVFromBN = (int: BN) => uintCV(int.toString(10)); +export const uintCVFromBN = (int: bigint) => uintCV(int.toString(10)); export const getZonefileHash = (zonefile: string) => hash160(Buffer.from(zonefile)); diff --git a/packages/bns/tests/bns.test.ts b/packages/bns/tests/bns.test.ts index 4ed3f776..b154dc5c 100644 --- a/packages/bns/tests/bns.test.ts +++ b/packages/bns/tests/bns.test.ts @@ -30,8 +30,7 @@ import { import {bufferCVFromString, decodeFQN, getZonefileHash, uintCVFromBN} from "../src/utils"; -import BN from "bn.js"; -import { ChainID } from "@stacks/common"; +import { ChainID } from '@stacks/common'; beforeEach(() => { fetchMock.resetMocks(); @@ -356,7 +355,7 @@ test('getNamePrice error', async () => { test('preorderNamespace', async () => { const namespace = 'id'; const salt = 'salt'; - const stxToBurn = new BN(10); + const stxToBurn = BigInt(10); const publicKey = '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab'; const network = new StacksTestnet(); @@ -416,29 +415,29 @@ test('revealNamespace', async () => { const publicKey = '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab'; const priceFunction: PriceFunction = { - base: new BN(10), - coefficient: new BN(1), - b1: new BN(1), - b2: new BN(2), - b3: new BN(3), - b4: new BN(4), - b5: new BN(5), - b6: new BN(6), - b7: new BN(7), - b8: new BN(8), - b9: new BN(9), - b10: new BN(10), - b11: new BN(11), - b12: new BN(12), - b13: new BN(13), - b14: new BN(14), - b15: new BN(15), - b16: new BN(16), - nonAlphaDiscount: new BN(0), - noVowelDiscount: new BN(0), + base: BigInt(10), + coefficient: BigInt(1), + b1: BigInt(1), + b2: BigInt(2), + b3: BigInt(3), + b4: BigInt(4), + b5: BigInt(5), + b6: BigInt(6), + b7: BigInt(7), + b8: BigInt(8), + b9: BigInt(9), + b10: BigInt(10), + b11: BigInt(11), + b12: BigInt(12), + b13: BigInt(13), + b14: BigInt(14), + b15: BigInt(15), + b16: BigInt(16), + nonAlphaDiscount: BigInt(0), + noVowelDiscount: BigInt(0), } - const lifetime = new BN(10000); + const lifetime = BigInt(10000); const namespaceImportAddress = 'SPF0324DSC4K505TP6A8C7GAK4R95E38TGNZP7RE'; const makeUnsignedContractCall = jest.fn().mockResolvedValue({}); @@ -602,7 +601,7 @@ test('readyNamespace', async () => { test('preorderName', async () => { const fullyQualifiedName = 'test.id'; const salt = 'salt'; - const stxToBurn = new BN(10); + const stxToBurn = BigInt(10); const publicKey = '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab'; const makeUnsignedContractCall = jest.fn().mockResolvedValue({}); @@ -957,7 +956,7 @@ test('revokeName', async () => { test('renewName', async () => { const fullyQualifiedName = 'test.id'; - const stxToBurn = new BN(10); + const stxToBurn = BigInt(10); const newOwnerAddress = 'SPF0324DSC4K505TP6A8C7GAK4R95E38TGNZP7RE'; const zonefile = 'zonefile'; const publicKey = '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab'; @@ -1023,7 +1022,7 @@ test('renewName', async () => { test('renewName optionalArguments', async () => { const fullyQualifiedName = 'test.id'; - const stxToBurn = new BN(10); + const stxToBurn = BigInt(10); const newOwnerAddress = undefined; const zonefile = undefined; const publicKey = '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab'; diff --git a/packages/cli/package.json b/packages/cli/package.json index 2c58d8c4..2bf2107f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -102,7 +102,6 @@ "bip39": "^3.0.2", "bitcoinjs-lib": "^5.2.0", "blockstack": "^19.2.2", - "bn.js": "^4.12.0", "c32check": "^1.1.3", "cors": "^2.8.4", "cross-fetch": "^3.1.4", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index a8f772a6..cbf66c2b 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -6,7 +6,6 @@ import * as fs from 'fs'; import * as winston from 'winston'; import cors from 'cors'; -import BN from 'bn.js'; import * as crypto from 'crypto'; import * as bip39 from 'bip39'; import express from 'express'; @@ -372,21 +371,10 @@ function balance(network: CLINetworkAdapter, args: string[]): Promise { return response.json(); }) .then(response => { - let balanceHex = response.balance; - if (response.balance.startsWith('0x')) { - balanceHex = response.balance.substr(2); - } - let lockedHex = response.locked; - if (response.locked.startsWith('0x')) { - lockedHex = response.locked.substr(2); - } - const unlockHeight = response.unlock_height; - const balance = new BN(balanceHex, 16); - const locked = new BN(lockedHex, 16); const res = { - balance: balance.toString(10), - locked: locked.toString(10), - unlock_height: unlockHeight, + balance: BigInt(response.balance).toString(10), + locked: BigInt(response.locked).toString(10), + unlock_height: response.unlock_height, nonce: response.nonce, }; return Promise.resolve(JSONStringify(res)); @@ -539,9 +527,9 @@ function getAccountHistory(network: CLINetworkAdapter, args: string[]): Promise< */ async function sendTokens(network: CLINetworkAdapter, args: string[]): Promise { const recipientAddress = args[0]; - const tokenAmount = new BN(args[1]); - const fee = new BN(args[2]); - const nonce = new BN(args[3]); + const tokenAmount = BigInt(args[1]); + const fee = BigInt(args[2]); + const nonce = BigInt(args[3]); const privateKey = args[4]; let memo = ''; @@ -605,8 +593,8 @@ async function sendTokens(network: CLINetworkAdapter, args: string[]): Promise { const sourceFile = args[0]; const contractName = args[1]; - const fee = new BN(args[2]); - const nonce = new BN(args[3]); + const fee = BigInt(args[2]); + const nonce = BigInt(args[3]); const privateKey = args[4]; const source = fs.readFileSync(sourceFile).toString(); @@ -668,8 +656,8 @@ async function contractFunctionCall(network: CLINetworkAdapter, args: string[]): const contractAddress = args[0]; const contractName = args[1]; const functionName = args[2]; - const fee = new BN(args[3]); - const nonce = new BN(args[4]); + const fee = BigInt(args[3]); + const nonce = BigInt(args[4]); const privateKey = args[5]; // temporary hack to use network config from stacks-transactions lib @@ -1517,7 +1505,7 @@ async function stackingStatus(network: CLINetworkAdapter, args: string[]): Promi } async function canStack(network: CLINetworkAdapter, args: string[]): Promise { - const amount = new BN(args[0]); + const amount = BigInt(args[0]); const cycles = Number(args[1]); const poxAddress = args[2]; const stxAddress = args[3]; @@ -1542,16 +1530,16 @@ async function canStack(network: CLINetworkAdapter, args: string[]): Promise { - const minAmount = new BN(poxInfo.min_amount_ustx); - const balanceBN = new BN(balance.stx.balance); + const minAmount = BigInt(poxInfo.min_amount_ustx); + const balanceBN = BigInt(balance.stx.balance); - if (minAmount.gt(amount)) { + if (minAmount > amount) { throw new Error( `Stacking amount less than required minimum of ${minAmount.toString()} microstacks` ); } - if (amount.gt(balanceBN)) { + if (amount > balanceBN) { throw new Error( `Stacking amount greater than account balance of ${balanceBN.toString()} microstacks` ); @@ -1569,7 +1557,7 @@ async function canStack(network: CLINetworkAdapter, args: string[]): Promise { - const amount = new BN(args[0]); + const amount = BigInt(args[0]); const cycles = Number(args[1]); const poxAddress = args[2]; const privateKey = args[3]; @@ -1610,18 +1598,18 @@ async function stack(network: CLINetworkAdapter, args: string[]): Promise { - const minAmount = new BN(poxInfo.min_amount_ustx); - const balanceBN = new BN(balance.stx.balance); + const minAmount = BigInt(poxInfo.min_amount_ustx); + const balanceBN = BigInt(balance.stx.balance); const burnChainBlockHeight = coreInfo.burn_block_height; const startBurnBlock = burnChainBlockHeight + 3; - if (minAmount.gt(amount)) { + if (minAmount > amount) { throw new Error( `Stacking amount less than required minimum of ${minAmount.toString()} microstacks` ); } - if (amount.gt(balanceBN)) { + if (amount > balanceBN) { throw new Error( `Stacking amount greater than account balance of ${balanceBN.toString()} microstacks` ); diff --git a/packages/cli/src/network.ts b/packages/cli/src/network.ts index 14940f6a..58bfdc7c 100644 --- a/packages/cli/src/network.ts +++ b/packages/cli/src/network.ts @@ -1,6 +1,5 @@ import blockstack from 'blockstack'; import * as bitcoin from 'bitcoinjs-lib'; -import BN from 'bn.js'; import fetch from 'node-fetch'; import { CLI_CONFIG_TYPE } from './argparse'; @@ -22,7 +21,7 @@ export interface CLI_NETWORK_OPTS { export interface PriceType { units: 'BTC' | 'STACKS'; - amount: import('bn.js'); + amount: bigint; } export type NameInfoType = { @@ -144,7 +143,7 @@ export class CLINetworkAdapter { return new Promise((resolve: any) => resolve({ units: String(this.priceUnits), - amount: new BN(this.priceToPay as string), + amount: BigInt(this.priceToPay), } as PriceType) ); } @@ -154,7 +153,7 @@ export class CLINetworkAdapter { if (!priceInfo.units) { priceInfo = { units: 'BTC', - amount: new BN(String(priceInfo)), + amount: BigInt(priceInfo), }; } return priceInfo; @@ -167,7 +166,7 @@ export class CLINetworkAdapter { return new Promise((resolve: any) => resolve({ units: String(this.priceUnits), - amount: new BN(String(this.priceToPay)), + amount: BigInt(this.priceToPay), } as PriceType) ); } @@ -177,7 +176,7 @@ export class CLINetworkAdapter { if (!priceInfo.units) { priceInfo = { units: 'BTC', - amount: new BN(String(priceInfo)), + amount: BigInt(priceInfo), } as PriceType; } return priceInfo; diff --git a/packages/common/package.json b/packages/common/package.json index 61119f31..924c4323 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -34,7 +34,6 @@ }, "dependencies": { "@types/node": "^14.14.43", - "bn.js": "^4.12.0", "buffer": "^6.0.3", "cross-fetch": "^3.1.4" }, diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 06c0fdf2..b99f2ada 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -9,8 +9,6 @@ import { Buffer as BufferPolyfill } from 'buffer/'; // so export using the type definition from NodeJS (@types/node). import type { Buffer as NodeJSBuffer } from 'buffer'; -import BN from 'bn.js'; - const AvailableBufferModule: typeof NodeJSBuffer = // eslint-disable-next-line node/prefer-global/buffer typeof Buffer !== 'undefined' ? Buffer : (BufferPolyfill as any); @@ -331,16 +329,11 @@ export function getGlobalObjects>( return result; } -export type IntegerType = number | string | bigint | Uint8Array | BN; +export type IntegerType = number | string | bigint | Uint8Array; // eslint-disable-next-line node/prefer-global/buffer export function intToBytes(value: IntegerType, signed: boolean, byteLength: number): Buffer { - return intToBN(value, signed).toArrayLike(AvailableBufferModule, 'be', byteLength); -} - -export function intToBN(value: IntegerType, signed: boolean): BN { - const bigInt = intToBigInt(value, signed); - return new BN(bigInt.toString()); + return toBuffer(intToBigInt(value, signed), byteLength); } export function intToBigInt(value: IntegerType, signed: boolean): bigint { @@ -380,17 +373,14 @@ export function intToBigInt(value: IntegerType, signed: boolean): bigint { // Allow byte arrays smaller than 128-bits to be passed. // This allows positive signed ints like `0x08` (8) or negative signed // ints like `0xf8` (-8) to be passed without having to pad to 16 bytes. - const bn = new BN(parsedValue, 'be').fromTwos(parsedValue.byteLength * 8); + const bn = fromTwos(BigInt(`0x${bytesToHex(parsedValue)}`), parsedValue.byteLength * 8); return BigInt(bn.toString()); } else { - return BigInt(new BN(parsedValue, 'be').toString()); + return BigInt(`0x${bytesToHex(parsedValue)}`); } } - if (parsedValue instanceof BN || BN.isBN(parsedValue)) { - return BigInt(parsedValue.toString()); - } throw new TypeError( - `Invalid value type. Must be a number, bigint, integer-string, hex-string, BN.js instance, or Buffer.` + `Invalid value type. Must be a number, bigint, integer-string, hex-string, BigInt instance, or Buffer.` ); } @@ -413,3 +403,93 @@ export function hexToBigInt(hex: string): bigint { export function utf8ToBytes(content: string) { return new TextEncoder().encode(content); } + +/** + * Converts IntegerType to hex string + */ +export const intToHexString = (integer: IntegerType, lengthBytes = 8): string => { + const value = typeof integer === 'bigint' ? integer : intToBigInt(integer, false); + return value.toString(16).padStart(lengthBytes * 2, '0'); +}; + +/** + * Converts hex string to Uint8Array + * @param {hex} hex string without 0x prefix + * @output {Uint8Array} instance of bytes + */ +export function hexToBytes(hex: string): Uint8Array { + if (typeof hex !== 'string') { + throw new TypeError('hexToBytes: expected string, got ' + typeof hex); + } + if (hex.slice(0, 2) === '0x') { + throw new Error('input hex should be without 0x prefix'); + } + if (hex.length % 2) + throw new Error(`hexToBytes: received invalid unpadded hex, got: ${hex.length}`); + + const array = new Uint8Array(hex.length / 2); + + for (let i = 0; i < array.length; i++) { + const j = i * 2; + const hexByte = hex.slice(j, j + 2); + const byte = Number.parseInt(hexByte, 16); + if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence'); + array[i] = byte; + } + return array; +} + +const byteToHexCache: string[] = new Array(0xff); + +for (let n = 0; n <= 0xff; ++n) { + byteToHexCache[n] = n.toString(16).padStart(2, '0'); +} + +/** + * Converts Uint8Array to hex string + */ +export function bytesToHex(uint8a: Uint8Array): string { + const hexOctets = new Array(uint8a.length); + for (let i = 0; i < uint8a.length; ++i) hexOctets[i] = byteToHexCache[uint8a[i]]; + return hexOctets.join(''); +} + +/** + * Converts bigint to buffer type + * @param {value} bigint value to be converted into buffer + * @param {length} buffer optional length + * @return {Buffer} buffer instance in big endian format + */ +export function toBuffer(value: bigint, length: number = 16) { + const hex = intToHexString(value, length); + // buffer instance in big endian format + return AvailableBufferModule.from(hexToBytes(hex)); +} + +/** + * Converts from negative number to two's complement + */ +export function toTwos(value: bigint, size: number): bigint { + // make sure its in range given the number of bits + if ( + value < -(BigInt(1) << (BigInt(size) - BigInt(1))) || + value > (BigInt(1) << (BigInt(size) - BigInt(1))) - BigInt(1) + ) + throw `Integer out of range given ${size} bits to represent.`; + + // if positive, return the positive value + if (value >= BigInt(0)) return BigInt(value); + + // if negative, convert to twos complement representation + const result = ~((-value - BigInt(1)) | ~((BigInt(1) << BigInt(size)) - BigInt(1))); + return BigInt(result); +} + +/** + * Converts from two's complement to negative number + */ +export function fromTwos(value: bigint, size: number) { + if ((value & (BigInt(1) << (BigInt(size) - BigInt(1)))) > BigInt(0)) + value = value - (BigInt(1) << BigInt(size)); + return value; +} diff --git a/packages/common/tests/utils.test.ts b/packages/common/tests/utils.test.ts index 3d67f5a1..6dde8e32 100644 --- a/packages/common/tests/utils.test.ts +++ b/packages/common/tests/utils.test.ts @@ -1,4 +1,13 @@ -import { isSameOriginAbsoluteUrl, isLaterVersion } from '../src' +import { + isSameOriginAbsoluteUrl, + isLaterVersion, + intToHexString, + hexToBytes, + bytesToHex, + fromTwos, + toTwos, + toBuffer +} from '../src' test('isLaterVersion', () => { expect(isLaterVersion('', '1.1.0')).toEqual(false) @@ -21,3 +30,80 @@ test('isSameOriginAbsoluteUrl', () => { expect(isSameOriginAbsoluteUrl('http://example.com', 'http://example.com:1234')).toEqual(false) expect(isSameOriginAbsoluteUrl('http://app.example.com', 'https://example.com/manifest.json')).toEqual(false) }) + +test('intToHexString', () => { + const expected = '0000000000000010'; + + expect(intToHexString(BigInt(16))).toEqual(expected); + expect(intToHexString(16)).toEqual(expected); +}); + +test('hexToBytes & bytesToHex', () => { + const hex = 'ff'; + const bytes = Uint8Array.of(255); + + expect(hexToBytes(hex)).toEqual(bytes); + expect(bytesToHex(bytes)).toEqual(hex); +}); + +test('should return proper buffer', () => { + const n = BigInt('0x123456'); + + expect(toBuffer(n, 5).toString('hex')).toBe('0000123456'); + + const s = '211e1566be78319bb949470577c2d4'; + + for (let i = 1; i <= s.length; i++) { + const slice = (i % 2 === 0 ? '' : '0') + s.slice(0, i); + const bn = BigInt(`0x${slice}`); + expect(toBuffer(bn).toString('hex')).toBe(slice.padStart(32, '0')) + } +}); + +test('should convert from two\'s complement to negative number', () => { + expect(Number(fromTwos(BigInt('0x00000000'),32))).toBe(0); + expect(Number(fromTwos(BigInt('0x00000001'),32))).toBe(1); + expect(Number(fromTwos(BigInt('0x7fffffff'),32))).toBe(2147483647); + expect(Number(fromTwos(BigInt('0x80000000'),32))).toBe(-2147483648); + expect(Number(fromTwos(BigInt('0xf0000000'),32))).toBe(-268435456); + expect(Number(fromTwos(BigInt('0xf1234567'),32))).toBe(-249346713); + expect(Number(fromTwos(BigInt('0xffffffff'),32))).toBe(-1); + expect(Number(fromTwos(BigInt('0xfffffffe'),32))).toBe(-2); + expect(Number(fromTwos(BigInt('0xfffffffffffffffffffffffffffffffe'),128))).toBe(-2); + expect(Number(fromTwos(BigInt('0xffffffffffffffffffffffffffffffff' + + 'fffffffffffffffffffffffffffffffe'),256))).toBe(-2); + expect(Number(fromTwos(BigInt('0xffffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffff'),256))).toBe(-1); + expect( + fromTwos(BigInt('0x7fffffffffffffffffffffffffffffff' + + 'ffffffffffffffffffffffffffffffff'),256).toString(10)) + .toEqual(BigInt('5789604461865809771178549250434395392663499' + + '2332820282019728792003956564819967').toString(10)); + expect( + fromTwos(BigInt('0x80000000000000000000000000000000' + + '00000000000000000000000000000000'),256).toString(10)) + .toEqual(BigInt('-578960446186580977117854925043439539266349' + + '92332820282019728792003956564819968').toString(10)); +}); + +test('should convert from negative number to two\'s complement', () => { + expect(toTwos(BigInt(0), 32).toString(16)).toEqual('0'); + expect(toTwos(BigInt(1), 32).toString(16)).toEqual('1'); + expect(toTwos(BigInt(2147483647), 32).toString(16)).toEqual('7fffffff'); + expect(toTwos(BigInt(-2147483648), 32).toString(16)).toEqual('80000000'); + expect(toTwos(BigInt(-268435456), 32).toString(16)).toEqual('f0000000'); + expect(toTwos(BigInt(-249346713), 32).toString(16)).toEqual('f1234567'); + expect(toTwos(BigInt(-1), 32).toString(16)).toEqual('ffffffff'); + expect(toTwos(BigInt(-2), 32).toString(16)).toEqual('fffffffe'); + expect(toTwos(BigInt(-2), 128).toString(16)).toEqual('fffffffffffffffffffffffffffffffe'); + expect(toTwos(BigInt(-2), 256).toString(16)) + .toEqual('fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe'); + expect(toTwos(BigInt(-1), 256).toString(16)) + .toEqual('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); + expect(toTwos(BigInt('5789604461865809771178549250434395392663' + + '4992332820282019728792003956564819967'), 256).toString(16)) + .toEqual('7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); + expect(toTwos(BigInt('-578960446186580977117854925043439539266' + + '34992332820282019728792003956564819968'), 256).toString(16)) + .toEqual('8000000000000000000000000000000000000000000000000000000000000000'); +}); diff --git a/packages/encryption/package.json b/packages/encryption/package.json index a67b3949..ec2ff541 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -31,11 +31,9 @@ }, "dependencies": { "@stacks/common": "^3.0.0", - "@types/bn.js": "^4.11.6", "@types/node": "^14.14.43", "bip39": "^3.0.2", "bitcoinjs-lib": "^5.2.0", - "bn.js": "^4.12.0", "elliptic": "^6.5.4", "randombytes": "^2.1.0", "ripemd160-min": "^0.0.6", diff --git a/packages/encryption/src/ec.ts b/packages/encryption/src/ec.ts index ef37b0bc..561bedce 100644 --- a/packages/encryption/src/ec.ts +++ b/packages/encryption/src/ec.ts @@ -1,8 +1,7 @@ import { Buffer } from '@stacks/common'; import { ec as EllipticCurve } from 'elliptic'; -import * as BN from 'bn.js'; import { randomBytes } from './cryptoRandom'; -import { FailedDecryptionError } from '@stacks/common'; +import { FailedDecryptionError, toBuffer } from '@stacks/common'; import { getPublicKeyFromPrivate } from './keys'; import { hashSha256Sync, hashSha512Sync } from './sha2Hash'; import { createHmacSha256 } from './hmacSha256'; @@ -162,12 +161,12 @@ function isValidPublicKey(pub: string): { } /** - * Hex encodes a 32-byte BN.js instance. + * Hex encodes a 32-byte bigint instance. * The result string is zero padded and always 64 characters in length. * @ignore */ -export function getHexFromBN(bnInput: BN): string { - const hexOut = bnInput.toString('hex', 64); +export function getHexFromBN(bnInput: bigint): string { + const hexOut = bnInput.toString(16); if (hexOut.length === 64) { return hexOut; } else if (hexOut.length < 64) { @@ -181,14 +180,14 @@ export function getHexFromBN(bnInput: BN): string { } /** - * Returns a big-endian encoded 32-byte BN.js instance. + * Returns a big-endian encoded 32-byte buffer instance. * The result Buffer is zero padded and always 32 bytes in length. * @ignore */ -export function getBufferFromBN(bnInput: BN): Buffer { - const result = bnInput.toArrayLike(Buffer, 'be', 32); +export function getBufferFromBN(bnInput: bigint): Buffer { + const result = toBuffer(bnInput, 32); if (result.byteLength !== 32) { - throw new Error('Failed to generate a 32-byte BN'); + throw new Error('Failed to generate a 32-byte buffer instance'); } return result; } @@ -328,8 +327,8 @@ export async function encryptECIES( const ecPK = ecurve.keyFromPublic(publicKey, 'hex').getPublic(); const ephemeralSK = ecurve.genKeyPair(); const ephemeralPK = Buffer.from(ephemeralSK.getPublic().encodeCompressed()); - const sharedSecret = ephemeralSK.derive(ecPK) as BN; - const sharedSecretBuffer = getBufferFromBN(sharedSecret); + const sharedSecret = ephemeralSK.derive(ecPK).toString(); + const sharedSecretBuffer = getBufferFromBN(BigInt(sharedSecret)); const sharedKeys = sharedSecretToKeys(sharedSecretBuffer); const initializationVector = randomBytes(16); @@ -392,8 +391,8 @@ export async function decryptECIES( ); } - const sharedSecret = ecSK.derive(ephemeralPK) as BN; - const sharedSecretBuffer = getBufferFromBN(sharedSecret); + const sharedSecret = ecSK.derive(ephemeralPK).toString(); + const sharedSecretBuffer = getBufferFromBN(BigInt(sharedSecret)); const sharedKeys = sharedSecretToKeys(sharedSecretBuffer); diff --git a/packages/encryption/tests/encryption.test.ts b/packages/encryption/tests/encryption.test.ts index 7787a916..b514520f 100644 --- a/packages/encryption/tests/encryption.test.ts +++ b/packages/encryption/tests/encryption.test.ts @@ -11,7 +11,6 @@ import * as aesCipher from '../src/aesCipher' import * as sha2Hash from '../src/sha2Hash' import * as hmacSha256 from '../src/hmacSha256' import * as ripemd160 from '../src/hashRipemd160' -import BN from 'bn.js' import { getBufferFromBN } from '../src/ec' const privateKey = 'a5c61c6ca7b3e7e55edee68566aeab22e4da26baa285c7bd10e8d2218aa3b229' @@ -461,15 +460,15 @@ test('bn-padded-to-64-bytes', () => { const ephemeralSK = ecurve.keyFromPrivate(hex) const ephemeralPK = ephemeralSK.getPublic() const sharedSecret = ephemeralSK.derive(ephemeralPK) - return getHexFromBN(sharedSecret).length === 64 + return getHexFromBN(BigInt(sharedSecret.toString())).length === 64 }) expect(results.every(x => x)).toEqual(true) - const bnBuffer = getBufferFromBN(new BN(123)) + const bnBuffer = getBufferFromBN(BigInt(123)) expect(bnBuffer.byteLength).toEqual(32) - expect(bnBuffer.toString('hex')).toEqual(getHexFromBN(new BN(123))) + expect(bnBuffer.toString('hex')).toEqual(getHexFromBN(BigInt(123))) }) test('encryptMnemonic & decryptMnemonic', async () => { diff --git a/packages/keychain/package.json b/packages/keychain/package.json index 1a7b5db4..5702cf14 100644 --- a/packages/keychain/package.json +++ b/packages/keychain/package.json @@ -70,13 +70,11 @@ "@stacks/profile": "^3.2.0", "@stacks/storage": "^3.2.0", "@stacks/transactions": "^3.2.0", - "@types/bn.js": "^4.11.6", "@types/node": "^14.14.43", "@types/triplesec": "^3.0.0", "bip32": "^2.0.4", "bip39": "^3.0.2", "bitcoinjs-lib": "^5.2.0", - "bn.js": "^4.12.0", "c32check": "^1.1.3", "jsontokens": "^3.0.0", "randombytes": "^2.1.0", diff --git a/packages/keychain/src/wallet/signer.ts b/packages/keychain/src/wallet/signer.ts index 9393e8ac..86e9a916 100644 --- a/packages/keychain/src/wallet/signer.ts +++ b/packages/keychain/src/wallet/signer.ts @@ -14,7 +14,6 @@ import { import { StacksTestnet, StacksNetwork, StacksNetworkName } from '@stacks/network'; import RPCClient from '@blockstack/rpc-client'; -import BN from 'bn.js'; interface ContractCallOptions { contractName: string; @@ -120,7 +119,7 @@ export class WalletSigner { codeBody: codeBody, senderKey: this.getSTXPrivateKey().toString('hex'), network: this.getNetwork(), - nonce: new BN(nonce), + nonce: BigInt(nonce), postConditionMode, postConditions, anchorMode, @@ -139,11 +138,11 @@ export class WalletSigner { }: STXTransferOptions) { const tx = await makeSTXTokenTransfer({ recipient, - amount: new BN(amount), + amount: BigInt(amount), memo, senderKey: this.getSTXPrivateKey().toString('hex'), network: this.getNetwork(), - nonce: new BN(nonce), + nonce: BigInt(nonce), postConditionMode, postConditions, anchorMode, diff --git a/packages/stacking/package.json b/packages/stacking/package.json index 5efd80b9..0240aeab 100644 --- a/packages/stacking/package.json +++ b/packages/stacking/package.json @@ -38,9 +38,7 @@ "@stacks/network": "^3.2.0", "@stacks/stacks-blockchain-api-types": "^0.61.0", "@stacks/transactions": "^3.2.0", - "@types/bn.js": "^4.11.6", - "bitcoinjs-lib": "^5.2.0", - "bn.js": "^4.12.0" + "bitcoinjs-lib": "^5.2.0" }, "devDependencies": { "@types/jest": "^26.0.22", diff --git a/packages/stacking/src/index.ts b/packages/stacking/src/index.ts index 727bcee7..44ca7c77 100644 --- a/packages/stacking/src/index.ts +++ b/packages/stacking/src/index.ts @@ -1,4 +1,5 @@ -import { Buffer, IntegerType, intToBigInt } from '@stacks/common'; +// @ts-ignore +import { Buffer, IntegerType, intToBigInt, toBuffer } from '@stacks/common'; import { makeContractCall, bufferCV, @@ -30,7 +31,6 @@ import { BurnchainRewardSlotHolderListResponse, } from '@stacks/stacks-blockchain-api-types'; import { StacksNetwork } from '@stacks/network'; -import BN from 'bn.js'; import { StackingErrors } from './constants'; import { fetchPrivate } from '@stacks/common'; import { decodeBtcAddress } from './utils'; @@ -326,7 +326,7 @@ export class StackingClient { return Promise.all([balancePromise, poxInfoPromise]) .then(([balance, poxInfo]) => { const { hashMode, data } = decodeBtcAddress(poxAddress); - const hashModeBuffer = bufferCV(new BN(hashMode, 10).toArrayLike(Buffer)); + const hashModeBuffer = bufferCV(toBuffer(BigInt(hashMode))); const hashbytes = bufferCV(data); const poxAddressCV = tupleCV({ hashbytes, @@ -528,7 +528,7 @@ export class StackingClient { burnBlockHeight: number; }) { const { hashMode, data } = decodeBtcAddress(poxAddress); - const hashModeBuffer = bufferCV(new BN(hashMode, 10).toArrayLike(Buffer)); + const hashModeBuffer = bufferCV(toBuffer(BigInt(hashMode))); const hashbytes = bufferCV(data); const address = tupleCV({ hashbytes, @@ -566,7 +566,7 @@ export class StackingClient { if (poxAddress) { const { hashMode, data } = decodeBtcAddress(poxAddress); - const hashModeBuffer = bufferCV(new BN(hashMode, 10).toArrayLike(Buffer)); + const hashModeBuffer = bufferCV(toBuffer(BigInt(hashMode))); const hashbytes = bufferCV(data); address = someCV( tupleCV({ @@ -613,7 +613,7 @@ export class StackingClient { nonce?: IntegerType; }) { const { hashMode, data } = decodeBtcAddress(poxAddress); - const hashModeBuffer = bufferCV(new BN(hashMode, 10).toArrayLike(Buffer)); + const hashModeBuffer = bufferCV(toBuffer(BigInt(hashMode))); const hashbytes = bufferCV(data); const address = tupleCV({ hashbytes, @@ -655,7 +655,7 @@ export class StackingClient { rewardCycle: number; }) { const { hashMode, data } = decodeBtcAddress(poxAddress); - const hashModeBuffer = bufferCV(new BN(hashMode, 10).toArrayLike(Buffer)); + const hashModeBuffer = bufferCV(toBuffer(BigInt(hashMode))); const hashbytes = bufferCV(data); const address = tupleCV({ hashbytes, diff --git a/packages/stacking/src/utils.ts b/packages/stacking/src/utils.ts index 890ecf58..4a9c9e80 100644 --- a/packages/stacking/src/utils.ts +++ b/packages/stacking/src/utils.ts @@ -8,7 +8,6 @@ import { TupleCV, } from '@stacks/transactions'; import { address } from 'bitcoinjs-lib'; -import BN from 'bn.js'; import { StackingErrors } from './constants'; export class InvalidAddressError extends Error { @@ -140,7 +139,7 @@ export function poxAddressToBtcAddress(...args: PoxAddressArgs): string { } export function getBTCAddress(version: Buffer, checksum: Buffer) { - const btcAddress = address.toBase58Check(checksum, new BN(version).toNumber()); + const btcAddress = address.toBase58Check(checksum, Number(version.toString())); return btcAddress; } diff --git a/packages/stacking/tests/stacking.test.ts b/packages/stacking/tests/stacking.test.ts index 3dfd8a85..dd7f792f 100644 --- a/packages/stacking/tests/stacking.test.ts +++ b/packages/stacking/tests/stacking.test.ts @@ -1,6 +1,6 @@ import { StacksTestnet } from '@stacks/network'; +import { Buffer, toBuffer } from '@stacks/common'; import fetchMock from 'jest-fetch-mock'; -import BN from 'bn.js'; import { StackingErrors } from '../src/constants'; import { uintCV, @@ -223,7 +223,7 @@ test('stack stx', async () => { const address = 'ST3XKKN4RPV69NN1PHFDNX3TYKXT7XPC4N8KC1ARH'; const poxAddress = '1Xik14zRm29UsyS6DjhYg4iZeZqsDa8D3'; const network = new StacksTestnet(); - const amountMicroStx = new BN(100000000000); + const amountMicroStx = BigInt(100000000000); const cycles = 10; const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; const burnBlockHeight = 2000; @@ -263,7 +263,7 @@ test('stack stx', async () => { }); const { version, hash } = btcAddress.fromBase58Check(poxAddress); - const versionBuffer = bufferCV(new BN(version, 10).toBuffer()); + const versionBuffer = bufferCV(toBuffer(BigInt(version))); const hashbytes = bufferCV(hash); const poxAddressCV = tupleCV({ hashbytes, @@ -299,7 +299,7 @@ test('delegate stx', async () => { const delegateTo = 'ST2MCYPWTFMD2MGR5YY695EJG0G1R4J2BTJPRGM7H'; const poxAddress = '1Xik14zRm29UsyS6DjhYg4iZeZqsDa8D3'; const network = new StacksTestnet(); - const amountMicroStx = new BN(100000000000); + const amountMicroStx = BigInt(100000000000); const untilBurnBlockHeight = 2000; const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; @@ -340,7 +340,7 @@ test('delegate stx', async () => { }); const { version, hash } = btcAddress.fromBase58Check(poxAddress); - const versionBuffer = bufferCV(new BN(version, 10).toBuffer()); + const versionBuffer = bufferCV(toBuffer(BigInt(version))); const hashbytes = bufferCV(hash); const poxAddressCV = tupleCV({ hashbytes, @@ -375,7 +375,7 @@ test('delegate stx with empty optional parameters', async () => { const address = 'ST3XKKN4RPV69NN1PHFDNX3TYKXT7XPC4N8KC1ARH'; const delegateTo = 'ST2MCYPWTFMD2MGR5YY695EJG0G1R4J2BTJPRGM7H'; const network = new StacksTestnet(); - const amountMicroStx = new BN(100000000000); + const amountMicroStx = BigInt(100000000000); const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; const transaction = { serialize: () => 'mocktxhex' } @@ -445,7 +445,7 @@ test('delegate stack stx with one delegator', async () => { const address = 'ST2MCYPWTFMD2MGR5YY695EJG0G1R4J2BTJPRGM7H'; const poxAddress = '1Xik14zRm29UsyS6DjhYg4iZeZqsDa8D3'; const network = new StacksTestnet(); - const amountMicroStx = new BN(100000000000); + const amountMicroStx = BigInt(100000000000); const burnBlockHeight = 2000; const cycles = 10; const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; @@ -497,7 +497,7 @@ test('delegate stack stx with one delegator', async () => { }); const { version, hash } = btcAddress.fromBase58Check(poxAddress); - const versionBuffer = bufferCV(new BN(version, 10).toBuffer()); + const versionBuffer = bufferCV(toBuffer(BigInt(version))); const hashbytes = bufferCV(hash); const poxAddressCV = tupleCV({ hashbytes, @@ -534,11 +534,11 @@ test('delegate stack stx with set nonce', async () => { const address = 'ST2MCYPWTFMD2MGR5YY695EJG0G1R4J2BTJPRGM7H'; const poxAddress = '1Xik14zRm29UsyS6DjhYg4iZeZqsDa8D3'; const network = new StacksTestnet(); - const amountMicroStx = new BN(100000000000); + const amountMicroStx = BigInt(100000000000); const burnBlockHeight = 2000; const cycles = 10; const privateKey = 'd48f215481c16cbe6426f8e557df9b78895661971d71735126545abddcd5377001'; - const nonce = new BN(1); + const nonce = BigInt(1); const transaction = { serialize: () => 'mocktxhex' } const makeContractCall = jest.fn().mockResolvedValue(transaction); @@ -588,7 +588,7 @@ test('delegate stack stx with set nonce', async () => { }); const { version, hash } = btcAddress.fromBase58Check(poxAddress); - const versionBuffer = bufferCV(new BN(version, 10).toBuffer()); + const versionBuffer = bufferCV(toBuffer(BigInt(version))); const hashbytes = bufferCV(hash); const poxAddressCV = tupleCV({ hashbytes, @@ -662,7 +662,7 @@ test('delegator commit', async () => { }); const { version, hash } = btcAddress.fromBase58Check(poxAddress); - const versionBuffer = bufferCV(new BN(version, 10).toBuffer()); + const versionBuffer = bufferCV(toBuffer(BigInt(version))); const hashbytes = bufferCV(hash); const poxAddressCV = tupleCV({ hashbytes, @@ -953,8 +953,9 @@ test('get account balance', async () => { const responseBalanceInfo = await client.getAccountBalance(); expect(fetchMock.mock.calls[0][0]).toEqual(network.getAccountApiUrl(address)); - expect(responseBalanceInfo.toString()).toEqual(new BN(balanceInfo.balance.substr(2), 'hex').toString()); + expect(responseBalanceInfo.toString()).toEqual(BigInt(balanceInfo.balance).toString()); }) + test('get seconds until next cycle', async () => { const address = 'ST3XKKN4RPV69NN1PHFDNX3TYKXT7XPC4N8KC1ARH'; const network = new StacksTestnet(); diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 5c3eb260..971ee420 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -46,11 +46,9 @@ "@noble/secp256k1": "^1.5.2", "@stacks/common": "^3.0.0", "@stacks/network": "^3.2.0", - "@types/bn.js": "^4.11.6", "@types/node": "^14.14.43", "@types/randombytes": "^2.0.0", "@types/sha.js": "^2.4.0", - "bn.js": "^4.12.0", "c32check": "^1.1.3", "cross-fetch": "^3.1.4", "lodash.clonedeep": "^4.5.0", diff --git a/packages/transactions/src/clarity/serialize.ts b/packages/transactions/src/clarity/serialize.ts index 9c1c3f5e..3648aace 100644 --- a/packages/transactions/src/clarity/serialize.ts +++ b/packages/transactions/src/clarity/serialize.ts @@ -1,4 +1,4 @@ -import { Buffer } from '@stacks/common'; +import { Buffer, toTwos, toBuffer } from '@stacks/common'; import { serializeAddress, serializeLPString } from '../types'; import { createLPString } from '../postcondition-types'; import { @@ -19,7 +19,6 @@ import { BufferArray } from '../utils'; import { SerializationError } from '../errors'; import { StringAsciiCV, StringUtf8CV } from './types/stringCV'; import { CLARITY_INT_BYTE_SIZE, CLARITY_INT_SIZE } from '../constants'; -import BN from 'bn.js'; function bufferWithTypeID(typeId: ClarityType, buffer: Buffer): Buffer { const id = Buffer.from([typeId]); @@ -45,14 +44,12 @@ function serializeBufferCV(cv: BufferCV): Buffer { } function serializeIntCV(cv: IntCV): Buffer { - const buffer = new BN(cv.value.toString()) - .toTwos(CLARITY_INT_SIZE) - .toArrayLike(Buffer, 'be', CLARITY_INT_BYTE_SIZE); + const buffer = toBuffer(toTwos(cv.value, CLARITY_INT_SIZE), CLARITY_INT_BYTE_SIZE); return bufferWithTypeID(cv.type, buffer); } function serializeUIntCV(cv: UIntCV): Buffer { - const buffer = new BN(cv.value.toString()).toArrayLike(Buffer, 'be', CLARITY_INT_BYTE_SIZE); + const buffer = toBuffer(cv.value, CLARITY_INT_BYTE_SIZE); return bufferWithTypeID(cv.type, buffer); } diff --git a/packages/transactions/tests/clarity.test.ts b/packages/transactions/tests/clarity.test.ts index 2be16649..f63cfd10 100644 --- a/packages/transactions/tests/clarity.test.ts +++ b/packages/transactions/tests/clarity.test.ts @@ -34,7 +34,6 @@ import { } from '../src/clarity'; import { BufferReader } from '../src/bufferReader'; import { cvToString, cvToJSON, cvToValue, getCVTypeString } from '../src/clarity/clarityValue'; -import BN from 'bn.js'; const ADDRESS = 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B'; @@ -227,7 +226,7 @@ describe('Clarity Types', () => { ['-10', '-10', '0xfffffffffffffffffffffffffffffff6'], ['0xfff6', '-10', '0xfffffffffffffffffffffffffffffff6'], ['0xf6', '-10', '0xfffffffffffffffffffffffffffffff6'], - [new BN(-10), '-10', '0xfffffffffffffffffffffffffffffff6'], + [BigInt(-10), '-10', '0xfffffffffffffffffffffffffffffff6'], [Buffer.from([0xff, 0xf6]), '-10', '0xfffffffffffffffffffffffffffffff6'], [Buffer.from([0xf6]), '-10', '0xfffffffffffffffffffffffffffffff6'], [Buffer.from([0xff, 0xfe]), '-2', '0xfffffffffffffffffffffffffffffffe'], @@ -246,7 +245,7 @@ describe('Clarity Types', () => { ['0x000a', '10', '0x0000000000000000000000000000000a'], ['0xa', '10', '0x0000000000000000000000000000000a'], // hex string with odd padding ['0x00a', '10', '0x0000000000000000000000000000000a'], // hex string with odd padding - [new BN(10), '10', '0x0000000000000000000000000000000a'], + [BigInt(10), '10', '0x0000000000000000000000000000000a'], [Buffer.from([0x0a]), '10', '0x0000000000000000000000000000000a'], [Buffer.from([0x00, 0x0a]), '10', '0x0000000000000000000000000000000a'], [Uint8Array.of(0x0a), '10', '0x0000000000000000000000000000000a'], @@ -329,7 +328,7 @@ describe('Clarity Types', () => { [10n, '10', '0x0000000000000000000000000000000a'], ['10', '10', '0x0000000000000000000000000000000a'], ['0x0a', '10', '0x0000000000000000000000000000000a'], - [new BN(10), '10', '0x0000000000000000000000000000000a'], + [BigInt(10), '10', '0x0000000000000000000000000000000a'], [Buffer.from([0x0a]), '10', '0x0000000000000000000000000000000a'], [Buffer.from([0x00, 0x0a]), '10', '0x0000000000000000000000000000000a'] ] diff --git a/packages/wallet-sdk/package.json b/packages/wallet-sdk/package.json index fc421597..0f0233f8 100644 --- a/packages/wallet-sdk/package.json +++ b/packages/wallet-sdk/package.json @@ -52,7 +52,6 @@ "bip32": "2.0.6", "bip39": "^3.0.2", "bitcoinjs-lib": "^5.1.6", - "bn.js": "^4.12.0", "c32check": "^1.1.3", "jsontokens": "^3.0.0", "randombytes": "^2.1.0",