Skip to content
Draft
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
8 changes: 4 additions & 4 deletions packages/tx/src/1559/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
import * as EIP1559 from '../capabilities/eip1559.js'
import * as EIP2718 from '../capabilities/eip2718.js'
import * as EIP2930 from '../capabilities/eip2930.js'
import * as Generic from '../capabilities/generic.js'
import * as Legacy from '../capabilities/legacy.js'
import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js'
import { TransactionType } from '../types.js'
import { AccessLists } from '../util.js'

Expand Down Expand Up @@ -86,7 +86,7 @@ export class FeeMarket1559Tx implements TransactionInterface<TransactionType.Fee
* varying data types.
*/
public constructor(txData: TxData, opts: TxOptions = {}) {
sharedConstructor(this, { ...txData, type: TransactionType.FeeMarketEIP1559 }, opts)
Generic.sharedConstructor(this, { ...txData, type: TransactionType.FeeMarketEIP1559 }, opts)
const { chainId, accessList, maxFeePerGas, maxPriorityFeePerGas } = txData

if (chainId !== undefined && bytesToBigInt(toBytes(chainId)) !== this.common.chainId()) {
Expand All @@ -111,7 +111,7 @@ export class FeeMarket1559Tx implements TransactionInterface<TransactionType.Fee
this.maxFeePerGas = bytesToBigInt(toBytes(maxFeePerGas))
this.maxPriorityFeePerGas = bytesToBigInt(toBytes(maxPriorityFeePerGas))

valueBoundaryCheck({
Generic.valueBoundaryCheck({
maxFeePerGas: this.maxFeePerGas,
maxPriorityFeePerGas: this.maxPriorityFeePerGas,
})
Expand Down Expand Up @@ -330,7 +330,7 @@ export class FeeMarket1559Tx implements TransactionInterface<TransactionType.Fee
*/
toJSON(): JSONTx {
const accessListJSON = AccessLists.getAccessListJSON(this.accessList)
const baseJSON = getBaseJSON(this)
const baseJSON = Generic.getBaseJSON(this)

return {
...baseJSON,
Expand Down
8 changes: 4 additions & 4 deletions packages/tx/src/2930/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {

import * as EIP2718 from '../capabilities/eip2718.js'
import * as EIP2930 from '../capabilities/eip2930.js'
import * as Generic from '../capabilities/generic.js'
import * as Legacy from '../capabilities/legacy.js'
import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js'
import { TransactionType } from '../types.js'
import { AccessLists } from '../util.js'

Expand Down Expand Up @@ -82,7 +82,7 @@ export class AccessList2930Tx implements TransactionInterface<TransactionType.Ac
* varying data types.
*/
public constructor(txData: TxData, opts: TxOptions = {}) {
sharedConstructor(this, { ...txData, type: TransactionType.AccessListEIP2930 }, opts)
Generic.sharedConstructor(this, { ...txData, type: TransactionType.AccessListEIP2930 }, opts)
const { chainId, accessList, gasPrice } = txData

if (chainId !== undefined && bytesToBigInt(toBytes(chainId)) !== this.common.chainId()) {
Expand All @@ -107,7 +107,7 @@ export class AccessList2930Tx implements TransactionInterface<TransactionType.Ac

this.gasPrice = bytesToBigInt(toBytes(gasPrice))

valueBoundaryCheck({ gasPrice: this.gasPrice })
Generic.valueBoundaryCheck({ gasPrice: this.gasPrice })

if (this.gasPrice * this.gasLimit > MAX_INTEGER) {
const msg = Legacy.errorMsg(this, 'gasLimit * gasPrice cannot exceed MAX_INTEGER')
Expand Down Expand Up @@ -305,7 +305,7 @@ export class AccessList2930Tx implements TransactionInterface<TransactionType.Ac
*/
toJSON(): JSONTx {
const accessListJSON = AccessLists.getAccessListJSON(this.accessList)
const baseJSON = getBaseJSON(this)
const baseJSON = Generic.getBaseJSON(this)

return {
...baseJSON,
Expand Down
8 changes: 4 additions & 4 deletions packages/tx/src/4844/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import {
import * as EIP1559 from '../capabilities/eip1559.js'
import * as EIP2718 from '../capabilities/eip2718.js'
import * as EIP2930 from '../capabilities/eip2930.js'
import * as Generic from '../capabilities/generic.js'
import * as Legacy from '../capabilities/legacy.js'
import { LIMIT_BLOBS_PER_TX } from '../constants.js'
import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js'
import { TransactionType } from '../types.js'
import { AccessLists, validateNotArray } from '../util.js'

Expand Down Expand Up @@ -95,7 +95,7 @@ export class Blob4844Tx implements TransactionInterface<TransactionType.BlobEIP4
* varying data types.
*/
constructor(txData: TxData, opts: TxOptions = {}) {
sharedConstructor(this, { ...txData, type: TransactionType.BlobEIP4844 }, opts)
Generic.sharedConstructor(this, { ...txData, type: TransactionType.BlobEIP4844 }, opts)
const { chainId, accessList, maxFeePerGas, maxPriorityFeePerGas, maxFeePerBlobGas } = txData

if (chainId !== undefined && bytesToBigInt(toBytes(chainId)) !== this.common.chainId()) {
Expand Down Expand Up @@ -124,7 +124,7 @@ export class Blob4844Tx implements TransactionInterface<TransactionType.BlobEIP4
this.maxFeePerGas = bytesToBigInt(toBytes(maxFeePerGas))
this.maxPriorityFeePerGas = bytesToBigInt(toBytes(maxPriorityFeePerGas))

valueBoundaryCheck({
Generic.valueBoundaryCheck({
maxFeePerGas: this.maxFeePerGas,
maxPriorityFeePerGas: this.maxPriorityFeePerGas,
})
Expand Down Expand Up @@ -372,7 +372,7 @@ export class Blob4844Tx implements TransactionInterface<TransactionType.BlobEIP4

toJSON(): JSONTx {
const accessListJSON = AccessLists.getAccessListJSON(this.accessList)
const baseJSON = getBaseJSON(this)
const baseJSON = Generic.getBaseJSON(this)

return {
...baseJSON,
Expand Down
8 changes: 4 additions & 4 deletions packages/tx/src/7702/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
import * as EIP1559 from '../capabilities/eip1559.js'
import * as EIP2718 from '../capabilities/eip2718.js'
import * as EIP7702 from '../capabilities/eip7702.js'
import * as Generic from '../capabilities/generic.js'
import * as Legacy from '../capabilities/legacy.js'
import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js'
import { TransactionType } from '../types.js'
import { AccessLists, AuthorizationLists, validateNotArray } from '../util.js'

Expand Down Expand Up @@ -89,7 +89,7 @@ export class EOACode7702Tx implements TransactionInterface<TransactionType.EOACo
* varying data types.
*/
public constructor(txData: TxData, opts: TxOptions = {}) {
sharedConstructor(this, { ...txData, type: TransactionType.EOACodeEIP7702 }, opts)
Generic.sharedConstructor(this, { ...txData, type: TransactionType.EOACodeEIP7702 }, opts)
const { chainId, accessList, authorizationList, maxFeePerGas, maxPriorityFeePerGas } = txData

if (chainId !== undefined && bytesToBigInt(toBytes(chainId)) !== this.common.chainId()) {
Expand Down Expand Up @@ -123,7 +123,7 @@ export class EOACode7702Tx implements TransactionInterface<TransactionType.EOACo
this.maxFeePerGas = bytesToBigInt(toBytes(maxFeePerGas))
this.maxPriorityFeePerGas = bytesToBigInt(toBytes(maxPriorityFeePerGas))

valueBoundaryCheck({
Generic.valueBoundaryCheck({
maxFeePerGas: this.maxFeePerGas,
maxPriorityFeePerGas: this.maxPriorityFeePerGas,
})
Expand Down Expand Up @@ -354,7 +354,7 @@ export class EOACode7702Tx implements TransactionInterface<TransactionType.EOACo
*/
toJSON(): JSONTx {
const accessListJSON = AccessLists.getAccessListJSON(this.accessList)
const baseJSON = getBaseJSON(this)
const baseJSON = Generic.getBaseJSON(this)

return {
...baseJSON,
Expand Down
104 changes: 104 additions & 0 deletions packages/tx/src/capabilities/ecdsaSignable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
Address,
SECP256K1_ORDER_DIV_2,
bigIntToUnpaddedBytes,
ecrecover,
publicToAddress,
unpadBytes,
} from '@ethereumjs/util'

import { Capability, type ECDSASignableInterface, type TxInterface } from '../types.js'

// TODO: likely add `addSignature`, `getSenderPublicKey`, `getSenderAddress`, `verifySignature`, `sign`
// to this capability as well!

// TxInterface does not support v/r/s by default
export function isSigned(tx: ECDSASignableInterface): boolean {
const { v, r, s } = tx
if (v === undefined || r === undefined || s === undefined) {
return false
} else {
return true
}
}

/**
* EIP-2: All transaction signatures whose s-value is greater than secp256k1n/2are considered invalid.
* Reasoning: https://ethereum.stackexchange.com/a/55728
*/
export function validateHighS(tx: ECDSASignableInterface): void {
const { s } = tx
if (tx.common.gteHardfork('homestead') && s !== undefined && s > SECP256K1_ORDER_DIV_2) {
/*const msg = errorMsg(
tx,
'Invalid Signature: s-values greater than secp256k1n/2 are considered invalid',
)*/
throw new Error('Invalid Signature: s-values greater than secp256k1n/2 are considered invalid')
}
}

export function getSenderPublicKey(
tx: ECDSASignableInterface,
getHashedMessageToSign: (tx: TxInterface) => Uint8Array,
): Uint8Array {
if (tx.cache.senderPubKey !== undefined) {
return tx.cache.senderPubKey
}

const msgHash = getHashedMessageToSign(tx)

const { v, r, s } = tx

validateHighS(tx)

try {
const ecrecoverFunction = tx.common.customCrypto.ecrecover ?? ecrecover
const sender = ecrecoverFunction(
msgHash,
v!,
bigIntToUnpaddedBytes(r!),
bigIntToUnpaddedBytes(s!),
tx.activeCapabilities.includes(Capability.EIP155ReplayProtection)
? tx.common.chainId()
: undefined,
)
if (Object.isFrozen(tx)) {
tx.cache.senderPubKey = sender
}
return sender
} catch (e: any) {
//const msg = errorMsg(tx, 'Invalid Signature') // TODO: generic errorMsg handling?
throw new Error('Invalid Signature')
}
}

/**
* Determines if the signature is valid
*/
export function verifySignature(
tx: ECDSASignableInterface,
getMessageToVerifySignature: (tx: TxInterface) => Uint8Array,
): boolean {
try {
// Main signature verification is done in `getSenderPublicKey()`
const publicKey = getSenderPublicKey(tx, getMessageToVerifySignature)
return unpadBytes(publicKey).length !== 0
} catch (e: any) {
return false
}
}

/**
* Validates the transaction signature and minimum gas requirements.
* @returns {boolean} true if the transaction is valid, false otherwise
*/
// TODO: we can likely remove this method, it is used nowhere (?)
/*export function isValid(tx: LegacyTxInterface): boolean {
const errors = tx.getValidationErrors()

return errors.length === 0
}*/

export function getSenderAddress(tx: ECDSASignableInterface): Address {
return new Address(publicToAddress(getSenderPublicKey(tx)))
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { EIP1559CompatibleTx } from '../types.js'
import type { EIP1559CompatibleTx, FeeGasMarketInterface } from '../../types.js'

export function getUpfrontCost(tx: EIP1559CompatibleTx, baseFee: bigint): bigint {
// NOTE: this file is currently essentially the "FeeMarket" capability

export function getUpfrontCost(tx: FeeGasMarketInterface, baseFee: bigint): bigint {
const prio = tx.maxPriorityFeePerGas
const maxBase = tx.maxFeePerGas - baseFee
const inclusionFeePerGas = prio < maxBase ? prio : maxBase
Expand All @@ -9,7 +11,7 @@ export function getUpfrontCost(tx: EIP1559CompatibleTx, baseFee: bigint): bigint
}

export function getEffectivePriorityFee(
tx: EIP1559CompatibleTx,
tx: FeeGasMarketInterface,
baseFee: bigint | undefined,
): bigint {
if (baseFee === undefined || baseFee > tx.maxFeePerGas) {
Expand Down
28 changes: 28 additions & 0 deletions packages/tx/src/capabilities/gasMarket/legacyGasMarket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Capability } from '../../types.js'

import type { LegacyGasMarketInterface } from '../../types.js'

export function getUpfrontCost(tx: LegacyGasMarketInterface): bigint {
if (!tx.activeCapabilities.includes(Capability.LegacyGasMarket)) {
throw new Error('Tx does not support legacy gas market')
}
return tx.gasLimit * tx.gasPrice + tx.value
}

export function getEffectivePriorityFee(
tx: LegacyGasMarketInterface,
baseFee: bigint | undefined,
): bigint {
if (!tx.activeCapabilities.includes(Capability.LegacyGasMarket)) {
throw new Error('Tx does not support legacy gas market')
}
if (baseFee !== undefined && baseFee > tx.gasPrice) {
throw new Error('Tx cannot pay baseFee')
}

if (baseFee === undefined) {
return tx.gasPrice
}

return tx.gasPrice - baseFee
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ import {
import { paramsTx } from '../params.js'
import { checkMaxInitCodeSize, validateNotArray } from '../util.js'

import type { TransactionInterface, TransactionType, TxData, TxOptions } from '../types.js'
import type {
ContractCreationInterface,
LegacyTxInterface,
ToInterface,
TransactionInterface,
TransactionType,
TxData,
TxInterface,
TxOptions,
} from '../types.js'

export function getCommon(common?: Common): Common {
return common?.copy() ?? new Common({ chain: Mainnet })
Expand Down Expand Up @@ -137,3 +146,7 @@ export function getBaseJSON(tx: TransactionInterface) {
yParity: tx.v === 0n || tx.v === 1n ? bigIntToHex(tx.v) : undefined,
}
}

export function toCreationAddress(tx: ContractCreationInterface | ToInterface): boolean {
return tx.to === undefined || tx.to.bytes.length === 0
}
Loading