Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(util): implement assertIsUint8Array #2271

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions packages/util/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
zeros,
} from './bytes'
import { KECCAK256_NULL, KECCAK256_RLP } from './constants'
import { assertIsBuffer, assertIsHexString, assertIsString } from './helpers'
import { assertIsHexString, assertIsString, assertIsUint8Array } from './helpers'
import { stripHexPrefix } from './internal'

import type { BigIntLike, BufferLike } from './types'
Expand Down Expand Up @@ -44,8 +44,8 @@ export class Account {
)
}

public static fromRlpSerializedAccount(serialized: Buffer) {
const values = arrToBufArr(RLP.decode(Uint8Array.from(serialized)) as Uint8Array[]) as Buffer[]
public static fromRlpSerializedAccount(serialized: Uint8Array) {
const values = arrToBufArr(RLP.decode(serialized) as Uint8Array[]) as Buffer[]

if (!Array.isArray(values)) {
throw new Error('Invalid serialized account input. Must be array')
Expand Down Expand Up @@ -194,9 +194,9 @@ export const isValidChecksumAddress = function (
* @param from The address which is creating this new address
* @param nonce The nonce of the from account
*/
export const generateAddress = function (from: Buffer, nonce: Buffer): Buffer {
assertIsBuffer(from)
assertIsBuffer(nonce)
export const generateAddress = function (from: Buffer, nonce: Uint8Array): Buffer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from forgotten

assertIsUint8Array(from)
assertIsUint8Array(nonce)

if (bufferToBigInt(nonce) === BigInt(0)) {
// in RLP we want to encode null in the case of zero nonce
Expand All @@ -214,10 +214,14 @@ export const generateAddress = function (from: Buffer, nonce: Buffer): Buffer {
* @param salt A salt
* @param initCode The init code of the contract being created
*/
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
): Buffer {
assertIsUint8Array(from)
assertIsUint8Array(salt)
assertIsUint8Array(initCode)

if (from.length !== 20) {
throw new Error('Expected from to be of length 20')
Expand All @@ -236,7 +240,7 @@ export const generateAddress2 = function (from: Buffer, salt: Buffer, initCode:
/**
* Checks if the private key satisfies the rules of the curve secp256k1.
*/
export const isValidPrivate = function (privateKey: Buffer): boolean {
export const isValidPrivate = function (privateKey: Uint8Array): boolean {
return utils.isValidPrivateKey(privateKey)
}

Expand All @@ -247,7 +251,7 @@ export const isValidPrivate = function (privateKey: Buffer): boolean {
* @param sanitize Accept public keys in other formats
*/
export const isValidPublic = function (publicKey: Buffer, sanitize: boolean = false): boolean {
assertIsBuffer(publicKey)
assertIsUint8Array(publicKey)
if (publicKey.length === 64) {
// Convert to SEC1 for secp256k1
// Automatically checks whether point is on curve
Expand Down Expand Up @@ -278,24 +282,24 @@ export const isValidPublic = function (publicKey: Buffer, sanitize: boolean = fa
* @param sanitize Accept public keys in other formats
*/
export const pubToAddress = function (pubKey: Buffer, sanitize: boolean = false): Buffer {
assertIsBuffer(pubKey)
assertIsUint8Array(pubKey)
if (sanitize && pubKey.length !== 64) {
pubKey = Buffer.from(Point.fromHex(pubKey).toRawBytes(false).slice(1))
}
if (pubKey.length !== 64) {
throw new Error('Expected pubKey to be of length 64')
}
// Only take the lower 160bits of the hash
return Buffer.from(keccak256(pubKey)).slice(-20)
return Buffer.from(keccak256(pubKey).slice(-20))
}
export const publicToAddress = pubToAddress

/**
* Returns the ethereum public key of a given private key.
* @param privateKey A private key must be 256 bits wide
*/
export const privateToPublic = function (privateKey: Buffer): Buffer {
assertIsBuffer(privateKey)
export const privateToPublic = function (privateKey: Uint8Array): Buffer {
assertIsUint8Array(privateKey)
// skip the type flag and use the X, Y points
return Buffer.from(Point.fromPrivateKey(privateKey).toRawBytes(false).slice(1))
}
Expand All @@ -304,19 +308,19 @@ export const privateToPublic = function (privateKey: Buffer): Buffer {
* Returns the ethereum address of a given private key.
* @param privateKey A private key must be 256 bits wide
*/
export const privateToAddress = function (privateKey: Buffer): Buffer {
export const privateToAddress = function (privateKey: Uint8Array): Buffer {
return publicToAddress(privateToPublic(privateKey))
}

/**
* Converts a public key to the Ethereum format.
*/
export const importPublic = function (publicKey: Buffer): Buffer {
assertIsBuffer(publicKey)
export const importPublic = function (publicKey: Uint8Array): Buffer {
assertIsUint8Array(publicKey)
if (publicKey.length !== 64) {
publicKey = Buffer.from(Point.fromHex(publicKey).toRawBytes(false).slice(1))
}
return publicKey
return toBuffer(publicKey)
}

/**
Expand Down
10 changes: 5 additions & 5 deletions packages/util/src/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import { bigIntToBuffer, bufferToBigInt, toBuffer, zeros } from './bytes'
export class Address {
public readonly buf: Buffer

constructor(buf: Buffer) {
constructor(buf: Uint8Array) {
if (buf.length !== 20) {
throw new Error('Invalid address length')
}
this.buf = buf
this.buf = toBuffer(buf)
}

/**
Expand All @@ -39,7 +39,7 @@ export class Address {
* Returns an address for a given public key.
* @param pubKey The two points of an uncompressed key
*/
static fromPublicKey(pubKey: Buffer): Address {
static fromPublicKey(pubKey: Uint8Array): Address {
if (!Buffer.isBuffer(pubKey)) {
throw new Error('Public key should be Buffer')
}
Expand All @@ -51,7 +51,7 @@ export class Address {
* Returns an address for a given private key.
* @param privateKey A private key must be 256 bits wide
*/
static fromPrivateKey(privateKey: Buffer): Address {
static fromPrivateKey(privateKey: Uint8Array): Address {
if (!Buffer.isBuffer(privateKey)) {
throw new Error('Private key should be Buffer')
}
Expand All @@ -77,7 +77,7 @@ export class Address {
* @param salt A salt
* @param initCode The init code of the contract being created
*/
static generate2(from: Address, salt: Buffer, initCode: Buffer): Address {
static generate2(from: Address, salt: Buffer, initCode: Uint8Array): Address {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

salt

if (!Buffer.isBuffer(salt)) {
throw new Error('Expected salt to be a Buffer')
}
Expand Down
39 changes: 19 additions & 20 deletions packages/util/src/bytes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { assertIsArray, assertIsBuffer, assertIsHexString } from './helpers'
import { assertIsArray, assertIsHexString, assertIsUint8Array } from './helpers'
import { isHexPrefixed, isHexString, padToEven, stripHexPrefix } from './internal'

import type {
NestedBufferArray,
NestedUint8Array,
PrefixedHexString,
TransformableToArray,
Expand Down Expand Up @@ -72,7 +71,7 @@ const setLength = function (msg: Buffer, length: number, right: boolean) {
* @return (Buffer)
*/
export const setLengthLeft = function (msg: Buffer, length: number) {
assertIsBuffer(msg)
assertIsUint8Array(msg)
return setLength(msg, length, false)
}

Expand All @@ -84,7 +83,7 @@ export const setLengthLeft = function (msg: Buffer, length: number) {
* @return (Buffer)
*/
export const setLengthRight = function (msg: Buffer, length: number) {
assertIsBuffer(msg)
assertIsUint8Array(msg)
return setLength(msg, length, true)
}

Expand All @@ -107,8 +106,8 @@ const stripZeros = function (a: any): Buffer | number[] | string {
* @param a (Buffer)
* @return (Buffer)
*/
export const unpadBuffer = function (a: Buffer): Buffer {
assertIsBuffer(a)
export const unpadBuffer = function (a: Uint8Array): Buffer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have again this naming problem here. Additionally I don't think it's a good thing for conversion functions to change the type in between, so here, to then have Uint8Array as input and return a Buffer. This is also hard to deprecate at some point.

I wouldn't want to do too quick decisions on these kind of things, since implications on how we can deal with this later on can be significant.

So I think if we do we should likely go the a bit harder way and also add here parallel unpadUint8Array() and the like functionality and then switch over one-by-one at some point deprecate (in the sense of: write a deprecation note, so not directly remove) the Buffer functions (if we decide that we want that. We can also have both in parallel).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also have both in parallel.

There's no reason to support both because a Buffer is literally Uint8Array, see https://nodejs.org/api/buffer.html. It is also less buggy and more consistent in its behaviour. Also see nodejs/node#41588 (comment) by @paulmillr

The Buffer class is a subclass of JavaScript's Uint8Array class and extends it with methods that cover additional use cases. Node.js APIs accept plain Uint8Arrays wherever Buffers are supported as well.

assertIsUint8Array(a)
return stripZeros(a) as Buffer
}

Expand Down Expand Up @@ -202,15 +201,15 @@ export const toBuffer = function (v: ToBufferInputTypes): Buffer {
* Converts a `Buffer` into a `0x`-prefixed hex `String`.
* @param buf `Buffer` object to convert
*/
export const bufferToHex = function (buf: Buffer): string {
export const bufferToHex = function (buf: Uint8Array): string {
buf = toBuffer(buf)
return '0x' + buf.toString('hex')
return '0x' + Buffer.from(buf).toString('hex')
}

/**
* Converts a {@link Buffer} to a {@link bigint}
*/
export function bufferToBigInt(buf: Buffer) {
export function bufferToBigInt(buf: Uint8Array) {
const hex = bufferToHex(buf)
if (hex === '0x') {
return BigInt(0)
Expand All @@ -230,7 +229,7 @@ export function bigIntToBuffer(num: bigint) {
* @param buf `Buffer` object to convert
* @throws If the input number exceeds 53 bits.
*/
export const bufferToInt = function (buf: Buffer): number {
export const bufferToInt = function (buf: Uint8Array): number {
const res = Number(bufferToBigInt(buf))
if (!Number.isSafeInteger(res)) throw new Error('Number exceeds 53 bits')
return res
Expand All @@ -240,7 +239,7 @@ export const bufferToInt = function (buf: Buffer): number {
* Interprets a `Buffer` as a signed integer and returns a `BigInt`. Assumes 256-bit numbers.
* @param num Signed integer value
*/
export const fromSigned = function (num: Buffer): bigint {
export const fromSigned = function (num: Uint8Array): bigint {
return BigInt.asIntN(256, bufferToBigInt(num))
}

Expand Down Expand Up @@ -346,25 +345,25 @@ export const validateNoLeadingZeroes = function (values: { [key: string]: Buffer
}

/**
* Converts a {@link Uint8Array} or {@link NestedUint8Array} to {@link Buffer} or {@link NestedBufferArray}
* Converts a {@link Uint8Array} or {@link NestedUint8Array} to {@link Buffer} or {@link NestedUint8Array}
*/
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 {
export function arrToBufArr(arr: NestedUint8Array): NestedUint8Array
export function arrToBufArr(arr: Uint8Array | NestedUint8Array): Buffer | NestedUint8Array
export function arrToBufArr(arr: Uint8Array | NestedUint8Array): Buffer | NestedUint8Array {
if (!Array.isArray(arr)) {
return Buffer.from(arr)
}
return arr.map((a) => arrToBufArr(a))
}

/**
* Converts a {@link Buffer} or {@link NestedBufferArray} to {@link Uint8Array} or {@link NestedUint8Array}
* Converts a {@link Buffer} or {@link NestedUint8Array} to {@link Uint8Array} or {@link NestedUint8Array}
*/
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 {
export function bufArrToArr(arr: Uint8Array): Uint8Array
export function bufArrToArr(arr: NestedUint8Array): NestedUint8Array
export function bufArrToArr(arr: Uint8Array | NestedUint8Array): Uint8Array | NestedUint8Array
export function bufArrToArr(arr: Uint8Array | NestedUint8Array): Uint8Array | NestedUint8Array {
if (!Array.isArray(arr)) {
return Uint8Array.from(arr ?? [])
}
Expand Down
11 changes: 11 additions & 0 deletions packages/util/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ export const assertIsBuffer = function (input: Buffer): void {
}
}

/**
* Throws if input is not a `Uint8Array`
* @param {Buffer} input value to check
*/
export const assertIsUint8Array = function (input: Uint8Array): void {
if (!(input instanceof Uint8Array)) {
const msg = `This method only supports Buffer but input was: ${input}`
throw new Error(msg)
}
}

/**
* Throws if input is not an array
* @param {number[]} input value to check
Expand Down
4 changes: 2 additions & 2 deletions packages/util/src/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { recoverPublicKey, signSync } 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 { assertIsUint8Array } from './helpers'

export interface ECDSASignature {
v: bigint
Expand Down Expand Up @@ -187,7 +187,7 @@ export const isValidSignature = function (
* used to produce the signature.
*/
export const hashPersonalMessage = function (message: Buffer): Buffer {
assertIsBuffer(message)
assertIsUint8Array(message)
const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${message.length}`, 'utf-8')
return Buffer.from(keccak256(Buffer.concat([prefix, message])))
}
1 change: 0 additions & 1 deletion packages/util/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export interface TransformableToBuffer {
}

export type NestedUint8Array = Array<Uint8Array | NestedUint8Array>
export type NestedBufferArray = Array<Buffer | NestedBufferArray>

/**
* Type output options
Expand Down