@nomicfoundation/ethereumjs-util
Advanced tools
Comparing version 9.0.3 to 9.0.4
{ | ||
"name": "@nomicfoundation/ethereumjs-util", | ||
"version": "9.0.3", | ||
"version": "9.0.4", | ||
"description": "A collection of utility functions for Ethereum", | ||
@@ -64,4 +64,5 @@ "keywords": [ | ||
], | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"type": "commonjs", | ||
"main": "dist/cjs/index.js", | ||
"module": "dist/esm/index.js", | ||
"files": [ | ||
@@ -74,25 +75,30 @@ "dist", | ||
"clean": "../../config/cli/clean-package.sh", | ||
"coverage": "../../config/cli/coverage.sh", | ||
"docs:build": "npx typedoc --options typedoc.js", | ||
"coverage": "DEBUG=ethjs npx vitest run --coverage.enabled --coverage.reporter=lcov", | ||
"docs:build": "npx typedoc --options typedoc.cjs", | ||
"examples": "tsx ../../scripts/examples-runner.ts -- util", | ||
"examples:build": "npx embedme README.md", | ||
"lint": "../../config/cli/lint.sh", | ||
"lint:diff": "../../config/cli/lint-diff.sh", | ||
"lint:fix": "../../config/cli/lint-fix.sh", | ||
"tape": "tape -r ts-node/register", | ||
"test": "npm run test:node && npm run test:browser", | ||
"test:browser": "karma start karma.conf.js", | ||
"test:node": "npm run tape -- test/*.spec.ts", | ||
"test:browser": "npx vitest run --config=./vitest.config.browser.ts --browser.name=chrome --browser.headless", | ||
"test:node": "npx vitest run", | ||
"tsc": "../../config/cli/ts-compile.sh" | ||
}, | ||
"dependencies": { | ||
"@chainsafe/ssz": "^0.10.0", | ||
"@nomicfoundation/ethereumjs-rlp": "5.0.3", | ||
"@nomicfoundation/ethereumjs-rlp": "5.0.4", | ||
"ethereum-cryptography": "0.1.3" | ||
}, | ||
"devDependencies": { | ||
"@types/bn.js": "^5.1.0", | ||
"@types/secp256k1": "^4.0.1" | ||
"devDependencies": {}, | ||
"peerDependencies": { | ||
"c-kzg": "^2.1.2" | ||
}, | ||
"peerDependenciesMeta": { | ||
"c-kzg": { | ||
"optional": true | ||
} | ||
}, | ||
"engines": { | ||
"node": ">=14" | ||
"node": ">=18" | ||
} | ||
} |
246
README.md
@@ -9,3 +9,4 @@ # @ethereumjs/util | ||
A collection of utility functions for Ethereum. It can be used in Node.js and in the browser with [browserify](http://browserify.org/). | ||
| A collection of utility functions for Ethereum. | | ||
| ----------------------------------------------- | | ||
@@ -22,11 +23,161 @@ ## Installation | ||
```js | ||
import assert from 'assert' | ||
import { isValidChecksumAddress, unpadBuffer } from '@ethereumjs/util' | ||
This package contains the following modules providing respective helper methods, classes and commonly re-used constants. | ||
assert.ok(isValidChecksumAddress('0x2F015C60E0be116B1f0CD534704Db9c92118FB6A')) | ||
All helpers are re-exported from the root level and deep imports are not necessary. So an import can be done like this: | ||
assert.ok(unpadBuffer(Buffer.from('000000006600', 'hex')).equals(Buffer.from('6600', 'hex'))) | ||
```ts | ||
import { hexToBytes, isValidChecksumAddress } from '@ethereumjs/util' | ||
``` | ||
### Module: [account](src/account.ts) | ||
Class representing an `Account` and providing private/public key and address-related functionality (creation, validation, conversion). | ||
```ts | ||
// ./examples/account.ts | ||
import { Account } from '@ethereumjs/util' | ||
const account = Account.fromAccountData({ | ||
nonce: '0x02', | ||
balance: '0x0384', | ||
storageRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', | ||
codeHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', | ||
}) | ||
console.log(`Account with nonce=${account.nonce} and balance=${account.balance} created`) | ||
``` | ||
### Module: [address](src/address.ts) | ||
Class representing an Ethereum `Address` with instantiation helpers and validation methods. | ||
```ts | ||
// ./examples/address.ts | ||
import { Address } from '@ethereumjs/util' | ||
const address = Address.fromString('0x2f015c60e0be116b1f0cd534704db9c92118fb6a') | ||
console.log(`Ethereum address ${address.toString()} created`) | ||
``` | ||
### Module: [blobs](src/blobs.ts) | ||
Module providing helpers for 4844 blobs and versioned hashes. | ||
```ts | ||
// ./examples/blobs.ts | ||
import { bytesToHex, computeVersionedHash, getBlobs } from '@ethereumjs/util' | ||
const blobs = getBlobs('test input') | ||
console.log('Created the following blobs:') | ||
console.log(blobs) | ||
const commitment = new Uint8Array([1, 2, 3]) | ||
const blobCommitmentVersion = 0x01 | ||
const versionedHash = computeVersionedHash(commitment, blobCommitmentVersion) | ||
console.log(`Versioned hash ${bytesToHex(versionedHash)} computed`) | ||
``` | ||
### Module: [bytes](src/bytes.ts) | ||
Byte-related helper and conversion functions. | ||
```ts | ||
// ./examples/bytes.ts | ||
import { bytesToBigInt } from '@ethereumjs/util' | ||
const bytesValue = new Uint8Array([97]) | ||
const bigIntValue = bytesToBigInt(bytesValue) | ||
console.log(`Converted value: ${bigIntValue}`) | ||
``` | ||
### Module: [constants](src/constants.ts) | ||
Exposed constants (e.g. `KECCAK256_NULL_S` for string representation of Keccak-256 hash of null) | ||
```ts | ||
// ./examples/constants.ts | ||
import { BIGINT_2EXP96, KECCAK256_NULL_S } from '@ethereumjs/util' | ||
console.log(`The keccak-256 hash of null: ${KECCAK256_NULL_S}`) | ||
console.log(`BigInt constants (performance), e.g. BIGINT_2EXP96: ${BIGINT_2EXP96}`) | ||
``` | ||
### Module: [db](src/db.ts) | ||
DB interface for database abstraction (Blockchain, Trie), see e.g. [@ethereumjs/trie recipes](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/trie/recipes/level.ts)) for usage. | ||
### Module: [genesis](src/genesis.ts) | ||
Genesis related interfaces and helpers. | ||
### Module: [internal](src/internal.ts) | ||
Internalized simple helper methods like `isHexPrefixed`. Note that methods from this module might get deprectared in the future. | ||
### Module: [kzg](src/kzg.ts) | ||
KZG interface (used for 4844 blob txs), see [@ethereumjs/tx](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/tx/README.md#kzg-setup) README for main usage instructions. | ||
### Module: [mapDB](src/mapDB.ts) | ||
Simple map DB implementation using the `DB` interface (see above). | ||
### Module: [signature](src/signature.ts) | ||
Functionality for signing, signature validation, conversion, recovery. | ||
```ts | ||
// ./examples/signature.ts | ||
import { bytesToHex, ecrecover, hexToBytes } from '@ethereumjs/util' | ||
const chainId = BigInt(3) // Ropsten | ||
const echash = hexToBytes('0x82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28') | ||
const r = hexToBytes('0x99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9') | ||
const s = hexToBytes('0x129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66') | ||
const v = BigInt(41) | ||
const pubkey = ecrecover(echash, v, r, s, chainId) | ||
console.log(`Recovered public key ${bytesToHex(pubkey)} from valid signature values`) | ||
``` | ||
### Module: [types](src/types.ts) | ||
Various TypeScript types. Direct usage is not recommended, type structure might change in the future. | ||
### Module: [withdrawal](src/withdrawal.ts) | ||
Class representing an `EIP-4895` `Withdrawal` with different constructors as well as conversion and output helpers. | ||
```ts | ||
// ./examples/withdrawal.ts | ||
import { Withdrawal } from '@ethereumjs/util' | ||
const withdrawal = Withdrawal.fromWithdrawalData({ | ||
index: 0n, | ||
validatorIndex: 65535n, | ||
address: '0x0000000000000000000000000000000000000000', | ||
amount: 0n, | ||
}) | ||
console.log('Withdrawal object created:') | ||
console.log(withdrawal.toJSON()) | ||
``` | ||
## Browser | ||
With the breaking release round in Summer 2023 we have added hybrid ESM/CJS builds for all our libraries (see section below) and have eliminated many of the caveats which had previously prevented a frictionless browser usage. | ||
It is now easily possible to run a browser build of one of the EthereumJS libraries within a modern browser using the provided ESM build. For a setup example see [./examples/browser.html](./examples/browser.html). | ||
## API | ||
@@ -38,28 +189,65 @@ | ||
### Modules | ||
### Upgrade Helpers in bytes-Module | ||
- [account](src/account.ts) | ||
- Account class | ||
- Private/public key and address-related functionality (creation, validation, conversion) | ||
- [address](src/address.ts) | ||
- Address class and type | ||
- [bytes](src/bytes.ts) | ||
- Byte-related helper and conversion functions | ||
- [constants](src/constants.ts) | ||
- Exposed constants | ||
- e.g. `KECCAK256_NULL_S` for string representation of Keccak-256 hash of null | ||
- hash | ||
- This module has been removed with `v8`, please use [ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography) directly instead | ||
- [signature](src/signature.ts) | ||
- Signing, signature validation, conversion, recovery | ||
- [types](src/types.ts) | ||
- Helpful TypeScript types | ||
- [internal](src/internal.ts) | ||
- Internalized helper methods | ||
- [withdrawal](src/withdrawal.ts) | ||
- Withdrawal class (EIP-4895) | ||
Depending on the extend of `Buffer` usage within your own libraries and other planning considerations, there are the two upgrade options to do the switch to `Uint8Array` yourself or keep `Buffer` and do transitions for input and output values. | ||
We have updated the `@ethereumjs/util` `bytes` module with helpers for the most common conversions: | ||
```ts | ||
Buffer.alloc(97) // Allocate a Buffer with length 97 | ||
new Uint8Array(97) // Allocate a Uint8Array with length 97 | ||
Buffer.from('342770c0', 'hex') // Convert a hex string to a Buffer | ||
hexToBytes('0x342770c0') // Convert a prefixed hex string to a Uint8Array, Util.hexToBytes() | ||
`0x${myBuffer.toString('hex')}` // Convert a Buffer to a prefixed hex string | ||
bytesToHex(myUint8Array) // Convert a Uint8Array to a prefixed hex string | ||
intToBuffer(9) // Convert an integer to a Buffer, old (removed) | ||
intToBytes(9) // Convert an integer to a Uint8Array, Util.intToBytes() | ||
bytesToInt(myUint8Array) // Convert a Uint8Array to an integer, Util.bytesToInt() | ||
bigIntToBytes(myBigInt) // Convert a BigInt to a Uint8Array, Util.bigIntToBytes() | ||
bytesToBigInt(myUint8Array) // Convert a Uint8Array to a BigInt, Util.bytesToInt() | ||
utf8ToBytes(myUtf8String) // Converts a UTF-8 string to a Uint8Array, Util.utf8ToBytes() | ||
bytesToUtf8(myUint8Array) // Converts a Uint8Array to a UTF-8 string, Util.bytesToUtf8() | ||
toBuffer(v: ToBufferInputTypes) // Converts various byte compatible types to Buffer, old (removed) | ||
toBytes(v: ToBytesInputTypes) // Converts various byte compatible types to Uint8Array, Util.toBytes() | ||
``` | ||
Helper methods can be imported like this: | ||
```ts | ||
import { hexToBytes } from '@ethereumjs/util' | ||
``` | ||
### Hybrid CJS/ESM Builds | ||
With the breaking releases from Summer 2023 we have started to ship our libraries with both CommonJS (`cjs` folder) and ESM builds (`esm` folder), see `package.json` for the detailed setup. | ||
If you use an ES6-style `import` in your code files from the ESM build will be used: | ||
```ts | ||
import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]' | ||
``` | ||
If you use Node.js specific `require`, the CJS build will be used: | ||
```ts | ||
const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]') | ||
``` | ||
Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking which CJS can not provide. | ||
### Buffer -> Uint8Array | ||
With the breaking releases from Summer 2023 we have removed all Node.js specific `Buffer` usages from our libraries and replace these with [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) representations, which are available both in Node.js and the browser (`Buffer` is a subclass of `Uint8Array`). | ||
We have converted existing Buffer conversion methods to Uint8Array conversion methods in the [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) `bytes` module, see the respective README section for guidance. | ||
### BigInt Support | ||
Starting with v8 the usage of [BN.js](https://github.com/indutny/bn.js/) for big numbers has been removed from the library and replaced with the usage of the native JS [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) data type (introduced in `ES2020`). | ||
Starting with Util v8 the usage of [BN.js](https://github.com/indutny/bn.js/) for big numbers has been removed from the library and replaced with the usage of the native JS [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) data type (introduced in `ES2020`). | ||
@@ -86,3 +274,3 @@ Please note that number-related API signatures have changed along with this version update and the minimal build target has been updated to `ES2020`. | ||
```typescript | ||
```ts | ||
import { stripHexPrefix } from '@ethereumjs/util' | ||
@@ -89,0 +277,0 @@ ``` |
import { RLP } from '@nomicfoundation/ethereumjs-rlp' | ||
import { keccak256 } from 'ethereum-cryptography/keccak' | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js' | ||
import { | ||
@@ -11,26 +11,26 @@ privateKeyVerify, | ||
import { | ||
arrToBufArr, | ||
bigIntToUnpaddedBuffer, | ||
bufArrToArr, | ||
bufferToBigInt, | ||
bufferToHex, | ||
toBuffer, | ||
bigIntToUnpaddedBytes, | ||
bytesToBigInt, | ||
bytesToHex, | ||
concatBytes, | ||
equalsBytes, | ||
hexToBytes, | ||
toBytes, | ||
utf8ToBytes, | ||
zeros, | ||
} from './bytes' | ||
import { KECCAK256_NULL, KECCAK256_RLP } from './constants' | ||
import { assertIsBuffer, assertIsHexString, assertIsString } from './helpers' | ||
import { stripHexPrefix } from './internal' | ||
} from './bytes.js' | ||
import { BIGINT_0, KECCAK256_NULL, KECCAK256_RLP } from './constants.js' | ||
import { assertIsBytes, assertIsHexString, assertIsString } from './helpers.js' | ||
import { stripHexPrefix } from './internal.js' | ||
import type { BigIntLike, BufferLike } from './types' | ||
import type { BigIntLike, BytesLike } from './types.js' | ||
const _0n = BigInt(0) | ||
export interface AccountData { | ||
nonce?: BigIntLike | ||
balance?: BigIntLike | ||
storageRoot?: BufferLike | ||
codeHash?: BufferLike | ||
storageRoot?: BytesLike | ||
codeHash?: BytesLike | ||
} | ||
export type AccountBodyBuffer = [Buffer, Buffer, Buffer | Uint8Array, Buffer | Uint8Array] | ||
export type AccountBodyBytes = [Uint8Array, Uint8Array, Uint8Array, Uint8Array] | ||
@@ -40,4 +40,4 @@ export class Account { | ||
balance: bigint | ||
storageRoot: Buffer | ||
codeHash: Buffer | ||
storageRoot: Uint8Array | ||
codeHash: Uint8Array | ||
@@ -48,11 +48,11 @@ static fromAccountData(accountData: AccountData) { | ||
return new Account( | ||
nonce !== undefined ? bufferToBigInt(toBuffer(nonce)) : undefined, | ||
balance !== undefined ? bufferToBigInt(toBuffer(balance)) : undefined, | ||
storageRoot !== undefined ? toBuffer(storageRoot) : undefined, | ||
codeHash !== undefined ? toBuffer(codeHash) : undefined | ||
nonce !== undefined ? bytesToBigInt(toBytes(nonce)) : undefined, | ||
balance !== undefined ? bytesToBigInt(toBytes(balance)) : undefined, | ||
storageRoot !== undefined ? toBytes(storageRoot) : undefined, | ||
codeHash !== undefined ? toBytes(codeHash) : undefined | ||
) | ||
} | ||
public static fromRlpSerializedAccount(serialized: Buffer) { | ||
const values = arrToBufArr(RLP.decode(Uint8Array.from(serialized)) as Uint8Array[]) as Buffer[] | ||
public static fromRlpSerializedAccount(serialized: Uint8Array) { | ||
const values = RLP.decode(serialized) as Uint8Array[] | ||
@@ -66,6 +66,6 @@ if (!Array.isArray(values)) { | ||
public static fromValuesArray(values: Buffer[]) { | ||
public static fromValuesArray(values: Uint8Array[]) { | ||
const [nonce, balance, storageRoot, codeHash] = values | ||
return new Account(bufferToBigInt(nonce), bufferToBigInt(balance), storageRoot, codeHash) | ||
return new Account(bytesToBigInt(nonce), bytesToBigInt(balance), storageRoot, codeHash) | ||
} | ||
@@ -77,3 +77,8 @@ | ||
*/ | ||
constructor(nonce = _0n, balance = _0n, storageRoot = KECCAK256_RLP, codeHash = KECCAK256_NULL) { | ||
constructor( | ||
nonce = BIGINT_0, | ||
balance = BIGINT_0, | ||
storageRoot = KECCAK256_RLP, | ||
codeHash = KECCAK256_NULL | ||
) { | ||
this.nonce = nonce | ||
@@ -88,6 +93,6 @@ this.balance = balance | ||
private _validate() { | ||
if (this.nonce < _0n) { | ||
if (this.nonce < BIGINT_0) { | ||
throw new Error('nonce must be greater than zero') | ||
} | ||
if (this.balance < _0n) { | ||
if (this.balance < BIGINT_0) { | ||
throw new Error('balance must be greater than zero') | ||
@@ -104,8 +109,8 @@ } | ||
/** | ||
* Returns a Buffer Array of the raw Buffers for the account, in order. | ||
* Returns an array of Uint8Arrays of the raw bytes for the account, in order. | ||
*/ | ||
raw(): Buffer[] { | ||
raw(): Uint8Array[] { | ||
return [ | ||
bigIntToUnpaddedBuffer(this.nonce), | ||
bigIntToUnpaddedBuffer(this.balance), | ||
bigIntToUnpaddedBytes(this.nonce), | ||
bigIntToUnpaddedBytes(this.balance), | ||
this.storageRoot, | ||
@@ -117,6 +122,6 @@ this.codeHash, | ||
/** | ||
* Returns the RLP serialization of the account as a `Buffer`. | ||
* Returns the RLP serialization of the account as a `Uint8Array`. | ||
*/ | ||
serialize(): Buffer { | ||
return Buffer.from(RLP.encode(bufArrToArr(this.raw()))) | ||
serialize(): Uint8Array { | ||
return RLP.encode(this.raw()) | ||
} | ||
@@ -128,3 +133,3 @@ | ||
isContract(): boolean { | ||
return !this.codeHash.equals(KECCAK256_NULL) | ||
return !equalsBytes(this.codeHash, KECCAK256_NULL) | ||
} | ||
@@ -138,3 +143,7 @@ | ||
isEmpty(): boolean { | ||
return this.balance === _0n && this.nonce === _0n && this.codeHash.equals(KECCAK256_NULL) | ||
return ( | ||
this.balance === BIGINT_0 && | ||
this.nonce === BIGINT_0 && | ||
equalsBytes(this.codeHash, KECCAK256_NULL) | ||
) | ||
} | ||
@@ -177,8 +186,8 @@ } | ||
if (eip1191ChainId !== undefined) { | ||
const chainId = bufferToBigInt(toBuffer(eip1191ChainId)) | ||
const chainId = bytesToBigInt(toBytes(eip1191ChainId)) | ||
prefix = chainId.toString() + '0x' | ||
} | ||
const buf = Buffer.from(prefix + address, 'utf8') | ||
const hash = keccak256(buf).toString('hex') | ||
const bytes = utf8ToBytes(prefix + address) | ||
const hash = bytesToHex(keccak256(Buffer.from(bytes))).slice(2) | ||
let ret = '0x' | ||
@@ -214,16 +223,14 @@ | ||
*/ | ||
export const generateAddress = function (from: Buffer, nonce: Buffer): Buffer { | ||
assertIsBuffer(from) | ||
assertIsBuffer(nonce) | ||
export const generateAddress = function (from: Uint8Array, nonce: Uint8Array): Uint8Array { | ||
assertIsBytes(from) | ||
assertIsBytes(nonce) | ||
if (bufferToBigInt(nonce) === BigInt(0)) { | ||
if (bytesToBigInt(nonce) === BIGINT_0) { | ||
// in RLP we want to encode null in the case of zero nonce | ||
// read the RLP documentation for an answer if you dare | ||
return Buffer.from(keccak256(arrToBufArr(RLP.encode(bufArrToArr([from, null] as any))))).slice( | ||
-20 | ||
) | ||
return keccak256(Buffer.from(RLP.encode([from, Uint8Array.from([])]))).subarray(-20) | ||
} | ||
// Only take the lower 160bits of the hash | ||
return Buffer.from(keccak256(arrToBufArr(RLP.encode(bufArrToArr([from, nonce]))))).slice(-20) | ||
return keccak256(Buffer.from(RLP.encode([from, nonce]))).subarray(-20) | ||
} | ||
@@ -237,6 +244,10 @@ | ||
*/ | ||
export const generateAddress2 = function (from: Buffer, salt: Buffer, initCode: Buffer): Buffer { | ||
assertIsBuffer(from) | ||
assertIsBuffer(salt) | ||
assertIsBuffer(initCode) | ||
export const generateAddress2 = function ( | ||
from: Uint8Array, | ||
salt: Uint8Array, | ||
initCode: Uint8Array | ||
): Uint8Array { | ||
assertIsBytes(from) | ||
assertIsBytes(salt) | ||
assertIsBytes(initCode) | ||
@@ -251,6 +262,6 @@ if (from.length !== 20) { | ||
const address = keccak256( | ||
Buffer.concat([Buffer.from('ff', 'hex'), from, salt, keccak256(initCode)]) | ||
Buffer.from(concatBytes(hexToBytes('0xff'), from, salt, keccak256(Buffer.from(initCode)))) | ||
) | ||
return toBuffer(address).slice(-20) | ||
return address.subarray(-20) | ||
} | ||
@@ -261,3 +272,3 @@ | ||
*/ | ||
export const isValidPrivate = function (privateKey: Buffer): boolean { | ||
export const isValidPrivate = function (privateKey: Uint8Array): boolean { | ||
try { | ||
@@ -276,4 +287,4 @@ return privateKeyVerify(privateKey) | ||
*/ | ||
export const isValidPublic = function (publicKey: Buffer, sanitize: boolean = false): boolean { | ||
assertIsBuffer(publicKey) | ||
export const isValidPublic = function (publicKey: Uint8Array, sanitize: boolean = false): boolean { | ||
assertIsBytes(publicKey) | ||
if (publicKey.length === 64) { | ||
@@ -297,4 +308,4 @@ // Convert to SEC1 for secp256k1 | ||
*/ | ||
export const pubToAddress = function (pubKey: Buffer, sanitize: boolean = false): Buffer { | ||
assertIsBuffer(pubKey) | ||
export const pubToAddress = function (pubKey: Uint8Array, sanitize: boolean = false): Uint8Array { | ||
assertIsBytes(pubKey) | ||
if (sanitize && pubKey.length !== 64) { | ||
@@ -307,3 +318,3 @@ pubKey = Buffer.from(publicKeyConvert(pubKey, false).slice(1)) | ||
// Only take the lower 160bits of the hash | ||
return Buffer.from(keccak256(pubKey)).slice(-20) | ||
return Buffer.from(keccak256(Buffer.from(pubKey))).slice(-20) | ||
} | ||
@@ -316,4 +327,4 @@ export const publicToAddress = pubToAddress | ||
*/ | ||
export const privateToPublic = function (privateKey: Buffer): Buffer { | ||
assertIsBuffer(privateKey) | ||
export const privateToPublic = function (privateKey: Uint8Array): Uint8Array { | ||
assertIsBytes(privateKey) | ||
// skip the type flag and use the X, Y points | ||
@@ -327,3 +338,3 @@ return Buffer.from(publicKeyCreate(privateKey, false)).slice(1) | ||
*/ | ||
export const privateToAddress = function (privateKey: Buffer): Buffer { | ||
export const privateToAddress = function (privateKey: Uint8Array): Uint8Array { | ||
return publicToAddress(privateToPublic(privateKey)) | ||
@@ -335,4 +346,4 @@ } | ||
*/ | ||
export const importPublic = function (publicKey: Buffer): Buffer { | ||
assertIsBuffer(publicKey) | ||
export const importPublic = function (publicKey: Uint8Array): Uint8Array { | ||
assertIsBytes(publicKey) | ||
if (publicKey.length !== 64) { | ||
@@ -350,3 +361,3 @@ publicKey = Buffer.from(publicKeyConvert(publicKey, false).slice(1)) | ||
const addr = zeros(addressLength) | ||
return bufferToHex(addr) | ||
return bytesToHex(addr) | ||
} | ||
@@ -368,3 +379,3 @@ | ||
export function accountBodyFromSlim(body: AccountBodyBuffer) { | ||
export function accountBodyFromSlim(body: AccountBodyBytes) { | ||
const [nonce, balance, storageRoot, codeHash] = body | ||
@@ -374,4 +385,4 @@ return [ | ||
balance, | ||
arrToBufArr(storageRoot).length === 0 ? KECCAK256_RLP : storageRoot, | ||
arrToBufArr(codeHash).length === 0 ? KECCAK256_NULL : codeHash, | ||
storageRoot.length === 0 ? KECCAK256_RLP : storageRoot, | ||
codeHash.length === 0 ? KECCAK256_NULL : codeHash, | ||
] | ||
@@ -381,3 +392,3 @@ } | ||
const emptyUint8Arr = new Uint8Array(0) | ||
export function accountBodyToSlim(body: AccountBodyBuffer) { | ||
export function accountBodyToSlim(body: AccountBodyBytes) { | ||
const [nonce, balance, storageRoot, codeHash] = body | ||
@@ -387,4 +398,4 @@ return [ | ||
balance, | ||
arrToBufArr(storageRoot).equals(KECCAK256_RLP) ? emptyUint8Arr : storageRoot, | ||
arrToBufArr(codeHash).equals(KECCAK256_NULL) ? emptyUint8Arr : codeHash, | ||
equalsBytes(storageRoot, KECCAK256_RLP) ? emptyUint8Arr : storageRoot, | ||
equalsBytes(codeHash, KECCAK256_NULL) ? emptyUint8Arr : codeHash, | ||
] | ||
@@ -395,8 +406,8 @@ } | ||
* Converts a slim account (per snap protocol spec) to the RLP encoded version of the account | ||
* @param body Array of 4 Buffer-like items to represent the account | ||
* @param body Array of 4 Uint8Array-like items to represent the account | ||
* @returns RLP encoded version of the account | ||
*/ | ||
export function accountBodyToRLP(body: AccountBodyBuffer, couldBeSlim = true) { | ||
export function accountBodyToRLP(body: AccountBodyBytes, couldBeSlim = true) { | ||
const accountBody = couldBeSlim ? accountBodyFromSlim(body) : body | ||
return arrToBufArr(RLP.encode(accountBody)) | ||
return RLP.encode(accountBody) | ||
} |
@@ -7,4 +7,5 @@ import { | ||
pubToAddress, | ||
} from './account' | ||
import { bigIntToBuffer, bufferToBigInt, toBuffer, zeros } from './bytes' | ||
} from './account.js' | ||
import { bigIntToBytes, bytesToBigInt, bytesToHex, equalsBytes, toBytes, zeros } from './bytes.js' | ||
import { BIGINT_0 } from './constants.js' | ||
@@ -15,9 +16,9 @@ /** | ||
export class Address { | ||
public readonly buf: Buffer | ||
public readonly bytes: Uint8Array | ||
constructor(buf: Buffer) { | ||
if (buf.length !== 20) { | ||
constructor(bytes: Uint8Array) { | ||
if (bytes.length !== 20) { | ||
throw new Error('Invalid address length') | ||
} | ||
this.buf = buf | ||
this.bytes = bytes | ||
} | ||
@@ -40,3 +41,3 @@ | ||
} | ||
return new Address(toBuffer(str)) | ||
return new Address(toBytes(str)) | ||
} | ||
@@ -48,8 +49,8 @@ | ||
*/ | ||
static fromPublicKey(pubKey: Buffer): Address { | ||
if (!Buffer.isBuffer(pubKey)) { | ||
throw new Error('Public key should be Buffer') | ||
static fromPublicKey(pubKey: Uint8Array): Address { | ||
if (!(pubKey instanceof Uint8Array)) { | ||
throw new Error('Public key should be Uint8Array') | ||
} | ||
const buf = pubToAddress(pubKey) | ||
return new Address(buf) | ||
const bytes = pubToAddress(pubKey) | ||
return new Address(bytes) | ||
} | ||
@@ -61,8 +62,8 @@ | ||
*/ | ||
static fromPrivateKey(privateKey: Buffer): Address { | ||
if (!Buffer.isBuffer(privateKey)) { | ||
throw new Error('Private key should be Buffer') | ||
static fromPrivateKey(privateKey: Uint8Array): Address { | ||
if (!(privateKey instanceof Uint8Array)) { | ||
throw new Error('Private key should be Uint8Array') | ||
} | ||
const buf = privateToAddress(privateKey) | ||
return new Address(buf) | ||
const bytes = privateToAddress(privateKey) | ||
return new Address(bytes) | ||
} | ||
@@ -79,3 +80,3 @@ | ||
} | ||
return new Address(generateAddress(from.buf, bigIntToBuffer(nonce))) | ||
return new Address(generateAddress(from.bytes, bigIntToBytes(nonce))) | ||
} | ||
@@ -89,10 +90,10 @@ | ||
*/ | ||
static generate2(from: Address, salt: Buffer, initCode: Buffer): Address { | ||
if (!Buffer.isBuffer(salt)) { | ||
throw new Error('Expected salt to be a Buffer') | ||
static generate2(from: Address, salt: Uint8Array, initCode: Uint8Array): Address { | ||
if (!(salt instanceof Uint8Array)) { | ||
throw new Error('Expected salt to be a Uint8Array') | ||
} | ||
if (!Buffer.isBuffer(initCode)) { | ||
throw new Error('Expected initCode to be a Buffer') | ||
if (!(initCode instanceof Uint8Array)) { | ||
throw new Error('Expected initCode to be a Uint8Array') | ||
} | ||
return new Address(generateAddress2(from.buf, salt, initCode)) | ||
return new Address(generateAddress2(from.bytes, salt, initCode)) | ||
} | ||
@@ -104,3 +105,3 @@ | ||
equals(address: Address): boolean { | ||
return this.buf.equals(address.buf) | ||
return equalsBytes(this.bytes, address.bytes) | ||
} | ||
@@ -120,4 +121,4 @@ | ||
isPrecompileOrSystemAddress(): boolean { | ||
const address = bufferToBigInt(this.buf) | ||
const rangeMin = BigInt(0) | ||
const address = bytesToBigInt(this.bytes) | ||
const rangeMin = BIGINT_0 | ||
const rangeMax = BigInt('0xffff') | ||
@@ -131,11 +132,11 @@ return address >= rangeMin && address <= rangeMax | ||
toString(): string { | ||
return '0x' + this.buf.toString('hex') | ||
return bytesToHex(this.bytes) | ||
} | ||
/** | ||
* Returns Buffer representation of address. | ||
* Returns a new Uint8Array representation of address. | ||
*/ | ||
toBuffer(): Buffer { | ||
return Buffer.from(this.buf) | ||
toBytes(): Uint8Array { | ||
return new Uint8Array(this.bytes) | ||
} | ||
} |
695
src/bytes.ts
@@ -1,19 +0,153 @@ | ||
import { assertIsArray, assertIsBuffer, assertIsHexString } from './helpers' | ||
import { isHexPrefixed, isHexString, padToEven, stripHexPrefix } from './internal' | ||
import { getRandomBytesSync } from 'ethereum-cryptography/random.js' | ||
import type { | ||
NestedBufferArray, | ||
NestedUint8Array, | ||
PrefixedHexString, | ||
TransformableToArray, | ||
TransformableToBuffer, | ||
TransformabletoBytes, | ||
} from './types' | ||
import { assertIsArray, assertIsBytes, assertIsHexString } from './helpers.js' | ||
import { isHexPrefixed, isHexString, padToEven, stripHexPrefix } from './internal.js' | ||
import type { PrefixedHexString, TransformabletoBytes } from './types.js' | ||
const BIGINT_0 = BigInt(0) | ||
function isBytes(a: unknown): a is Uint8Array { | ||
return ( | ||
a instanceof Uint8Array || | ||
// eslint-disable-next-line eqeqeq | ||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array') | ||
) | ||
} | ||
const hexes = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0')) | ||
export function _bytesToUnprefixedHex(bytes: Uint8Array): string { | ||
if (!isBytes(bytes)) throw new Error('Uint8Array expected') | ||
// pre-caching improves the speed 6x | ||
let hex = '' | ||
for (let i = 0; i < bytes.length; i++) { | ||
hex += hexes[bytes[i]] | ||
} | ||
return hex | ||
} | ||
/** | ||
* Converts a `Number` into a hex `String` | ||
* @param {Number} i | ||
* @return {String} | ||
* @deprecated | ||
*/ | ||
export const intToHex = function (i: number) { | ||
export const bytesToUnprefixedHex = _bytesToUnprefixedHex | ||
// hexToBytes cache | ||
const hexToBytesMapFirstKey: { [key: string]: number } = {} | ||
const hexToBytesMapSecondKey: { [key: string]: number } = {} | ||
for (let i = 0; i < 16; i++) { | ||
const vSecondKey = i | ||
const vFirstKey = i * 16 | ||
const key = i.toString(16).toLowerCase() | ||
hexToBytesMapSecondKey[key] = vSecondKey | ||
hexToBytesMapSecondKey[key.toUpperCase()] = vSecondKey | ||
hexToBytesMapFirstKey[key] = vFirstKey | ||
hexToBytesMapFirstKey[key.toUpperCase()] = vFirstKey | ||
} | ||
/** | ||
* NOTE: only use this function if the string is even, and only consists of hex characters | ||
* If this is not the case, this function could return weird results | ||
* @deprecated | ||
*/ | ||
function _unprefixedHexToBytes(hex: string): Uint8Array { | ||
const byteLen = hex.length | ||
const bytes = new Uint8Array(byteLen / 2) | ||
for (let i = 0; i < byteLen; i += 2) { | ||
bytes[i / 2] = hexToBytesMapFirstKey[hex[i]] + hexToBytesMapSecondKey[hex[i + 1]] | ||
} | ||
return bytes | ||
} | ||
/** | ||
* @deprecated | ||
*/ | ||
export const unprefixedHexToBytes = (inp: string) => { | ||
if (inp.slice(0, 2) === '0x') { | ||
throw new Error('hex string is prefixed with 0x, should be unprefixed') | ||
} else { | ||
return _unprefixedHexToBytes(padToEven(inp)) | ||
} | ||
} | ||
/**************** Borrowed from @chainsafe/ssz */ | ||
// Caching this info costs about ~1000 bytes and speeds up toHexString() by x6 | ||
const hexByByte = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')) | ||
export const bytesToHex = (bytes: Uint8Array): string => { | ||
let hex = '0x' | ||
if (bytes === undefined || bytes.length === 0) return hex | ||
for (const byte of bytes) { | ||
hex += hexByByte[byte] | ||
} | ||
return hex | ||
} | ||
// BigInt cache for the numbers 0 - 256*256-1 (two-byte bytes) | ||
const BIGINT_CACHE: bigint[] = [] | ||
for (let i = 0; i <= 256 * 256 - 1; i++) { | ||
BIGINT_CACHE[i] = BigInt(i) | ||
} | ||
/** | ||
* Converts a {@link Uint8Array} to a {@link bigint} | ||
* @param {Uint8Array} bytes the bytes to convert | ||
* @returns {bigint} | ||
*/ | ||
export const bytesToBigInt = (bytes: Uint8Array, littleEndian = false): bigint => { | ||
if (littleEndian) { | ||
bytes.reverse() | ||
} | ||
const hex = bytesToHex(bytes) | ||
if (hex === '0x') { | ||
return BIGINT_0 | ||
} | ||
if (hex.length === 4) { | ||
// If the byte length is 1 (this is faster than checking `bytes.length === 1`) | ||
return BIGINT_CACHE[bytes[0]] | ||
} | ||
if (hex.length === 6) { | ||
return BIGINT_CACHE[bytes[0] * 256 + bytes[1]] | ||
} | ||
return BigInt(hex) | ||
} | ||
/** | ||
* Converts a {@link Uint8Array} to a {@link number}. | ||
* @param {Uint8Array} bytes the bytes to convert | ||
* @return {number} | ||
* @throws If the input number exceeds 53 bits. | ||
*/ | ||
export const bytesToInt = (bytes: Uint8Array): number => { | ||
const res = Number(bytesToBigInt(bytes)) | ||
if (!Number.isSafeInteger(res)) throw new Error('Number exceeds 53 bits') | ||
return res | ||
} | ||
export const hexToBytes = (hex: string): Uint8Array => { | ||
if (typeof hex !== 'string') { | ||
throw new Error(`hex argument type ${typeof hex} must be of type string`) | ||
} | ||
if (!/^0x[0-9a-fA-F]*$/.test(hex)) { | ||
throw new Error(`Input must be a 0x-prefixed hexadecimal string, got ${hex}`) | ||
} | ||
hex = hex.slice(2) | ||
if (hex.length % 2 !== 0) { | ||
hex = padToEven(hex) | ||
} | ||
return _unprefixedHexToBytes(hex) | ||
} | ||
/******************************************/ | ||
/** | ||
* Converts a {@link number} into a {@link PrefixedHexString} | ||
* @param {number} i | ||
* @return {PrefixedHexString} | ||
*/ | ||
export const intToHex = (i: number): PrefixedHexString => { | ||
if (!Number.isSafeInteger(i) || i < 0) { | ||
@@ -26,9 +160,9 @@ throw new Error(`Received an invalid integer type: ${i}`) | ||
/** | ||
* Converts an `Number` to a `Buffer` | ||
* Converts an {@link number} to a {@link Uint8Array} | ||
* @param {Number} i | ||
* @return {Buffer} | ||
* @return {Uint8Array} | ||
*/ | ||
export const intToBuffer = function (i: number) { | ||
export const intToBytes = (i: number): Uint8Array => { | ||
const hex = intToHex(i) | ||
return Buffer.from(padToEven(hex.slice(2)), 'hex') | ||
return hexToBytes(hex) | ||
} | ||
@@ -41,37 +175,37 @@ | ||
*/ | ||
export const bigIntToBytes = (num: bigint): Uint8Array => { | ||
export const bigIntToBytes = (num: bigint, littleEndian = false): Uint8Array => { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
return toBytes('0x' + padToEven(num.toString(16))) | ||
const bytes = toBytes('0x' + padToEven(num.toString(16))) | ||
return littleEndian ? bytes.reverse() : bytes | ||
} | ||
/** | ||
* Returns a buffer filled with 0s. | ||
* @param bytes the number of bytes the buffer should be | ||
* Returns a Uint8Array filled with 0s. | ||
* @param {number} bytes the number of bytes of the Uint8Array | ||
* @return {Uint8Array} | ||
*/ | ||
export const zeros = function (bytes: number): Buffer { | ||
return Buffer.allocUnsafe(bytes).fill(0) | ||
export const zeros = (bytes: number): Uint8Array => { | ||
return new Uint8Array(bytes) | ||
} | ||
/** | ||
* Pads a `Buffer` with zeros till it has `length` bytes. | ||
* Pads a `Uint8Array` with zeros till it has `length` bytes. | ||
* Truncates the beginning or end of input if its length exceeds `length`. | ||
* @param msg the value to pad (Buffer) | ||
* @param length the number of bytes the output should be | ||
* @param right whether to start padding form the left or right | ||
* @return (Buffer) | ||
* @param {Uint8Array} msg the value to pad | ||
* @param {number} length the number of bytes the output should be | ||
* @param {boolean} right whether to start padding form the left or right | ||
* @return {Uint8Array} | ||
*/ | ||
const setLength = function (msg: Buffer, length: number, right: boolean) { | ||
const buf = zeros(length) | ||
const setLength = (msg: Uint8Array, length: number, right: boolean): Uint8Array => { | ||
if (right) { | ||
if (msg.length < length) { | ||
msg.copy(buf) | ||
return buf | ||
return new Uint8Array([...msg, ...zeros(length - msg.length)]) | ||
} | ||
return msg.slice(0, length) | ||
return msg.subarray(0, length) | ||
} else { | ||
if (msg.length < length) { | ||
msg.copy(buf, length - msg.length) | ||
return buf | ||
return new Uint8Array([...zeros(length - msg.length), ...msg]) | ||
} | ||
return msg.slice(-length) | ||
return msg.subarray(-length) | ||
} | ||
@@ -81,10 +215,10 @@ } | ||
/** | ||
* Left Pads a `Buffer` with leading zeros till it has `length` bytes. | ||
* Left Pads a `Uint8Array` with leading zeros till it has `length` bytes. | ||
* Or it truncates the beginning if it exceeds. | ||
* @param msg the value to pad (Buffer) | ||
* @param length the number of bytes the output should be | ||
* @return (Buffer) | ||
* @param {Uint8Array} msg the value to pad | ||
* @param {number} length the number of bytes the output should be | ||
* @return {Uint8Array} | ||
*/ | ||
export const setLengthLeft = function (msg: Buffer, length: number) { | ||
assertIsBuffer(msg) | ||
export const setLengthLeft = (msg: Uint8Array, length: number): Uint8Array => { | ||
assertIsBytes(msg) | ||
return setLength(msg, length, false) | ||
@@ -94,10 +228,10 @@ } | ||
/** | ||
* Right Pads a `Buffer` with trailing zeros till it has `length` bytes. | ||
* Right Pads a `Uint8Array` with trailing zeros till it has `length` bytes. | ||
* it truncates the end if it exceeds. | ||
* @param msg the value to pad (Buffer) | ||
* @param length the number of bytes the output should be | ||
* @return (Buffer) | ||
* @param {Uint8Array} msg the value to pad | ||
* @param {number} length the number of bytes the output should be | ||
* @return {Uint8Array} | ||
*/ | ||
export const setLengthRight = function (msg: Buffer, length: number) { | ||
assertIsBuffer(msg) | ||
export const setLengthRight = (msg: Uint8Array, length: number): Uint8Array => { | ||
assertIsBytes(msg) | ||
return setLength(msg, length, true) | ||
@@ -107,10 +241,14 @@ } | ||
/** | ||
* Trims leading zeros from a `Buffer`, `String` or `Number[]`. | ||
* @param a (Buffer|Array|String) | ||
* @return (Buffer|Array|String) | ||
* Trims leading zeros from a `Uint8Array`, `number[]` or PrefixedHexString`. | ||
* @param {Uint8Array|number[]|PrefixedHexString} a | ||
* @return {Uint8Array|number[]|PrefixedHexString} | ||
*/ | ||
const stripZeros = function (a: any): Buffer | number[] | string { | ||
const stripZeros = < | ||
T extends Uint8Array | number[] | PrefixedHexString = Uint8Array | number[] | PrefixedHexString | ||
>( | ||
a: T | ||
): T => { | ||
let first = a[0] | ||
while (a.length > 0 && first.toString() === '0') { | ||
a = a.slice(1) | ||
a = a.slice(1) as T | ||
first = a[0] | ||
@@ -122,9 +260,9 @@ } | ||
/** | ||
* Trims leading zeros from a `Buffer`. | ||
* @param a (Buffer) | ||
* @return (Buffer) | ||
* Trims leading zeros from a `Uint8Array`. | ||
* @param {Uint8Array} a | ||
* @return {Uint8Array} | ||
*/ | ||
export const unpadBuffer = function (a: Buffer): Buffer { | ||
assertIsBuffer(a) | ||
return stripZeros(a) as Buffer | ||
export const unpadBytes = (a: Uint8Array): Uint8Array => { | ||
assertIsBytes(a) | ||
return stripZeros(a) | ||
} | ||
@@ -134,183 +272,21 @@ | ||
* Trims leading zeros from an `Array` (of numbers). | ||
* @param a (number[]) | ||
* @return (number[]) | ||
* @param {number[]} a | ||
* @return {number[]} | ||
*/ | ||
export const unpadArray = function (a: number[]): number[] { | ||
export const unpadArray = (a: number[]): number[] => { | ||
assertIsArray(a) | ||
return stripZeros(a) as number[] | ||
return stripZeros(a) | ||
} | ||
/** | ||
* Trims leading zeros from a hex-prefixed `String`. | ||
* @param a (String) | ||
* @return (String) | ||
* Trims leading zeros from a `PrefixedHexString`. | ||
* @param {PrefixedHexString} a | ||
* @return {PrefixedHexString} | ||
*/ | ||
export const unpadHexString = function (a: string): string { | ||
export const unpadHex = (a: string): PrefixedHexString => { | ||
assertIsHexString(a) | ||
a = stripHexPrefix(a) | ||
return ('0x' + stripZeros(a)) as string | ||
return '0x' + stripZeros(a) | ||
} | ||
export type ToBufferInputTypes = | ||
| PrefixedHexString | ||
| number | ||
| bigint | ||
| Buffer | ||
| Uint8Array | ||
| number[] | ||
| TransformableToArray | ||
| TransformableToBuffer | ||
| null | ||
| undefined | ||
/** | ||
* Attempts to turn a value into a `Buffer`. | ||
* Inputs supported: `Buffer`, `String` (hex-prefixed), `Number`, null/undefined, `BigInt` and other objects | ||
* with a `toArray()` or `toBuffer()` method. | ||
* @param v the value | ||
*/ | ||
export const toBuffer = function (v: ToBufferInputTypes): Buffer { | ||
if (v === null || v === undefined) { | ||
return Buffer.allocUnsafe(0) | ||
} | ||
if (Buffer.isBuffer(v)) { | ||
return Buffer.from(v) | ||
} | ||
if (Array.isArray(v) || v instanceof Uint8Array) { | ||
return Buffer.from(v as Uint8Array) | ||
} | ||
if (typeof v === 'string') { | ||
if (!isHexString(v)) { | ||
throw new Error( | ||
`Cannot convert string to buffer. toBuffer only supports 0x-prefixed hex strings and this string was given: ${v}` | ||
) | ||
} | ||
return Buffer.from(padToEven(stripHexPrefix(v)), 'hex') | ||
} | ||
if (typeof v === 'number') { | ||
return intToBuffer(v) | ||
} | ||
if (typeof v === 'bigint') { | ||
if (v < BigInt(0)) { | ||
throw new Error(`Cannot convert negative bigint to buffer. Given: ${v}`) | ||
} | ||
let n = v.toString(16) | ||
if (n.length % 2) n = '0' + n | ||
return Buffer.from(n, 'hex') | ||
} | ||
if (v.toArray) { | ||
// converts a BN to a Buffer | ||
return Buffer.from(v.toArray()) | ||
} | ||
if (v.toBuffer) { | ||
return Buffer.from(v.toBuffer()) | ||
} | ||
throw new Error('invalid type') | ||
} | ||
/** | ||
* Converts a `Buffer` into a `0x`-prefixed hex `String`. | ||
* @param buf `Buffer` object to convert | ||
*/ | ||
export const bufferToHex = function (buf: Buffer): string { | ||
buf = toBuffer(buf) | ||
return '0x' + buf.toString('hex') | ||
} | ||
/** | ||
* Converts a {@link Buffer} to a {@link bigint} | ||
*/ | ||
export function bufferToBigInt(buf: Buffer) { | ||
const hex = bufferToHex(buf) | ||
if (hex === '0x') { | ||
return BigInt(0) | ||
} | ||
return BigInt(hex) | ||
} | ||
/** | ||
* Converts a {@link bigint} to a {@link Buffer} | ||
*/ | ||
export function bigIntToBuffer(num: bigint) { | ||
return toBuffer('0x' + num.toString(16)) | ||
} | ||
/** | ||
* Converts a `Buffer` to a `Number`. | ||
* @param buf `Buffer` object to convert | ||
* @throws If the input number exceeds 53 bits. | ||
*/ | ||
export const bufferToInt = function (buf: Buffer): number { | ||
const res = Number(bufferToBigInt(buf)) | ||
if (!Number.isSafeInteger(res)) throw new Error('Number exceeds 53 bits') | ||
return res | ||
} | ||
export const hexToBytes = (hex: string): Uint8Array => { | ||
if (typeof hex !== 'string') { | ||
throw new Error(`hex argument type ${typeof hex} must be of type string`) | ||
} | ||
if (!hex.startsWith('0x')) { | ||
throw new Error(`prefixed hex input should start with 0x, got ${hex.substring(0, 2)}`) | ||
} | ||
hex = hex.slice(2) | ||
if (hex.length % 2 !== 0) { | ||
hex = padToEven(hex) | ||
} | ||
const byteLen = hex.length / 2 | ||
const bytes = new Uint8Array(byteLen) | ||
for (let i = 0; i < byteLen; i++) { | ||
const byte = parseInt(hex.slice(i * 2, (i + 1) * 2), 16) | ||
bytes[i] = byte | ||
} | ||
return bytes | ||
} | ||
/** | ||
* Converts an {@link number} to a {@link Uint8Array} | ||
* @param {Number} i | ||
* @return {Uint8Array} | ||
*/ | ||
export const intToBytes = (i: number): Uint8Array => { | ||
const hex = intToHex(i) | ||
return hexToBytes(hex) | ||
} | ||
const _unprefixedHexToBytes = (data: string) => { | ||
const hex = data.startsWith('0x') ? data.substring(2) : data | ||
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex) | ||
const len = hex.length | ||
if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len) | ||
const array = new Uint8Array(len / 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 | ||
} | ||
export const unprefixedHexToBytes = (inp: string) => { | ||
if (inp.slice(0, 2) === '0x') { | ||
throw new Error('hex string is prefixed with 0x, should be unprefixed') | ||
} else { | ||
return _unprefixedHexToBytes(padToEven(inp)) | ||
} | ||
} | ||
export type ToBytesInputTypes = | ||
@@ -333,2 +309,3 @@ | PrefixedHexString | ||
*/ | ||
export const toBytes = (v: ToBytesInputTypes): Uint8Array => { | ||
@@ -357,3 +334,3 @@ if (v === null || v === undefined) { | ||
if (typeof v === 'bigint') { | ||
if (v < BigInt(0)) { | ||
if (v < BIGINT_0) { | ||
throw new Error(`Cannot convert negative bigint to Uint8Array. Given: ${v}`) | ||
@@ -375,21 +352,25 @@ } | ||
/** | ||
* Interprets a `Buffer` as a signed integer and returns a `BigInt`. Assumes 256-bit numbers. | ||
* @param num Signed integer value | ||
* Interprets a `Uint8Array` as a signed integer and returns a `BigInt`. Assumes 256-bit numbers. | ||
* @param {Uint8Array} num Signed integer value | ||
* @returns {bigint} | ||
*/ | ||
export const fromSigned = function (num: Buffer): bigint { | ||
return BigInt.asIntN(256, bufferToBigInt(num)) | ||
export const fromSigned = (num: Uint8Array): bigint => { | ||
return BigInt.asIntN(256, bytesToBigInt(num)) | ||
} | ||
/** | ||
* Converts a `BigInt` to an unsigned integer and returns it as a `Buffer`. Assumes 256-bit numbers. | ||
* @param num | ||
* Converts a `BigInt` to an unsigned integer and returns it as a `Uint8Array`. Assumes 256-bit numbers. | ||
* @param {bigint} num | ||
* @returns {Uint8Array} | ||
*/ | ||
export const toUnsigned = function (num: bigint): Buffer { | ||
return bigIntToBuffer(BigInt.asUintN(256, num)) | ||
export const toUnsigned = (num: bigint): Uint8Array => { | ||
return bigIntToBytes(BigInt.asUintN(256, num)) | ||
} | ||
/** | ||
* Adds "0x" to a given `String` if it does not already start with "0x". | ||
* Adds "0x" to a given `string` if it does not already start with "0x". | ||
* @param {string} str | ||
* @return {PrefixedHexString} | ||
*/ | ||
export const addHexPrefix = function (str: string): string { | ||
export const addHexPrefix = (str: string): PrefixedHexString => { | ||
if (typeof str !== 'string') { | ||
@@ -403,3 +384,3 @@ return str | ||
/** | ||
* Shortens a string or buffer's hex string representation to maxLength (default 50). | ||
* Shortens a string or Uint8Array's hex string representation to maxLength (default 50). | ||
* | ||
@@ -410,142 +391,188 @@ * Examples: | ||
* Output: '657468657265756d0000000000000000000000000000000000…' | ||
* @param {Uint8Array | string} bytes | ||
* @param {number} maxLength | ||
* @return {string} | ||
*/ | ||
export function short(buffer: Buffer | string, maxLength: number = 50): string { | ||
const bufferStr = Buffer.isBuffer(buffer) ? buffer.toString('hex') : buffer | ||
if (bufferStr.length <= maxLength) { | ||
return bufferStr | ||
export const short = (bytes: Uint8Array | string, maxLength: number = 50): string => { | ||
const byteStr = bytes instanceof Uint8Array ? bytesToHex(bytes) : bytes | ||
const len = byteStr.slice(0, 2) === '0x' ? maxLength + 2 : maxLength | ||
if (byteStr.length <= len) { | ||
return byteStr | ||
} | ||
return bufferStr.slice(0, maxLength) + '…' | ||
return byteStr.slice(0, len) + '…' | ||
} | ||
/** | ||
* Returns the utf8 string representation from a hex string. | ||
* Checks provided Uint8Array for leading zeroes and throws if found. | ||
* | ||
* Examples: | ||
* | ||
* Input 1: '657468657265756d000000000000000000000000000000000000000000000000' | ||
* Input 2: '657468657265756d' | ||
* Input 3: '000000000000000000000000000000000000000000000000657468657265756d' | ||
* Valid values: 0x1, 0x, 0x01, 0x1234 | ||
* Invalid values: 0x0, 0x00, 0x001, 0x0001 | ||
* | ||
* Output (all 3 input variants): 'ethereum' | ||
* | ||
* Note that this method is not intended to be used with hex strings | ||
* representing quantities in both big endian or little endian notation. | ||
* | ||
* @param string Hex string, should be `0x` prefixed | ||
* @return Utf8 string | ||
* Note: This method is useful for validating that RLP encoded integers comply with the rule that all | ||
* integer values encoded to RLP must be in the most compact form and contain no leading zero bytes | ||
* @param values An object containing string keys and Uint8Array values | ||
* @throws if any provided value is found to have leading zero bytes | ||
*/ | ||
export const toUtf8 = function (hex: string): string { | ||
const zerosRegexp = /^(00)+|(00)+$/g | ||
hex = stripHexPrefix(hex) | ||
if (hex.length % 2 !== 0) { | ||
throw new Error('Invalid non-even hex string input for toUtf8() provided') | ||
export const validateNoLeadingZeroes = (values: { [key: string]: Uint8Array | undefined }) => { | ||
for (const [k, v] of Object.entries(values)) { | ||
if (v !== undefined && v.length > 0 && v[0] === 0) { | ||
throw new Error(`${k} cannot have leading zeroes, received: ${bytesToHex(v)}`) | ||
} | ||
} | ||
const bufferVal = Buffer.from(hex.replace(zerosRegexp, ''), 'hex') | ||
} | ||
return bufferVal.toString('utf8') | ||
/** | ||
* Converts a {@link bigint} to a `0x` prefixed hex string | ||
* @param {bigint} num the bigint to convert | ||
* @returns {PrefixedHexString} | ||
*/ | ||
export const bigIntToHex = (num: bigint): PrefixedHexString => { | ||
return '0x' + num.toString(16) | ||
} | ||
/** | ||
* Converts a `Buffer` or `Array` to JSON. | ||
* @param ba (Buffer|Array) | ||
* @return (Array|String|null) | ||
* Convert value from bigint to an unpadded Uint8Array | ||
* (useful for RLP transport) | ||
* @param {bigint} value the bigint to convert | ||
* @returns {Uint8Array} | ||
*/ | ||
export const baToJSON = function (ba: any): any { | ||
if (Buffer.isBuffer(ba)) { | ||
return `0x${ba.toString('hex')}` | ||
} else if (ba instanceof Array) { | ||
const array = [] | ||
for (let i = 0; i < ba.length; i++) { | ||
array.push(baToJSON(ba[i])) | ||
} | ||
return array | ||
} | ||
export const bigIntToUnpaddedBytes = (value: bigint): Uint8Array => { | ||
return unpadBytes(bigIntToBytes(value)) | ||
} | ||
/** | ||
* Checks provided Buffers for leading zeroes and throws if found. | ||
* Convert value from number to an unpadded Uint8Array | ||
* (useful for RLP transport) | ||
* @param {number} value the bigint to convert | ||
* @returns {Uint8Array} | ||
*/ | ||
export const intToUnpaddedBytes = (value: number): Uint8Array => { | ||
return unpadBytes(intToBytes(value)) | ||
} | ||
/** | ||
* Compares two Uint8Arrays and returns a number indicating their order in a sorted array. | ||
* | ||
* Examples: | ||
* @param {Uint8Array} value1 - The first Uint8Array to compare. | ||
* @param {Uint8Array} value2 - The second Uint8Array to compare. | ||
* @returns {number} A positive number if value1 is larger than value2, | ||
* A negative number if value1 is smaller than value2, | ||
* or 0 if value1 and value2 are equal. | ||
*/ | ||
export const compareBytes = (value1: Uint8Array, value2: Uint8Array): number => { | ||
const bigIntValue1 = bytesToBigInt(value1) | ||
const bigIntValue2 = bytesToBigInt(value2) | ||
return bigIntValue1 > bigIntValue2 ? 1 : bigIntValue1 < bigIntValue2 ? -1 : 0 | ||
} | ||
/** | ||
* Generates a Uint8Array of random bytes of specified length. | ||
* | ||
* Valid values: 0x1, 0x, 0x01, 0x1234 | ||
* Invalid values: 0x0, 0x00, 0x001, 0x0001 | ||
* | ||
* Note: This method is useful for validating that RLP encoded integers comply with the rule that all | ||
* integer values encoded to RLP must be in the most compact form and contain no leading zero bytes | ||
* @param values An object containing string keys and Buffer values | ||
* @throws if any provided value is found to have leading zero bytes | ||
* @param {number} length - The length of the Uint8Array. | ||
* @returns {Uint8Array} A Uint8Array of random bytes of specified length. | ||
*/ | ||
export const validateNoLeadingZeroes = function (values: { [key: string]: Buffer | undefined }) { | ||
for (const [k, v] of Object.entries(values)) { | ||
if (v !== undefined && v.length > 0 && v[0] === 0) { | ||
throw new Error(`${k} cannot have leading zeroes, received: ${v.toString('hex')}`) | ||
} | ||
} | ||
export const randomBytes = (length: number): Uint8Array => { | ||
return getRandomBytesSync(length) | ||
} | ||
/** | ||
* Converts a {@link Uint8Array} or {@link NestedUint8Array} to {@link Buffer} or {@link NestedBufferArray} | ||
* This mirrors the functionality of the `ethereum-cryptography` export except | ||
* it skips the check to validate that every element of `arrays` is indead a `uint8Array` | ||
* Can give small performance gains on large arrays | ||
* @param {Uint8Array[]} arrays an array of Uint8Arrays | ||
* @returns {Uint8Array} one Uint8Array with all the elements of the original set | ||
* works like `Buffer.concat` | ||
*/ | ||
export function arrToBufArr(arr: Uint8Array): Buffer | ||
export function arrToBufArr(arr: NestedUint8Array): NestedBufferArray | ||
export function arrToBufArr(arr: Uint8Array | NestedUint8Array): Buffer | NestedBufferArray | ||
export function arrToBufArr(arr: Uint8Array | NestedUint8Array): Buffer | NestedBufferArray { | ||
if (!Array.isArray(arr)) { | ||
return Buffer.from(arr) | ||
export const concatBytes = (...arrays: Uint8Array[]): Uint8Array => { | ||
if (arrays.length === 1) return arrays[0] | ||
const length = arrays.reduce((a, arr) => a + arr.length, 0) | ||
const result = new Uint8Array(length) | ||
for (let i = 0, pad = 0; i < arrays.length; i++) { | ||
const arr = arrays[i] | ||
result.set(arr, pad) | ||
pad += arr.length | ||
} | ||
return arr.map((a) => arrToBufArr(a)) | ||
return result | ||
} | ||
/** | ||
* Converts a {@link Buffer} or {@link NestedBufferArray} to {@link Uint8Array} or {@link NestedUint8Array} | ||
* @notice Convert a Uint8Array to a 32-bit integer | ||
* @param {Uint8Array} bytes The input Uint8Array from which to read the 32-bit integer. | ||
* @param {boolean} littleEndian True for little-endian, undefined or false for big-endian. | ||
* @return {number} The 32-bit integer read from the input Uint8Array. | ||
*/ | ||
export function bufArrToArr(arr: Buffer): Uint8Array | ||
export function bufArrToArr(arr: NestedBufferArray): NestedUint8Array | ||
export function bufArrToArr(arr: Buffer | NestedBufferArray): Uint8Array | NestedUint8Array | ||
export function bufArrToArr(arr: Buffer | NestedBufferArray): Uint8Array | NestedUint8Array { | ||
if (!Array.isArray(arr)) { | ||
return Uint8Array.from(arr ?? []) | ||
export function bytesToInt32(bytes: Uint8Array, littleEndian: boolean = false): number { | ||
if (bytes.length < 4) { | ||
bytes = setLength(bytes, 4, littleEndian) | ||
} | ||
return arr.map((a) => bufArrToArr(a)) | ||
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) | ||
return dataView.getUint32(0, littleEndian) | ||
} | ||
/** | ||
* Converts a {@link bigint} to a `0x` prefixed hex string | ||
* @notice Convert a Uint8Array to a 64-bit bigint | ||
* @param {Uint8Array} bytes The input Uint8Array from which to read the 64-bit bigint. | ||
* @param {boolean} littleEndian True for little-endian, undefined or false for big-endian. | ||
* @return {bigint} The 64-bit bigint read from the input Uint8Array. | ||
*/ | ||
export const bigIntToHex = (num: bigint) => { | ||
return '0x' + num.toString(16) | ||
export function bytesToBigInt64(bytes: Uint8Array, littleEndian: boolean = false): bigint { | ||
if (bytes.length < 8) { | ||
bytes = setLength(bytes, 8, littleEndian) | ||
} | ||
const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) | ||
return dataView.getBigUint64(0, littleEndian) | ||
} | ||
/** | ||
* Convert value from bigint to an unpadded Buffer | ||
* (useful for RLP transport) | ||
* @param value value to convert | ||
* @notice Convert a 32-bit integer to a Uint8Array. | ||
* @param {number} value The 32-bit integer to convert. | ||
* @param {boolean} littleEndian True for little-endian, undefined or false for big-endian. | ||
* @return {Uint8Array} A Uint8Array of length 4 containing the integer. | ||
*/ | ||
export function bigIntToUnpaddedBuffer(value: bigint): Buffer { | ||
return unpadBuffer(bigIntToBuffer(value)) | ||
export function int32ToBytes(value: number, littleEndian: boolean = false): Uint8Array { | ||
const buffer = new ArrayBuffer(4) | ||
const dataView = new DataView(buffer) | ||
dataView.setUint32(0, value, littleEndian) | ||
return new Uint8Array(buffer) | ||
} | ||
export function intToUnpaddedBuffer(value: number): Buffer { | ||
return unpadBuffer(intToBuffer(value)) | ||
/** | ||
* @notice Convert a 64-bit bigint to a Uint8Array. | ||
* @param {bigint} value The 64-bit bigint to convert. | ||
* @param {boolean} littleEndian True for little-endian, undefined or false for big-endian. | ||
* @return {Uint8Array} A Uint8Array of length 8 containing the bigint. | ||
*/ | ||
export function bigInt64ToBytes(value: bigint, littleEndian: boolean = false): Uint8Array { | ||
const buffer = new ArrayBuffer(8) | ||
const dataView = new DataView(buffer) | ||
dataView.setBigUint64(0, value, littleEndian) | ||
return new Uint8Array(buffer) | ||
} | ||
/**************** Borrowed from @chainsafe/ssz */ | ||
// Caching this info costs about ~1000 bytes and speeds up toHexString() by x6 | ||
const hexByByte = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')) | ||
export function equalsBytes(a: Uint8Array, b: Uint8Array): boolean { | ||
if (a.length !== b.length) { | ||
return false | ||
} | ||
export const bytesToHex = (bytes: Uint8Array): string => { | ||
let hex = '0x' | ||
if (bytes === undefined || bytes.length === 0) return hex | ||
for (const byte of bytes) { | ||
hex += hexByByte[byte] | ||
for (let i = 0; i < a.length; i++) { | ||
if (a[i] !== b[i]) { | ||
return false | ||
} | ||
} | ||
return hex | ||
return true | ||
} | ||
export const bytesToBigInt = (bytes: Uint8Array): bigint => { | ||
const hex = bytesToHex(bytes) | ||
if (hex === '0x') { | ||
return BigInt(0) | ||
export function bytesToUtf8(data: Uint8Array): string { | ||
if (!(data instanceof Uint8Array)) { | ||
throw new TypeError(`bytesToUtf8 expected Uint8Array, got ${typeof data}`) | ||
} | ||
return BigInt(hex) | ||
return new TextDecoder().decode(data) | ||
} | ||
export function utf8ToBytes(str: string): Uint8Array { | ||
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`) | ||
return new Uint8Array(new TextEncoder().encode(str)) // https://bugzil.la/1681809 | ||
} |
@@ -1,2 +0,2 @@ | ||
import { Buffer } from 'buffer' | ||
import { hexToBytes } from './bytes.js' | ||
@@ -39,3 +39,3 @@ /** | ||
*/ | ||
export const KECCAK256_NULL_S = 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' | ||
export const KECCAK256_NULL_S = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' | ||
@@ -45,3 +45,3 @@ /** | ||
*/ | ||
export const KECCAK256_NULL = Buffer.from(KECCAK256_NULL_S, 'hex') | ||
export const KECCAK256_NULL = hexToBytes(KECCAK256_NULL_S) | ||
@@ -52,3 +52,3 @@ /** | ||
export const KECCAK256_RLP_ARRAY_S = | ||
'1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347' | ||
'0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347' | ||
@@ -58,3 +58,3 @@ /** | ||
*/ | ||
export const KECCAK256_RLP_ARRAY = Buffer.from(KECCAK256_RLP_ARRAY_S, 'hex') | ||
export const KECCAK256_RLP_ARRAY = hexToBytes(KECCAK256_RLP_ARRAY_S) | ||
@@ -64,3 +64,3 @@ /** | ||
*/ | ||
export const KECCAK256_RLP_S = '56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421' | ||
export const KECCAK256_RLP_S = '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421' | ||
@@ -70,3 +70,3 @@ /** | ||
*/ | ||
export const KECCAK256_RLP = Buffer.from(KECCAK256_RLP_S, 'hex') | ||
export const KECCAK256_RLP = hexToBytes(KECCAK256_RLP_S) | ||
@@ -76,4 +76,39 @@ /** | ||
*/ | ||
export const RLP_EMPTY_STRING = Buffer.from([0x80]) | ||
export const RLP_EMPTY_STRING = Uint8Array.from([0x80]) | ||
export const MAX_WITHDRAWALS_PER_PAYLOAD = 16 | ||
export const RIPEMD160_ADDRESS_STRING = '0000000000000000000000000000000000000003' | ||
/** | ||
* BigInt constants | ||
*/ | ||
export const BIGINT_NEG1 = BigInt(-1) | ||
export const BIGINT_0 = BigInt(0) | ||
export const BIGINT_1 = BigInt(1) | ||
export const BIGINT_2 = BigInt(2) | ||
export const BIGINT_3 = BigInt(3) | ||
export const BIGINT_7 = BigInt(7) | ||
export const BIGINT_8 = BigInt(8) | ||
export const BIGINT_27 = BigInt(27) | ||
export const BIGINT_28 = BigInt(28) | ||
export const BIGINT_31 = BigInt(31) | ||
export const BIGINT_32 = BigInt(32) | ||
export const BIGINT_64 = BigInt(64) | ||
export const BIGINT_128 = BigInt(128) | ||
export const BIGINT_255 = BigInt(255) | ||
export const BIGINT_256 = BigInt(256) | ||
export const BIGINT_96 = BigInt(96) | ||
export const BIGINT_100 = BigInt(100) | ||
export const BIGINT_160 = BigInt(160) | ||
export const BIGINT_224 = BigInt(224) | ||
export const BIGINT_2EXP96 = BigInt(79228162514264337593543950336) | ||
export const BIGINT_2EXP160 = BigInt(1461501637330902918203684832716283019655932542976) | ||
export const BIGINT_2EXP224 = | ||
BigInt(26959946667150639794667015087019630673637144422540572481103610249216) | ||
export const BIGINT_2EXP256 = BIGINT_2 ** BIGINT_256 |
@@ -1,2 +0,2 @@ | ||
import { isHexString } from './internal' | ||
import { isHexString } from './internal.js' | ||
@@ -18,5 +18,5 @@ /** | ||
*/ | ||
export const assertIsBuffer = function (input: Buffer): void { | ||
if (!Buffer.isBuffer(input)) { | ||
const msg = `This method only supports Buffer but input was: ${input}` | ||
export const assertIsBytes = function (input: Uint8Array): void { | ||
if (!(input instanceof Uint8Array)) { | ||
const msg = `This method only supports Uint8Array but input was: ${input}` | ||
throw new Error(msg) | ||
@@ -23,0 +23,0 @@ } |
/** | ||
* Constants | ||
*/ | ||
export * from './constants' | ||
export * from './constants.js' | ||
@@ -9,3 +9,3 @@ /** | ||
*/ | ||
export * from './units' | ||
export * from './units.js' | ||
@@ -15,3 +15,3 @@ /** | ||
*/ | ||
export * from './account' | ||
export * from './account.js' | ||
@@ -21,23 +21,23 @@ /** | ||
*/ | ||
export * from './address' | ||
export * from './address.js' | ||
/** | ||
* Withdrawal type | ||
* DB type | ||
*/ | ||
export * from './withdrawal' | ||
export * from './db.js' | ||
/** | ||
* ECDSA signature | ||
* Withdrawal type | ||
*/ | ||
export * from './signature' | ||
export * from './withdrawal.js' | ||
/** | ||
* Utilities for manipulating Buffers, byte arrays, etc. | ||
* ECDSA signature | ||
*/ | ||
export * from './bytes' | ||
export * from './signature.js' | ||
/** | ||
* SSZ containers | ||
* Utilities for manipulating bytes, Uint8Arrays, etc. | ||
*/ | ||
export * as ssz from './ssz' | ||
export * from './bytes.js' | ||
@@ -47,3 +47,3 @@ /** | ||
*/ | ||
export * from './types' | ||
export * from './types.js' | ||
@@ -53,3 +53,5 @@ /** | ||
*/ | ||
export * from './asyncEventEmitter' | ||
export * from './asyncEventEmitter.js' | ||
export * from './blobs.js' | ||
export * from './genesis.js' | ||
export { | ||
@@ -66,3 +68,6 @@ arrayContainsArray, | ||
toAscii, | ||
} from './internal' | ||
export * from './lock' | ||
} from './internal.js' | ||
export * from './kzg.js' | ||
export * from './lock.js' | ||
export * from './mapDB.js' | ||
export * from './provider.js' |
@@ -25,2 +25,4 @@ /* | ||
import { bytesToUnprefixedHex, utf8ToBytes } from './bytes.js' | ||
/** | ||
@@ -79,3 +81,3 @@ * Returns a `Boolean` on whether or not the a `String` starts with '0x' | ||
return Buffer.byteLength(str, 'utf8') | ||
return utf8ToBytes(str).byteLength | ||
} | ||
@@ -132,3 +134,4 @@ | ||
/** | ||
* Should be called to get hex representation (prefixed by 0x) of utf8 string | ||
* Should be called to get hex representation (prefixed by 0x) of utf8 string. | ||
* Strips leading and trailing 0's. | ||
* | ||
@@ -140,5 +143,5 @@ * @param string | ||
export function fromUtf8(stringValue: string) { | ||
const str = Buffer.from(stringValue, 'utf8') | ||
const str = utf8ToBytes(stringValue) | ||
return `0x${padToEven(str.toString('hex')).replace(/^0+|0+$/g, '')}` | ||
return `0x${padToEven(bytesToUnprefixedHex(str)).replace(/^0+|0+$/g, '')}` | ||
} | ||
@@ -145,0 +148,0 @@ |
@@ -1,12 +0,26 @@ | ||
import { keccak256 } from 'ethereum-cryptography/keccak' | ||
import { keccak256 } from 'ethereum-cryptography/keccak.js' | ||
import { ecdsaRecover, ecdsaSign, publicKeyConvert } from 'ethereum-cryptography/secp256k1' | ||
import { bufferToBigInt, bufferToHex, bufferToInt, setLengthLeft, toBuffer } from './bytes' | ||
import { SECP256K1_ORDER, SECP256K1_ORDER_DIV_2 } from './constants' | ||
import { assertIsBuffer } from './helpers' | ||
import { | ||
bytesToBigInt, | ||
bytesToHex, | ||
bytesToInt, | ||
concatBytes, | ||
setLengthLeft, | ||
toBytes, | ||
} from './bytes.js' | ||
import { | ||
BIGINT_0, | ||
BIGINT_1, | ||
BIGINT_2, | ||
BIGINT_27, | ||
SECP256K1_ORDER, | ||
SECP256K1_ORDER_DIV_2, | ||
} from './constants.js' | ||
import { assertIsBytes } from './helpers.js' | ||
export interface ECDSASignature { | ||
v: bigint | ||
r: Buffer | ||
s: Buffer | ||
r: Uint8Array | ||
s: Uint8Array | ||
} | ||
@@ -20,3 +34,7 @@ | ||
*/ | ||
export function ecsign(msgHash: Buffer, privateKey: Buffer, chainId?: bigint): ECDSASignature { | ||
export function ecsign( | ||
msgHash: Uint8Array, | ||
privateKey: Uint8Array, | ||
chainId?: bigint | ||
): ECDSASignature { | ||
const { signature, recid: recovery } = ecdsaSign(msgHash, privateKey) | ||
@@ -35,13 +53,13 @@ | ||
function calculateSigRecovery(v: bigint, chainId?: bigint): bigint { | ||
if (v === BigInt(0) || v === BigInt(1)) return v | ||
export function calculateSigRecovery(v: bigint, chainId?: bigint): bigint { | ||
if (v === BIGINT_0 || v === BIGINT_1) return v | ||
if (chainId === undefined) { | ||
return v - BigInt(27) | ||
return v - BIGINT_27 | ||
} | ||
return v - (chainId * BigInt(2) + BigInt(35)) | ||
return v - (chainId * BIGINT_2 + BigInt(35)) | ||
} | ||
function isValidSigRecovery(recovery: bigint): boolean { | ||
return recovery === BigInt(0) || recovery === BigInt(1) | ||
return recovery === BIGINT_0 || recovery === BIGINT_1 | ||
} | ||
@@ -55,9 +73,9 @@ | ||
export const ecrecover = function ( | ||
msgHash: Buffer, | ||
msgHash: Uint8Array, | ||
v: bigint, | ||
r: Buffer, | ||
s: Buffer, | ||
r: Uint8Array, | ||
s: Uint8Array, | ||
chainId?: bigint | ||
): Buffer { | ||
const signature = Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32)], 64) | ||
): Uint8Array { | ||
const signature = concatBytes(setLengthLeft(r, 32), setLengthLeft(s, 32)) | ||
const recovery = calculateSigRecovery(v, chainId) | ||
@@ -77,3 +95,8 @@ if (!isValidSigRecovery(recovery)) { | ||
*/ | ||
export const toRpcSig = function (v: bigint, r: Buffer, s: Buffer, chainId?: bigint): string { | ||
export const toRpcSig = function ( | ||
v: bigint, | ||
r: Uint8Array, | ||
s: Uint8Array, | ||
chainId?: bigint | ||
): string { | ||
const recovery = calculateSigRecovery(v, chainId) | ||
@@ -85,3 +108,4 @@ if (!isValidSigRecovery(recovery)) { | ||
// geth (and the RPC eth_sign method) uses the 65 byte format used by Bitcoin | ||
return bufferToHex(Buffer.concat([setLengthLeft(r, 32), setLengthLeft(s, 32), toBuffer(v)])) | ||
return bytesToHex(concatBytes(setLengthLeft(r, 32), setLengthLeft(s, 32), toBytes(v))) | ||
} | ||
@@ -94,3 +118,8 @@ | ||
*/ | ||
export const toCompactSig = function (v: bigint, r: Buffer, s: Buffer, chainId?: bigint): string { | ||
export const toCompactSig = function ( | ||
v: bigint, | ||
r: Uint8Array, | ||
s: Uint8Array, | ||
chainId?: bigint | ||
): string { | ||
const recovery = calculateSigRecovery(v, chainId) | ||
@@ -101,9 +130,8 @@ if (!isValidSigRecovery(recovery)) { | ||
let ss = s | ||
if ((v > BigInt(28) && v % BigInt(2) === BigInt(1)) || v === BigInt(1) || v === BigInt(28)) { | ||
ss = Buffer.from(s) | ||
const ss = Uint8Array.from([...s]) | ||
if ((v > BigInt(28) && v % BIGINT_2 === BIGINT_1) || v === BIGINT_1 || v === BigInt(28)) { | ||
ss[0] |= 0x80 | ||
} | ||
return bufferToHex(Buffer.concat([setLengthLeft(r, 32), setLengthLeft(ss, 32)])) | ||
return bytesToHex(concatBytes(setLengthLeft(r, 32), setLengthLeft(ss, 32))) | ||
} | ||
@@ -120,16 +148,16 @@ | ||
export const fromRpcSig = function (sig: string): ECDSASignature { | ||
const buf: Buffer = toBuffer(sig) | ||
const bytes: Uint8Array = toBytes(sig) | ||
let r: Buffer | ||
let s: Buffer | ||
let r: Uint8Array | ||
let s: Uint8Array | ||
let v: bigint | ||
if (buf.length >= 65) { | ||
r = buf.slice(0, 32) | ||
s = buf.slice(32, 64) | ||
v = bufferToBigInt(buf.slice(64)) | ||
} else if (buf.length === 64) { | ||
if (bytes.length >= 65) { | ||
r = bytes.subarray(0, 32) | ||
s = bytes.subarray(32, 64) | ||
v = bytesToBigInt(bytes.subarray(64)) | ||
} else if (bytes.length === 64) { | ||
// Compact Signature Representation (https://eips.ethereum.org/EIPS/eip-2098) | ||
r = buf.slice(0, 32) | ||
s = buf.slice(32, 64) | ||
v = BigInt(bufferToInt(buf.slice(32, 33)) >> 7) | ||
r = bytes.subarray(0, 32) | ||
s = bytes.subarray(32, 64) | ||
v = BigInt(bytesToInt(bytes.subarray(32, 33)) >> 7) | ||
s[0] &= 0x7f | ||
@@ -142,3 +170,3 @@ } else { | ||
if (v < 27) { | ||
v = v + BigInt(27) | ||
v = v + BIGINT_27 | ||
} | ||
@@ -160,4 +188,4 @@ | ||
v: bigint, | ||
r: Buffer, | ||
s: Buffer, | ||
r: Uint8Array, | ||
s: Uint8Array, | ||
homesteadOrLater: boolean = true, | ||
@@ -174,9 +202,9 @@ chainId?: bigint | ||
const rBigInt = bufferToBigInt(r) | ||
const sBigInt = bufferToBigInt(s) | ||
const rBigInt = bytesToBigInt(r) | ||
const sBigInt = bytesToBigInt(s) | ||
if ( | ||
rBigInt === BigInt(0) || | ||
rBigInt === BIGINT_0 || | ||
rBigInt >= SECP256K1_ORDER || | ||
sBigInt === BigInt(0) || | ||
sBigInt === BIGINT_0 || | ||
sBigInt >= SECP256K1_ORDER | ||
@@ -200,6 +228,6 @@ ) { | ||
*/ | ||
export const hashPersonalMessage = function (message: Buffer): Buffer { | ||
assertIsBuffer(message) | ||
export const hashPersonalMessage = function (message: Uint8Array): Uint8Array { | ||
assertIsBytes(message) | ||
const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${message.length}`, 'utf-8') | ||
return Buffer.from(keccak256(Buffer.concat([prefix, message]))) | ||
} |
@@ -1,6 +0,6 @@ | ||
import { bufferToBigInt, bufferToHex, toBuffer } from './bytes' | ||
import { isHexString } from './internal' | ||
import { bytesToBigInt, bytesToHex, toBytes } from './bytes.js' | ||
import { isHexString } from './internal.js' | ||
import type { Address } from './address' | ||
import type { ToBufferInputTypes } from './bytes' | ||
import type { Address } from './address.js' | ||
import type { ToBytesInputTypes } from './bytes.js' | ||
@@ -10,9 +10,8 @@ /* | ||
*/ | ||
export type BigIntLike = bigint | PrefixedHexString | number | Buffer | ||
export type BigIntLike = bigint | PrefixedHexString | number | Uint8Array | ||
/* | ||
* A type that represents an input that can be converted to a Buffer. | ||
* A type that represents an input that can be converted to a Uint8Array. | ||
*/ | ||
export type BufferLike = | ||
| Buffer | ||
export type BytesLike = | ||
| Uint8Array | ||
@@ -22,3 +21,3 @@ | number[] | ||
| bigint | ||
| TransformableToBuffer | ||
| TransformabletoBytes | ||
| PrefixedHexString | ||
@@ -34,3 +33,3 @@ | ||
*/ | ||
export type AddressLike = Address | Buffer | PrefixedHexString | ||
export type AddressLike = Address | Uint8Array | PrefixedHexString | ||
@@ -41,20 +40,3 @@ export interface TransformabletoBytes { | ||
/* | ||
* A type that represents an object that has a `toArray()` method. | ||
*/ | ||
export interface TransformableToArray { | ||
toArray(): Uint8Array | ||
toBuffer?(): Buffer | ||
} | ||
/* | ||
* A type that represents an object that has a `toBuffer()` method. | ||
*/ | ||
export interface TransformableToBuffer { | ||
toBuffer(): Buffer | ||
toArray?(): Uint8Array | ||
} | ||
export type NestedUint8Array = Array<Uint8Array | NestedUint8Array> | ||
export type NestedBufferArray = Array<Buffer | NestedBufferArray> | ||
@@ -67,3 +49,3 @@ /** | ||
BigInt, | ||
Buffer, | ||
Uint8Array, | ||
PrefixedHexString, | ||
@@ -75,3 +57,3 @@ } | ||
[TypeOutput.BigInt]: bigint | ||
[TypeOutput.Buffer]: Buffer | ||
[TypeOutput.Uint8Array]: Uint8Array | ||
[TypeOutput.PrefixedHexString]: PrefixedHexString | ||
@@ -89,7 +71,7 @@ } | ||
export function toType<T extends TypeOutput>( | ||
input: ToBufferInputTypes, | ||
input: ToBytesInputTypes, | ||
outputType: T | ||
): TypeOutputReturnType[T] | ||
export function toType<T extends TypeOutput>( | ||
input: ToBufferInputTypes, | ||
input: ToBytesInputTypes, | ||
outputType: T | ||
@@ -112,11 +94,11 @@ ): TypeOutputReturnType[T] | undefined | null { | ||
const output = toBuffer(input) | ||
const output = toBytes(input) | ||
switch (outputType) { | ||
case TypeOutput.Buffer: | ||
case TypeOutput.Uint8Array: | ||
return output as TypeOutputReturnType[T] | ||
case TypeOutput.BigInt: | ||
return bufferToBigInt(output) as TypeOutputReturnType[T] | ||
return bytesToBigInt(output) as TypeOutputReturnType[T] | ||
case TypeOutput.Number: { | ||
const bigInt = bufferToBigInt(output) | ||
const bigInt = bytesToBigInt(output) | ||
if (bigInt > BigInt(Number.MAX_SAFE_INTEGER)) { | ||
@@ -130,3 +112,3 @@ throw new Error( | ||
case TypeOutput.PrefixedHexString: | ||
return bufferToHex(output) as TypeOutputReturnType[T] | ||
return bytesToHex(output) as TypeOutputReturnType[T] | ||
default: | ||
@@ -133,0 +115,0 @@ throw new Error('unknown outputType') |
@@ -0,2 +1,20 @@ | ||
import { BIGINT_0, BIGINT_1 } from './constants.js' | ||
/** Easy conversion from Gwei to wei */ | ||
export const GWEI_TO_WEI = BigInt(1000000000) | ||
export function formatBigDecimal( | ||
numerator: bigint, | ||
denominator: bigint, | ||
maxDecimalFactor: bigint | ||
): string { | ||
if (denominator === BIGINT_0) { | ||
denominator = BIGINT_1 | ||
} | ||
const full = numerator / denominator | ||
const fraction = ((numerator - full * denominator) * maxDecimalFactor) / denominator | ||
// zeros to be added post decimal are number of zeros in maxDecimalFactor - number of digits in fraction | ||
const zerosPostDecimal = String(maxDecimalFactor).length - 1 - String(fraction).length | ||
return `${full}.${'0'.repeat(zerosPostDecimal)}${fraction}` | ||
} |
@@ -1,6 +0,7 @@ | ||
import { Address } from './address' | ||
import { bigIntToHex } from './bytes' | ||
import { TypeOutput, toType } from './types' | ||
import { Address } from './address.js' | ||
import { bigIntToHex, bytesToHex, toBytes } from './bytes.js' | ||
import { BIGINT_0 } from './constants.js' | ||
import { TypeOutput, toType } from './types.js' | ||
import type { AddressLike, BigIntLike } from './types' | ||
import type { AddressLike, BigIntLike } from './types.js' | ||
@@ -29,3 +30,3 @@ /** | ||
export type WithdrawalBuffer = [Buffer, Buffer, Buffer, Buffer] | ||
export type WithdrawalBytes = [Uint8Array, Uint8Array, Uint8Array, Uint8Array] | ||
@@ -60,3 +61,3 @@ /** | ||
const validatorIndex = toType(validatorIndexData, TypeOutput.BigInt) | ||
const address = new Address(toType(addressData, TypeOutput.Buffer)) | ||
const address = addressData instanceof Address ? addressData : new Address(toBytes(addressData)) | ||
const amount = toType(amountData, TypeOutput.BigInt) | ||
@@ -67,3 +68,3 @@ | ||
public static fromValuesArray(withdrawalArray: WithdrawalBuffer) { | ||
public static fromValuesArray(withdrawalArray: WithdrawalBytes) { | ||
if (withdrawalArray.length !== 4) { | ||
@@ -81,28 +82,25 @@ throw Error(`Invalid withdrawalArray length expected=4 actual=${withdrawalArray.length}`) | ||
*/ | ||
public static toBufferArray(withdrawal: Withdrawal | WithdrawalData): WithdrawalBuffer { | ||
public static toBytesArray(withdrawal: Withdrawal | WithdrawalData): WithdrawalBytes { | ||
const { index, validatorIndex, address, amount } = withdrawal | ||
const indexBuffer = | ||
toType(index, TypeOutput.BigInt) === BigInt(0) | ||
? Buffer.alloc(0) | ||
: toType(index, TypeOutput.Buffer) | ||
const validatorIndexBuffer = | ||
toType(validatorIndex, TypeOutput.BigInt) === BigInt(0) | ||
? Buffer.alloc(0) | ||
: toType(validatorIndex, TypeOutput.Buffer) | ||
let addressBuffer | ||
if (address instanceof Address) { | ||
addressBuffer = (<Address>address).buf | ||
} else { | ||
addressBuffer = toType(address, TypeOutput.Buffer) | ||
} | ||
const amountBuffer = | ||
toType(amount, TypeOutput.BigInt) === BigInt(0) | ||
? Buffer.alloc(0) | ||
: toType(amount, TypeOutput.Buffer) | ||
const indexBytes = | ||
toType(index, TypeOutput.BigInt) === BIGINT_0 | ||
? new Uint8Array() | ||
: toType(index, TypeOutput.Uint8Array) | ||
const validatorIndexBytes = | ||
toType(validatorIndex, TypeOutput.BigInt) === BIGINT_0 | ||
? new Uint8Array() | ||
: toType(validatorIndex, TypeOutput.Uint8Array) | ||
const addressBytes = | ||
address instanceof Address ? (<Address>address).bytes : toType(address, TypeOutput.Uint8Array) | ||
return [indexBuffer, validatorIndexBuffer, addressBuffer, amountBuffer] | ||
const amountBytes = | ||
toType(amount, TypeOutput.BigInt) === BIGINT_0 | ||
? new Uint8Array() | ||
: toType(amount, TypeOutput.Uint8Array) | ||
return [indexBytes, validatorIndexBytes, addressBytes, amountBytes] | ||
} | ||
raw() { | ||
return Withdrawal.toBufferArray(this) | ||
return Withdrawal.toBytesArray(this) | ||
} | ||
@@ -114,3 +112,3 @@ | ||
validatorIndex: this.validatorIndex, | ||
address: this.address.buf, | ||
address: this.address.bytes, | ||
amount: this.amount, | ||
@@ -124,3 +122,3 @@ } | ||
validatorIndex: bigIntToHex(this.validatorIndex), | ||
address: '0x' + this.address.buf.toString('hex'), | ||
address: bytesToHex(this.address.bytes), | ||
amount: bigIntToHex(this.amount), | ||
@@ -127,0 +125,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
544968
0
178
3
8453
293
3
+ Added@nomicfoundation/ethereumjs-rlp@5.0.4(transitive)
+ Addedbindings@1.5.0(transitive)
+ Addedc-kzg@2.1.2(transitive)
+ Addedfile-uri-to-path@1.0.0(transitive)
+ Addednode-addon-api@5.1.0(transitive)
- Removed@chainsafe/ssz@^0.10.0
- Removed@chainsafe/as-sha256@0.3.1(transitive)
- Removed@chainsafe/persistent-merkle-tree@0.5.0(transitive)
- Removed@chainsafe/ssz@0.10.2(transitive)
- Removed@nomicfoundation/ethereumjs-rlp@5.0.3(transitive)