From 9a6edc54f5a062d349ecda32e5905deacc4f27b1 Mon Sep 17 00:00:00 2001 From: rileystephens28 Date: Wed, 5 Jun 2024 16:23:05 -0500 Subject: [PATCH] Enforce checksum address user inputs --- src/address/address.ts | 29 ++- src/address/checks.ts | 27 ++- src/address/index.ts | 2 +- src/contract/contract.ts | 8 +- src/contract/factory.ts | 4 +- src/hash/typed-data.ts | 2 +- src/providers/abstract-provider.ts | 6 +- src/providers/provider.ts | 1 - src/quais.ts | 272 +++++++++++++++++++--------- src/signers/abstract-signer.ts | 24 +-- src/transaction/accesslist.ts | 2 + src/transaction/quai-transaction.ts | 40 ++-- src/transaction/utxo.ts | 25 ++- src/wallet/base-wallet.ts | 18 +- 14 files changed, 295 insertions(+), 165 deletions(-) diff --git a/src/address/address.ts b/src/address/address.ts index f6dabfe2..1850fa2e 100644 --- a/src/address/address.ts +++ b/src/address/address.ts @@ -14,7 +14,7 @@ import { import type { SignatureLike } from '../crypto/index.js'; -function getChecksumAddress(address: string): string { +export function formatMixedCaseChecksumAddress(address: string): string { address = address.toLowerCase(); const chars = address.substring(2).split(''); @@ -39,11 +39,11 @@ function getChecksumAddress(address: string): string { } /** - * Returns a normalized and checksumed address for `address`. This accepts non-checksum addresses, checksum addresses - * and [[getIcapAddress]] formats. + * Returns a normalized and checksumed address for `address`. This accepts non-checksum addressesa and checksum + * addresses. * - * The checksum in Ethereum uses the capitalization (upper-case vs lower-case) of the characters within an address to - * encode its checksum, which offers, on average, a checksum of 15-bits. + * The checksum in Quai uses the capitalization (upper-case vs lower-case) of the characters within an address to encode + * its checksum, which offers, on average, a checksum of 15-bits. * * If `address` contains both upper-case and lower-case, it is assumed to already be a checksum address and its checksum * is validated, and if the address fails its expected checksum an error is thrown. @@ -60,19 +60,11 @@ function getChecksumAddress(address: string): string { * getAddress('0x8ba1f109551bd432803012645ac136ddd64dba72'); * //_result: * - * // Converts ICAP address and adds checksum - * getAddress('XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36'); - * //_result: - * * // Throws an error if an address contains mixed case, * // but the checksum fails * getAddress('0x8Ba1f109551bD432803012645Ac136ddd64DBA72'); * //_error: * ``` - * - * @todo Revise this documentation as ICAP addresses are not supported - * - * @todo GetIcapAddress has been removed, link must be revised or removed */ export function getAddress(address: string): string { assertArgument(typeof address === 'string', 'invalid address', 'address', address); @@ -83,12 +75,13 @@ export function getAddress(address: string): string { address = '0x' + address; } - const result = getChecksumAddress(address); + const result = formatMixedCaseChecksumAddress(address); - // It is a checksummed address with a bad checksum + // If original address is mix cased and recomputed version doesn't + // match the original this could indicate a potential typo or mispaste. assertArgument( !address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) || result === address, - 'bad address checksum', + 'invalid address checksum', 'address', address, ); @@ -96,7 +89,7 @@ export function getAddress(address: string): string { return result; } - assertArgument(false, 'invalid address', 'address', address); + assertArgument(false, 'invalid address string format', 'address', address); } export function getContractAddress(from: string, nonce: BigNumberish, data: BytesLike): string { @@ -135,4 +128,4 @@ export function computeAddress(key: string | SigningKey): string { */ export function recoverAddress(digest: BytesLike, signature: SignatureLike): string { return computeAddress(SigningKey.recoverPublicKey(digest, signature)); -} \ No newline at end of file +} diff --git a/src/address/checks.ts b/src/address/checks.ts index 0856d4e2..b3245302 100644 --- a/src/address/checks.ts +++ b/src/address/checks.ts @@ -1,6 +1,6 @@ import { assertArgument } from '../utils/index.js'; -import { getAddress } from './address.js'; +import { formatMixedCaseChecksumAddress, getAddress } from './address.js'; import type { Addressable, AddressLike } from './index.js'; @@ -63,7 +63,7 @@ async function checkAddress(target: any, promise: Promise): Promi if (result == null || result === '0x0000000000000000000000000000000000000000') { assertArgument(false, 'invalid AddressLike value; did not resolve to a value address', 'target', target); } - return getAddress(result); + return result; } /** @@ -88,8 +88,6 @@ async function checkAddress(target: any, promise: Promise): Promi * contract = new Contract(addr, []); * resolveAddress(contract, provider); * //_result: - * - * * ``` * * @param {AddressLike} target - The target to resolve to an address. @@ -100,7 +98,7 @@ async function checkAddress(target: any, promise: Promise): Promi export function resolveAddress(target: AddressLike): string | Promise { if (typeof target === 'string') { if (target.match(/^0x[0-9a-f]{40}$/i)) { - return getAddress(target); + return target; } } else if (isAddressable(target)) { return checkAddress(target, target.getAddress()); @@ -110,3 +108,22 @@ export function resolveAddress(target: AddressLike): string | Promise { assertArgument(false, 'unsupported addressable value', 'target', target); } + +/** + * Checks if the address is a valid mixed case checksummed address. + * + * @category Address + * @param address - The address to validate. + * + * @returns True if the address is a valid mixed case checksummed address. + */ +export function validateAddress(address: string): void { + assertArgument(typeof address === 'string', 'address must be string', 'address', address); + assertArgument( + Boolean(address.match(/^(0x)?[0-9a-fA-F]{40}$/)), + 'invalid address string format', + 'address', + address, + ); + assertArgument(formatMixedCaseChecksumAddress(address) === address, 'invalid address checksum', 'address', address); +} diff --git a/src/address/index.ts b/src/address/index.ts index 6c8cd27a..a5000c1a 100644 --- a/src/address/index.ts +++ b/src/address/index.ts @@ -35,4 +35,4 @@ export { getAddress, computeAddress, recoverAddress } from './address.js'; export { getCreateAddress, getCreate2Address } from './contract-address.js'; -export { isAddressable, isAddress, resolveAddress } from './checks.js'; +export { isAddressable, isAddress, resolveAddress, validateAddress } from './checks.js'; diff --git a/src/contract/contract.ts b/src/contract/contract.ts index 2c1c557a..38760424 100644 --- a/src/contract/contract.ts +++ b/src/contract/contract.ts @@ -1,5 +1,5 @@ import { Interface, Typed } from '../abi/index.js'; -import { isAddressable, resolveAddress } from '../address/index.js'; +import { isAddressable, resolveAddress, validateAddress } from '../address/index.js'; // import from provider.ts instead of index.ts to prevent circular dep // from quaiscanProvider import { @@ -205,9 +205,11 @@ function buildWrappedFallback(contract: BaseContract): WrappedFallback { const tx: ContractTransaction = await copyOverrides<'data'>(overrides, ['data']); tx.to = await contract.getAddress(); + validateAddress(tx.to); if (tx.from) { tx.from = await resolveAddress(tx.from); + validateAddress(tx.from); } const iface = contract.interface; @@ -749,8 +751,8 @@ export class BaseContract implements Addressable, EventEmitterable = Array, I = BaseContract ); if (this.runner instanceof Wallet) { + validateAddress(this.runner.address); tx.from = this.runner.address; } const grindedTx = await this.grindContractAddress(tx); @@ -170,7 +171,6 @@ export class ContractFactory = Array, I = BaseContract while (i < 10000) { const contractAddress = getContractAddress(sender, BigInt(tx.nonce || 0), tx.data || ''); const contractShard = getZoneForAddress(contractAddress); - console.log('contractAddress ', contractAddress); const utxo = isQiAddress(contractAddress); if (contractShard === toShard && !utxo) { return tx; diff --git a/src/hash/typed-data.ts b/src/hash/typed-data.ts index d01b239e..ef154d66 100644 --- a/src/hash/typed-data.ts +++ b/src/hash/typed-data.ts @@ -127,7 +127,7 @@ const domainChecks: Record any> = { }, verifyingContract: function (value: any) { try { - return getAddress(value).toLowerCase(); + return getAddress(value); // eslint-disable-next-line no-empty } catch (error) {} assertArgument(false, `invalid domain value "verifyingContract"`, 'domain.verifyingContract', value); diff --git a/src/providers/abstract-provider.ts b/src/providers/abstract-provider.ts index cf0becc8..708d1e8d 100644 --- a/src/providers/abstract-provider.ts +++ b/src/providers/abstract-provider.ts @@ -1100,10 +1100,8 @@ export class AbstractProvider implements Provider { ? 'address' in request[key][0] ? ((request)[key]).map((it) => resolveAddress(hexlify(it.address))) : ((request)[key]).map((it) => - resolveAddress( - getAddress( - keccak256('0x' + SigningKey.computePublicKey(it.pub_key).substring(4)).substring(26), - ), + getAddress( + keccak256('0x' + SigningKey.computePublicKey(it.pub_key).substring(4)).substring(26), ), ) : resolveAddress((request)[key]); diff --git a/src/providers/provider.ts b/src/providers/provider.ts index da53f679..ce9333cf 100644 --- a/src/providers/provider.ts +++ b/src/providers/provider.ts @@ -128,7 +128,6 @@ export class FeeData { } export function addressFromTransactionRequest(tx: TransactionRequest): AddressLike { - //return 'from' in tx ? tx.from : tx.inputs[0].address; if ('from' in tx) { return tx.from; } diff --git a/src/quais.ts b/src/quais.ts index 004f6c67..06796e26 100644 --- a/src/quais.ts +++ b/src/quais.ts @@ -28,10 +28,15 @@ export { // ADDRESS export { getAddress, - computeAddress, recoverAddress, - getCreateAddress, getCreate2Address, - isAddressable, isAddress, resolveAddress -} from "./address/index.js"; + computeAddress, + recoverAddress, + getCreateAddress, + getCreate2Address, + isAddressable, + isAddress, + resolveAddress, + validateAddress, +} from './address/index.js'; //CONSTANTS export { @@ -78,77 +83,123 @@ export { // HASH export { id, - hashMessage, verifyMessage, - solidityPacked, solidityPackedKeccak256, solidityPackedSha256, + hashMessage, + verifyMessage, + solidityPacked, + solidityPackedKeccak256, + solidityPackedSha256, TypedDataEncoder, verifyTypedData, } from './hash/index.js'; // PROVIDERS export { - Block, FeeData, Log, TransactionReceipt, TransactionResponse, - + Block, + FeeData, + Log, + TransactionReceipt, + TransactionResponse, AbstractProvider, - - JsonRpcApiProvider, JsonRpcProvider, - + JsonRpcApiProvider, + JsonRpcProvider, BrowserProvider, - - SocketProvider, WebSocketProvider, - + SocketProvider, + WebSocketProvider, Network, + SocketBlockSubscriber, + SocketEventSubscriber, + SocketPendingSubscriber, + SocketSubscriber, + UnmanagedSubscriber, + copyRequest, +} from './providers/index.js'; - SocketBlockSubscriber, SocketEventSubscriber, SocketPendingSubscriber, - SocketSubscriber, UnmanagedSubscriber, - - copyRequest -} from "./providers/index.js"; - -export { - AbstractSigner, VoidSigner, -} from "./signers/index.js"; +export { AbstractSigner, VoidSigner } from './signers/index.js'; // TRANSACTION -export { - accessListify, - AbstractTransaction, FewestCoinSelector, - QiTransaction -} from "./transaction/index.js"; +export { accessListify, AbstractTransaction, FewestCoinSelector, QiTransaction } from './transaction/index.js'; // UTILS export { - concat, dataLength, dataSlice, getBytes, getBytesCopy, hexlify, - isHexString, isBytesLike, stripZerosLeft, zeroPadBytes, zeroPadValue, - defineProperties, resolveProperties, - assert, assertArgument, assertArgumentCount, assertNormalize, assertPrivate, + concat, + dataLength, + dataSlice, + getBytes, + getBytesCopy, + hexlify, + isHexString, + isBytesLike, + stripZerosLeft, + zeroPadBytes, + zeroPadValue, + defineProperties, + resolveProperties, + assert, + assertArgument, + assertArgumentCount, + assertNormalize, + assertPrivate, makeError, - isCallException, isError, + isCallException, + isError, EventPayload, - FetchRequest, FetchResponse, FetchCancelSignal, + FetchRequest, + FetchResponse, + FetchCancelSignal, FixedNumber, - getBigInt, getNumber, getUint, toBeArray, toBigInt, toBeHex, toNumber, toQuantity, - fromTwos, toTwos, mask, - formatQuai, parseQuai, formatEther, parseEther, formatUnits, parseUnits, - uuidV4, getTxType, getZoneForAddress, getAddressDetails, isQiAddress, -} from "./utils/index.js"; + getBigInt, + getNumber, + getUint, + toBeArray, + toBigInt, + toBeHex, + toNumber, + toQuantity, + fromTwos, + toTwos, + mask, + formatQuai, + parseQuai, + formatEther, + parseEther, + formatUnits, + parseUnits, + uuidV4, + getTxType, + getZoneForAddress, + getAddressDetails, + isQiAddress, +} from './utils/index.js'; export { - decodeBase58, encodeBase58, - decodeBase64, encodeBase64, - decodeProtoTransaction, encodeProtoTransaction, - decodeProtoWorkObject, encodeProtoWorkObject, - toUtf8Bytes, toUtf8CodePoints, toUtf8String, Utf8ErrorFuncs, -} from "./encoding/index.js"; + decodeBase58, + encodeBase58, + decodeBase64, + encodeBase64, + decodeProtoTransaction, + encodeProtoTransaction, + decodeProtoWorkObject, + encodeProtoWorkObject, + toUtf8Bytes, + toUtf8CodePoints, + toUtf8String, + Utf8ErrorFuncs, +} from './encoding/index.js'; // WALLET export { Mnemonic, - BaseWallet, QuaiHDWallet, HDNodeVoidWallet, QiHDWallet, + BaseWallet, + QuaiHDWallet, + HDNodeVoidWallet, + QiHDWallet, Wallet, isKeystoreJson, - decryptKeystoreJsonSync, decryptKeystoreJson, - encryptKeystoreJson, encryptKeystoreJsonSync, -} from "./wallet/index.js"; + decryptKeystoreJsonSync, + decryptKeystoreJson, + encryptKeystoreJson, + encryptKeystoreJsonSync, +} from './wallet/index.js'; // WORDLIST export { Wordlist, LangEn, LangEs, WordlistOwl, WordlistOwlA, wordlists } from './wordlists/index.js'; @@ -172,12 +223,22 @@ export type { Addressable, AddressLike } from './address/index.js'; // CONTRACT export type { - ConstantContractMethod, ContractEvent, ContractEventArgs, ContractEventName, - ContractInterface, ContractMethod, ContractMethodArgs, ContractTransaction, - DeferredTopicFilter, Overrides, - ContractRunner, BaseContractMethod, ContractDeployTransaction, PostfixOverrides, - WrappedFallback -} from "./contract/index.js"; + ConstantContractMethod, + ContractEvent, + ContractEventArgs, + ContractEventName, + ContractInterface, + ContractMethod, + ContractMethodArgs, + ContractTransaction, + DeferredTopicFilter, + Overrides, + ContractRunner, + BaseContractMethod, + ContractDeployTransaction, + PostfixOverrides, + WrappedFallback, +} from './contract/index.js'; // CRYPTO export type { ProgressCallback, SignatureLike } from './crypto/index.js'; @@ -188,23 +249,42 @@ export type { TypedDataDomain, TypedDataField } from './hash/index.js'; // PROVIDERS export type { Provider, - AbstractProviderOptions, - - AbstractProviderPlugin, BlockParams, BlockTag, DebugEventBrowserProvider, - Eip1193Provider, EventFilter, Filter, FilterByBlockHash, - JsonRpcApiProviderOptions, JsonRpcError, JsonRpcPayload, JsonRpcResult, - JsonRpcTransactionRequest, LogParams, MinedBlock, MinedTransactionResponse, Networkish, - OrphanFilter, PerformActionFilter, PerformActionRequest, PerformActionTransaction, - PreparedTransactionRequest, ProviderEvent, Subscriber, Subscription, TopicFilter, - TransactionReceiptParams, TransactionRequest, TransactionResponseParams, - WebSocketCreator, WebSocketLike -} from "./providers/index.js"; + AbstractProviderPlugin, + BlockParams, + BlockTag, + DebugEventBrowserProvider, + Eip1193Provider, + EventFilter, + Filter, + FilterByBlockHash, + JsonRpcApiProviderOptions, + JsonRpcError, + JsonRpcPayload, + JsonRpcResult, + JsonRpcTransactionRequest, + LogParams, + MinedBlock, + MinedTransactionResponse, + Networkish, + OrphanFilter, + PerformActionFilter, + PerformActionRequest, + PerformActionTransaction, + PreparedTransactionRequest, + ProviderEvent, + Subscriber, + Subscription, + TopicFilter, + TransactionReceiptParams, + TransactionRequest, + TransactionResponseParams, + WebSocketCreator, + WebSocketLike, +} from './providers/index.js'; // SIGNERS -export type { - Signer, -} from "./signers/index.js"; +export type { Signer } from './signers/index.js'; // TRANSACTION export type { AccessList, AccessListish, AccessListEntry, TransactionLike } from './transaction/index.js'; @@ -212,32 +292,44 @@ export type { AccessList, AccessListish, AccessListEntry, TransactionLike } from // UTILS export type { BytesLike, - BigNumberish, Numeric, + BigNumberish, + Numeric, ErrorCode, FixedFormat, - GetUrlResponse, - FetchPreflightFunc, FetchProcessFunc, FetchRetryFunc, - FetchGatewayFunc, FetchGetUrlFunc, - - quaisError, UnknownError, NotImplementedError, UnsupportedOperationError, NetworkError, - ServerError, TimeoutError, BadDataError, CancelledError, BufferOverrunError, - NumericFaultError, InvalidArgumentError, MissingArgumentError, UnexpectedArgumentError, - CallExceptionError, InsufficientFundsError, NonceExpiredError, - ReplacementUnderpricedError, TransactionReplacedError, + FetchPreflightFunc, + FetchProcessFunc, + FetchRetryFunc, + FetchGatewayFunc, + FetchGetUrlFunc, + quaisError, + UnknownError, + NotImplementedError, + UnsupportedOperationError, + NetworkError, + ServerError, + TimeoutError, + BadDataError, + CancelledError, + BufferOverrunError, + NumericFaultError, + InvalidArgumentError, + MissingArgumentError, + UnexpectedArgumentError, + CallExceptionError, + InsufficientFundsError, + NonceExpiredError, + ReplacementUnderpricedError, + TransactionReplacedError, ActionRejectedError, CodedquaisError, + CallExceptionAction, + CallExceptionTransaction, + EventEmitterable, + Listener, +} from './utils/index.js'; - CallExceptionAction, CallExceptionTransaction, - EventEmitterable, Listener -} from "./utils/index.js"; - -export type { - Utf8ErrorFunc, UnicodeNormalizationForm, Utf8ErrorReason, -} from "./encoding/index.js"; +export type { Utf8ErrorFunc, UnicodeNormalizationForm, Utf8ErrorReason } from './encoding/index.js'; // WALLET -export type { - KeystoreAccount, EncryptOptions -} from "./wallet/index.js"; - +export type { KeystoreAccount, EncryptOptions } from './wallet/index.js'; diff --git a/src/signers/abstract-signer.ts b/src/signers/abstract-signer.ts index 273d552b..5fc2d968 100644 --- a/src/signers/abstract-signer.ts +++ b/src/signers/abstract-signer.ts @@ -4,22 +4,22 @@ * * @section api/providers/abstract-signer: Subclassing Signer [abstract-signer] */ -import { AddressLike, resolveAddress } from '../address/index.js'; +import { AddressLike, resolveAddress, validateAddress } from '../address/index.js'; +import { defineProperties, getBigInt, resolveProperties, assert, assertArgument, isQiAddress } from '../utils/index.js'; import { - defineProperties, getBigInt, resolveProperties, - assert, assertArgument, isQiAddress -} from "../utils/index.js"; -import {addressFromTransactionRequest, copyRequest, QiTransactionRequest, QuaiTransactionRequest} from "../providers/provider.js"; + addressFromTransactionRequest, + copyRequest, + QiTransactionRequest, + QuaiTransactionRequest, +} from '../providers/provider.js'; import type { TypedDataDomain, TypedDataField } from '../hash/index.js'; import type { TransactionLike } from '../transaction/index.js'; -import type { - BlockTag, Provider, TransactionRequest, TransactionResponse -} from "../providers/provider.js"; -import type { Signer } from "./signer.js"; -import { getTxType } from "../utils/index.js"; -import {QiTransaction, QiTransactionLike, QuaiTransaction, QuaiTransactionLike} from "../transaction/index.js"; +import type { BlockTag, Provider, TransactionRequest, TransactionResponse } from '../providers/provider.js'; +import type { Signer } from './signer.js'; +import { getTxType } from '../utils/index.js'; +import { QiTransaction, QiTransactionLike, QuaiTransaction, QuaiTransactionLike } from '../transaction/index.js'; import { toZone, Zone } from '../constants/index.js'; function checkProvider(signer: AbstractSigner, operation: string): Provider { @@ -34,6 +34,7 @@ async function populate(signer: AbstractSigner, tx: TransactionRequest): Promise if (pop.to != null) { pop.to = resolveAddress(pop.to); + validateAddress(pop.to); } if (pop.from != null) { @@ -45,6 +46,7 @@ async function populate(signer: AbstractSigner, tx: TransactionRequest): Promise } else { pop.from = signer.getAddress(); } + validateAddress(pop.from); return await resolveProperties(pop); } diff --git a/src/transaction/accesslist.ts b/src/transaction/accesslist.ts index 738eec65..ff207955 100644 --- a/src/transaction/accesslist.ts +++ b/src/transaction/accesslist.ts @@ -1,9 +1,11 @@ +import { validateAddress } from '../address/index.js'; import { getAddress } from '../address/index.js'; import { assertArgument, isHexString } from '../utils/index.js'; import type { AccessList, AccessListish } from './index.js'; function accessSetify(addr: string, storageKeys: Array): { address: string; storageKeys: Array } { + validateAddress(addr); return { address: getAddress(addr), storageKeys: storageKeys.map((storageKey, index) => { diff --git a/src/transaction/quai-transaction.ts b/src/transaction/quai-transaction.ts index 2382d43d..f1326e1d 100644 --- a/src/transaction/quai-transaction.ts +++ b/src/transaction/quai-transaction.ts @@ -1,5 +1,5 @@ -import { keccak256, Signature } from "../crypto/index.js"; -import { AccessList, accessListify, AccessListish, AbstractTransaction, TransactionLike } from "./index.js"; +import { keccak256, Signature } from '../crypto/index.js'; +import { AccessList, accessListify, AccessListish, AbstractTransaction, TransactionLike } from './index.js'; import { assert, assertArgument, @@ -9,13 +9,16 @@ import { getBytes, getNumber, getZoneForAddress, - hexlify, isQiAddress, - toBeArray, toBigInt, zeroPadValue -} from "../utils/index.js"; + hexlify, + isQiAddress, + toBeArray, + toBigInt, + zeroPadValue, +} from '../utils/index.js'; import { decodeProtoTransaction, encodeProtoTransaction } from '../encoding/index.js'; -import { getAddress, recoverAddress } from "../address/index.js"; -import { formatNumber, handleNumber } from "../providers/format.js"; -import { ProtoTransaction} from "./abstract-transaction.js"; +import { recoverAddress, validateAddress } from '../address/index.js'; +import { formatNumber, handleNumber } from '../providers/format.js'; +import { ProtoTransaction } from './abstract-transaction.js'; import { Zone } from '../constants'; /** @@ -113,7 +116,8 @@ export class QuaiTransaction extends AbstractTransaction implements Q return this.#to; } set to(value: null | string) { - this.#to = value == null ? null : getAddress(value); + if (value !== null) validateAddress(value); + this.#to = value; } get hash(): null | string { @@ -122,12 +126,13 @@ export class QuaiTransaction extends AbstractTransaction implements Q } return this.unsignedHash; } + get unsignedHash(): string { const destUtxo = isQiAddress(this.to || ''); const originUtxo = isQiAddress(this.from); if (!this.originZone) { - throw new Error('Invalid Shard for from or to address'); + throw new Error('Invalid Zone for from address'); } if (this.isExternal && destUtxo !== originUtxo) { throw new Error('Cross-zone & cross-ledger transactions are not supported'); @@ -397,6 +402,7 @@ export class QuaiTransaction extends AbstractTransaction implements Q result.type = tx.type; } if (tx.to != null) { + validateAddress(tx.to); result.to = tx.to; } if (tx.nonce != null) { @@ -432,6 +438,7 @@ export class QuaiTransaction extends AbstractTransaction implements Q } if (tx.from != null) { + validateAddress(tx.from); assertArgument(result.from.toLowerCase() === (tx.from || '').toLowerCase(), 'from mismatch', 'tx', tx); result.from = tx.from; } @@ -449,7 +456,7 @@ export class QuaiTransaction extends AbstractTransaction implements Q static fromProto(protoTx: ProtoTransaction): QuaiTransaction { // TODO: Fix this because new tx instance requires a 'from' address let signature: null | Signature = null; - let address; + let address: string = ''; if (protoTx.v && protoTx.r && protoTx.s) { // check if protoTx.r is zero if (protoTx.r.reduce((acc, val) => (acc += val), 0) == 0) { @@ -468,22 +475,25 @@ export class QuaiTransaction extends AbstractTransaction implements Q delete protoTxCopy.etx_index; address = recoverAddress(keccak256(encodeProtoTransaction(protoTxCopy)), signature); - } else { - address = ''; } - const tx = new QuaiTransaction(address); if (signature) { tx.signature = signature; } + + if (protoTx.to !== null) { + const toAddr = hexlify(protoTx.to!); + validateAddress(toAddr); + tx.to = toAddr; + } + tx.type = protoTx.type; tx.chainId = toBigInt(protoTx.chain_id); tx.nonce = Number(protoTx.nonce); tx.maxPriorityFeePerGas = toBigInt(protoTx.gas_tip_cap!); tx.maxFeePerGas = toBigInt(protoTx.gas_fee_cap!); tx.gasLimit = toBigInt(protoTx.gas!); - tx.to = protoTx.to !== null ? hexlify(protoTx.to!) : null; tx.value = protoTx.value !== null ? toBigInt(protoTx.value!) : BigInt(0); tx.data = hexlify(protoTx.data!); tx.accessList = protoTx.access_list!.access_tuples.map((tuple) => ({ diff --git a/src/transaction/utxo.ts b/src/transaction/utxo.ts index a44db9b7..8a362632 100644 --- a/src/transaction/utxo.ts +++ b/src/transaction/utxo.ts @@ -1,6 +1,6 @@ -import { getAddress } from "../address/index.js"; -import { getBigInt } from "../utils/index.js"; -import type { BigNumberish } from "../utils/index.js"; +import { validateAddress } from '../address/index.js'; +import { getBigInt } from '../utils/index.js'; +import type { BigNumberish } from '../utils/index.js'; /** * @category Transaction @@ -230,7 +230,8 @@ export class UTXO implements UTXOLike { return this.#address || ''; } set address(value: string) { - this.#address = getAddress(value); + validateAddress(value); + this.#address = value; } get denomination(): null | bigint { @@ -287,10 +288,18 @@ export class UTXO implements UTXOLike { } const result = utxo instanceof UTXO ? utxo : new UTXO(); - if (utxo.txhash != null) { result.txhash = utxo.txhash; } - if (utxo.index != null) { result.index = utxo.index; } - if (utxo.address != null && utxo.address !== '') { result.address = utxo.address; } - if (utxo.denomination != null) { result.denomination = utxo.denomination; } + if (utxo.txhash != null) { + result.txhash = utxo.txhash; + } + if (utxo.index != null) { + result.index = utxo.index; + } + if (utxo.address != null && utxo.address !== '') { + result.address = utxo.address; + } + if (utxo.denomination != null) { + result.denomination = utxo.denomination; + } return result; } diff --git a/src/wallet/base-wallet.ts b/src/wallet/base-wallet.ts index bcf861ca..e6a43a50 100644 --- a/src/wallet/base-wallet.ts +++ b/src/wallet/base-wallet.ts @@ -1,4 +1,4 @@ -import { getAddress, computeAddress, resolveAddress } from '../address/index.js'; +import { getAddress, computeAddress, resolveAddress, validateAddress } from '../address/index.js'; import { hashMessage, TypedDataEncoder } from '../hash/index.js'; import { AbstractSigner } from '../signers/index.js'; import { resolveProperties, assertArgument } from '../utils/index.js'; @@ -90,21 +90,27 @@ export class BaseWallet extends AbstractSigner { }); if (to != null) { + validateAddress(to); tx.to = to; } if (from != null) { + validateAddress(from); tx.from = from; } if (tx.from != null) { - assertArgument(getAddress((tx.from)) === this.#address, - "transaction from address mismatch", "tx.from", tx.from); + assertArgument( + getAddress(tx.from) === this.#address, + 'transaction from address mismatch', + 'tx.from', + tx.from, + ); } const btx = QuaiTransaction.from(tx); - console.log('unsigned', btx.unsignedSerialized) - const digest= keccak256(btx.unsignedSerialized) - btx.signature = this.signingKey.sign(digest) + console.log('unsigned', btx.unsignedSerialized); + const digest = keccak256(btx.unsignedSerialized); + btx.signature = this.signingKey.sign(digest); return btx.serialized; }