From 4b5d6cff211869015875ae04ba8d15ce5e0a1948 Mon Sep 17 00:00:00 2001 From: Muhammad-Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> Date: Tue, 10 Oct 2023 00:46:50 +0200 Subject: [PATCH] improve type checking for Uint8Array --- packages/web3-eth-abi/src/utils.ts | 4 ++-- packages/web3-eth-accounts/src/account.ts | 16 +++++++--------- packages/web3-eth-accounts/src/common/utils.ts | 8 ++++++-- .../src/tx/transactionFactory.ts | 5 +++-- packages/web3-utils/src/converters.ts | 7 ++++--- packages/web3-utils/src/formatter.ts | 4 ++-- packages/web3-utils/src/uint8array.ts | 17 +++++++++++++++++ .../web3-validator/src/validation/address.ts | 5 +++-- packages/web3-validator/src/validation/bytes.ts | 11 +++-------- 9 files changed, 47 insertions(+), 30 deletions(-) diff --git a/packages/web3-eth-abi/src/utils.ts b/packages/web3-eth-abi/src/utils.ts index e8212c28329..081e4e544ce 100644 --- a/packages/web3-eth-abi/src/utils.ts +++ b/packages/web3-eth-abi/src/utils.ts @@ -16,7 +16,7 @@ along with web3.js. If not, see . */ import { AbiError } from 'web3-errors'; -import { isNullish, leftPad, rightPad, toHex } from 'web3-utils'; +import { isNullish, isUint8Array, leftPad, rightPad, toHex } from 'web3-utils'; import { AbiInput, AbiCoderStruct, @@ -189,7 +189,7 @@ export const formatParam = (type: string, _param: unknown): unknown => { // Format correct length for bytes[0-9]+ match = paramTypeBytes.exec(type); if (match) { - const hexParam = param instanceof Uint8Array ? toHex(param) : param; + const hexParam = isUint8Array(param) ? toHex(param) : param; // format to correct length const size = parseInt(match[1], 10); diff --git a/packages/web3-eth-accounts/src/account.ts b/packages/web3-eth-accounts/src/account.ts index dcdbe3fc4ed..323162d1e14 100644 --- a/packages/web3-eth-accounts/src/account.ts +++ b/packages/web3-eth-accounts/src/account.ts @@ -49,6 +49,7 @@ import { bytesToHex, fromUtf8, hexToBytes, + isUint8Array, numberToHex, randomBytes, sha3Raw, @@ -82,10 +83,7 @@ export const parseAndValidatePrivateKey = (data: Bytes, ignoreLength?: boolean): } try { - privateKeyUint8Array = - data instanceof Uint8Array || data?.constructor?.name === 'Uint8Array' - ? (data as Uint8Array) - : bytesToUint8Array(data); + privateKeyUint8Array = isUint8Array(data) ? (data ) : bytesToUint8Array(data); } catch { throw new InvalidPrivateKeyError(); } @@ -344,7 +342,7 @@ export const recover = ( const V_INDEX = 130; // r = first 32 bytes, s = second 32 bytes, v = last byte of signature const hashedMessage = prefixedOrR ? data : hashMessage(data); - let v = parseInt(signatureOrV.substring(V_INDEX),16); // 0x + r + s + v + let v = parseInt(signatureOrV.substring(V_INDEX), 16); // 0x + r + s + v if (v > 26) { v -= 27; } @@ -359,7 +357,7 @@ export const recover = ( const address = toChecksumAddress(`0x${publicHash.slice(-40)}`); return address; -}; +};; /** * Get the ethereum Address from a private key @@ -393,7 +391,7 @@ export const privateKeyToAddress = (privateKey: Bytes): string => { * Get the public key from a private key * * @param privateKey - String or Uint8Array of 32 bytes - * @param isCompressed - if true, will generate a 33 byte compressed public key instead of a 65 byte public key + * @param isCompressed - if true, will generate a 33 byte compressed public key instead of a 65 byte public key * @returns The public key * @example * ```ts @@ -401,7 +399,7 @@ export const privateKeyToAddress = (privateKey: Bytes): string => { * > "0x42beb65f179720abaa3ec9a70a539629cbbc5ec65bb57e7fc78977796837e537662dd17042e6449dc843c281067a4d6d8d1a1775a13c41901670d5de7ee6503a" // uncompressed public key * ``` */ - export const privateKeyToPublicKey = (privateKey: Bytes, isCompressed: boolean): string => { +export const privateKeyToPublicKey = (privateKey: Bytes, isCompressed: boolean): string => { const privateKeyUint8Array = parseAndValidatePrivateKey(privateKey); // Get public key from private key in compressed format @@ -495,7 +493,7 @@ export const encrypt = async ( salt = randomBytes(32); } - if (!(isString(password) || password instanceof Uint8Array)) { + if (!(isString(password) || isUint8Array(password))) { throw new InvalidPasswordError(); } diff --git a/packages/web3-eth-accounts/src/common/utils.ts b/packages/web3-eth-accounts/src/common/utils.ts index f243ede269d..a1d46c36140 100644 --- a/packages/web3-eth-accounts/src/common/utils.ts +++ b/packages/web3-eth-accounts/src/common/utils.ts @@ -15,7 +15,7 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ import { isHexPrefixed, isHexString } from 'web3-validator'; -import { bytesToHex, hexToBytes, numberToHex } from 'web3-utils'; +import { bytesToHex, hexToBytes, isUint8Array, numberToHex } from 'web3-utils'; import { secp256k1 } from '../tx/constants.js'; import { Hardfork } from './enums.js'; import { ToBytesInputTypes, TypeOutput, TypeOutputReturnType } from './types.js'; @@ -331,6 +331,10 @@ export const toUint8Array = function (v: ToBytesInputTypes): Uint8Array { return v; } + if (v?.constructor?.name === 'Uint8Array') { + return Uint8Array.from(v as unknown as Uint8Array); + } + if (Array.isArray(v)) { return Uint8Array.from(v); } @@ -420,7 +424,7 @@ const setLength = function (msg: Uint8Array, length: number, right: boolean) { * @param {Uint8Array} input value to check */ export function assertIsUint8Array(input: unknown): asserts input is Uint8Array { - if (!(input instanceof Uint8Array)) { + if (!isUint8Array(input)) { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions const msg = `This method only supports Uint8Array but input was: ${input}`; throw new Error(msg); diff --git a/packages/web3-eth-accounts/src/tx/transactionFactory.ts b/packages/web3-eth-accounts/src/tx/transactionFactory.ts index 605b32ca1aa..16be0c7c9cd 100644 --- a/packages/web3-eth-accounts/src/tx/transactionFactory.ts +++ b/packages/web3-eth-accounts/src/tx/transactionFactory.ts @@ -14,6 +14,7 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +import { isUint8Array } from 'web3-utils'; import { toUint8Array, uint8ArrayToBigInt } from '../common/utils.js'; import { FeeMarketEIP1559Transaction } from './eip1559Transaction.js'; import { AccessListEIP2930Transaction } from './eip2930Transaction.js'; @@ -104,8 +105,8 @@ export class TransactionFactory { * @param txOptions - The transaction options */ public static fromBlockBodyData(data: Uint8Array | Uint8Array[], txOptions: TxOptions = {}) { - if (data instanceof Uint8Array || data?.constructor?.name === 'Uint8Array') { - return this.fromSerializedData(data as Uint8Array, txOptions); + if (isUint8Array(data)) { + return this.fromSerializedData(data , txOptions); } if (Array.isArray(data)) { // It is a legacy transaction diff --git a/packages/web3-utils/src/converters.ts b/packages/web3-utils/src/converters.ts index 66e3047024d..649545df7dc 100644 --- a/packages/web3-utils/src/converters.ts +++ b/packages/web3-utils/src/converters.ts @@ -37,6 +37,7 @@ import { InvalidNumberError, InvalidUnitError, } from 'web3-errors'; +import { ensureIfUint8Array, isUint8Array } from './uint8array.js'; const base = BigInt(10); const expo10 = (expo: number) => base ** BigInt(expo); @@ -88,8 +89,8 @@ export type EtherUnits = keyof typeof ethUnitMap; export const bytesToUint8Array = (data: Bytes): Uint8Array | never => { validator.validate(['bytes'], [data]); - if (data instanceof Uint8Array || data?.constructor?.name === 'Uint8Array') { - return data as Uint8Array; + if (isUint8Array(data)) { + return data; } if (Array.isArray(data)) { @@ -588,7 +589,7 @@ export const toChecksumAddress = (address: Address): string => { // calling `Uint8Array.from` because `noble-hashes` checks with `instanceof Uint8Array` that fails in some edge cases: // https://github.com/paulmillr/noble-hashes/issues/25#issuecomment-1750106284 const hash = utils.uint8ArrayToHexString( - keccak256(Uint8Array.from(utf8ToBytes(lowerCaseAddress))), + keccak256(ensureIfUint8Array(utf8ToBytes(lowerCaseAddress))), ); if ( diff --git a/packages/web3-utils/src/formatter.ts b/packages/web3-utils/src/formatter.ts index 0cfeb600b9a..1901e1fde21 100644 --- a/packages/web3-utils/src/formatter.ts +++ b/packages/web3-utils/src/formatter.ts @@ -20,7 +20,7 @@ import { isNullish, isObject, JsonSchema, utils, ValidationSchemaInput } from 'w import { bytesToUint8Array, bytesToHex, numberToHex, toBigInt } from './converters.js'; import { mergeDeep } from './objects.js'; import { padLeft } from './string_manipulation.js'; -import { uint8ArrayConcat } from './uint8array.js'; +import { isUint8Array, uint8ArrayConcat } from './uint8array.js'; const { parseBaseType } = utils; @@ -112,7 +112,7 @@ export const convertScalarValue = (value: unknown, ethType: string, format: Data let paddedValue; if (baseTypeSize) { if (typeof value === 'string') paddedValue = padLeft(value, baseTypeSize * 2); - else if (value instanceof Uint8Array) { + else if (isUint8Array(value)) { paddedValue = uint8ArrayConcat( new Uint8Array(baseTypeSize - value.length), value, diff --git a/packages/web3-utils/src/uint8array.ts b/packages/web3-utils/src/uint8array.ts index 0f1173b04e7..772e648920c 100644 --- a/packages/web3-utils/src/uint8array.ts +++ b/packages/web3-utils/src/uint8array.ts @@ -14,6 +14,23 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ + +export function isUint8Array(data: unknown | Uint8Array): data is Uint8Array { + return ( + data instanceof Uint8Array || + (data as { constructor: { name: string } })?.constructor?.name === 'Uint8Array' + ); +} +export function ensureIfUint8Array(data: T) { + if ( + !(data instanceof Uint8Array) && + (data as { constructor: { name: string } })?.constructor?.name === 'Uint8Array' + ) { + return Uint8Array.from(data as unknown as Uint8Array); + } + return data; +} + export function uint8ArrayConcat(...parts: Uint8Array[]): Uint8Array { const length = parts.reduce((prev, part) => { const agg = prev + part.length; diff --git a/packages/web3-validator/src/validation/address.ts b/packages/web3-validator/src/validation/address.ts index f1c5b820a35..2fb61c5b9a3 100644 --- a/packages/web3-validator/src/validation/address.ts +++ b/packages/web3-validator/src/validation/address.ts @@ -20,6 +20,7 @@ import { utf8ToBytes } from 'ethereum-cryptography/utils.js'; import { ValidInputTypes } from '../types.js'; import { uint8ArrayToHexString } from '../utils.js'; import { isHexStrict } from './string.js'; +import { isUint8Array } from './bytes.js'; /** * Checks the checksum of a given address. Will also return false on non-checksum addresses. @@ -47,13 +48,13 @@ export const checkAddressCheckSum = (data: string): boolean => { * Checks if a given string is a valid Ethereum address. It will also check the checksum, if the address has upper and lowercase letters. */ export const isAddress = (value: ValidInputTypes, checkChecksum = true) => { - if (typeof value !== 'string' && !(value instanceof Uint8Array)) { + if (typeof value !== 'string' && !isUint8Array(value)) { return false; } let valueToCheck: string; - if (value instanceof Uint8Array) { + if (isUint8Array(value)) { valueToCheck = uint8ArrayToHexString(value); } else if (typeof value === 'string' && !isHexStrict(value)) { valueToCheck = value.toLowerCase().startsWith('0x') ? value : `0x${value}`; diff --git a/packages/web3-validator/src/validation/bytes.ts b/packages/web3-validator/src/validation/bytes.ts index 5cc4bcf63d8..ab6ab2fbdd0 100644 --- a/packages/web3-validator/src/validation/bytes.ts +++ b/packages/web3-validator/src/validation/bytes.ts @@ -22,7 +22,7 @@ import { isHexStrict } from './string.js'; /** * checks input if typeof data is valid Uint8Array input */ -export const isUint8Array = (data: ValidInputTypes) => +export const isUint8Array = (data: ValidInputTypes): data is Uint8Array => data instanceof Uint8Array || data?.constructor?.name === 'Uint8Array'; export const isBytes = ( @@ -31,12 +31,7 @@ export const isBytes = ( abiType: 'bytes', }, ) => { - if ( - typeof value !== 'string' && - !Array.isArray(value) && - !(value instanceof Uint8Array) && - value?.constructor?.name !== 'Uint8Array' - ) { + if (typeof value !== 'string' && !Array.isArray(value) && !isUint8Array(value)) { return false; } @@ -63,7 +58,7 @@ export const isBytes = ( } valueToCheck = new Uint8Array(value); } else { - valueToCheck = value as Uint8Array; + valueToCheck = value ; } if (options?.abiType) {