diff --git a/src/ethereum/arrow_glacier/fork.py b/src/ethereum/arrow_glacier/fork.py index 1cce622ecd..8b24973a19 100644 --- a/src/ethereum/arrow_glacier/fork.py +++ b/src/ethereum/arrow_glacier/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,18 +38,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -866,263 +862,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - access_list_cost = 0 - if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: - access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - r, s = tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if isinstance(tx, LegacyTransaction): - v = tx.v - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, - s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), - ) - elif isinstance(tx, AccessListTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_2930(tx) - ) - elif isinstance(tx, FeeMarketTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_1559(tx) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - -def signing_hash_2930(tx: AccessListTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 2930 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x01" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 1559 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x02" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/arrow_glacier/transactions.py b/src/ethereum/arrow_glacier/transactions.py index 9f1180f5c6..10f52ad908 100644 --- a/src/ethereum/arrow_glacier/transactions.py +++ b/src/ethereum/arrow_glacier/transactions.py @@ -10,6 +10,10 @@ from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,260 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: raise TransactionTypeError(tx[0]) else: return tx + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + access_list_cost = 0 + if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) diff --git a/src/ethereum/berlin/fork.py b/src/ethereum/berlin/fork.py index b7764c2fad..a3bdea1a00 100644 --- a/src/ethereum/berlin/fork.py +++ b/src/ethereum/berlin/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,17 +38,14 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -744,227 +740,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - access_list_cost = 0 - if isinstance(tx, AccessListTransaction): - for _address, keys in tx.access_list: - access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - r, s = tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if isinstance(tx, LegacyTransaction): - v = tx.v - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, - s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), - ) - elif isinstance(tx, AccessListTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_2930(tx) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - -def signing_hash_2930(tx: AccessListTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 2930 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x01" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/berlin/transactions.py b/src/ethereum/berlin/transactions.py index 7215321be2..17c941886c 100644 --- a/src/ethereum/berlin/transactions.py +++ b/src/ethereum/berlin/transactions.py @@ -10,6 +10,10 @@ from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -85,3 +89,224 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: return rlp.decode_to(AccessListTransaction, tx[1:]) else: return tx + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + access_list_cost = 0 + if isinstance(tx, AccessListTransaction): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) diff --git a/src/ethereum/byzantium/fork.py b/src/ethereum/byzantium/fork.py index 3e311e695f..fad24adba7 100644 --- a/src/ethereum/byzantium/fork.py +++ b/src/ethereum/byzantium/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -722,180 +720,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) - ) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/byzantium/transactions.py b/src/ethereum/byzantium/transactions.py index d0634e2086..da6aff2c1b 100644 --- a/src/ethereum/byzantium/transactions.py +++ b/src/ethereum/byzantium/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) diff --git a/src/ethereum/cancun/fork.py b/src/ethereum/cancun/fork.py index 454ded353a..ada6431cf3 100644 --- a/src/ethereum/cancun/fork.py +++ b/src/ethereum/cancun/fork.py @@ -18,7 +18,6 @@ from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -40,19 +39,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, BlobTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, ) from .trie import Trie, root, trie_set from .utils.hexadecimal import hex_to_address @@ -63,7 +58,6 @@ calculate_data_fee, calculate_excess_blob_gas, calculate_total_blob_gas, - init_code_cost, ) from .vm.interpreter import MAX_CODE_SIZE, process_message_call @@ -815,271 +809,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) - else: - create_cost = 0 - - access_list_cost = 0 - if isinstance( - tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) - ): - for _address, keys in tx.access_list: - access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - r, s = tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if isinstance(tx, LegacyTransaction): - v = tx.v - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, - s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), - ) - elif isinstance(tx, AccessListTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_2930(tx) - ) - elif isinstance(tx, FeeMarketTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_1559(tx) - ) - elif isinstance(tx, BlobTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_4844(tx) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - -def signing_hash_2930(tx: AccessListTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 2930 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x01" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 1559 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x02" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_4844(tx: BlobTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP-4844 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x03" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - tx.max_fee_per_blob_gas, - tx.blob_versioned_hashes, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/cancun/transactions.py b/src/ethereum/cancun/transactions.py index 13f04b3037..d1f7df04f7 100644 --- a/src/ethereum/cancun/transactions.py +++ b/src/ethereum/cancun/transactions.py @@ -10,6 +10,10 @@ from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address, VersionedHash @@ -143,3 +147,270 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: raise TransactionTypeError(tx[0]) else: return tx + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + from .vm.gas import init_code_cost + + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) + else: + create_cost = 0 + + access_list_cost = 0 + if isinstance( + tx, (AccessListTransaction, FeeMarketTransaction, BlobTransaction) + ): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + elif isinstance(tx, BlobTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_4844(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_4844(tx: BlobTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP-4844 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x03" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + tx.max_fee_per_blob_gas, + tx.blob_versioned_hashes, + ) + ) + ) diff --git a/src/ethereum/constantinople/fork.py b/src/ethereum/constantinople/fork.py index 2cae7ad3fa..8f46f92056 100644 --- a/src/ethereum/constantinople/fork.py +++ b/src/ethereum/constantinople/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -722,180 +720,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) - ) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/constantinople/transactions.py b/src/ethereum/constantinople/transactions.py index d0634e2086..da6aff2c1b 100644 --- a/src/ethereum/constantinople/transactions.py +++ b/src/ethereum/constantinople/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) diff --git a/src/ethereum/dao_fork/fork.py b/src/ethereum/dao_fork/fork.py index 8ebf877de1..8679efabe2 100644 --- a/src/ethereum/dao_fork/fork.py +++ b/src/ethereum/dao_fork/fork.py @@ -17,10 +17,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -41,11 +40,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -721,141 +719,6 @@ def process_transaction( return total_gas_used, output.logs -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if v != 27 and v != 28: - raise InvalidBlock - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in the signature. - - The values that are used to compute the signing hash set the rules for a - transaction. For example, signing over the gas sets a limit for the - amount of money that is allowed to be pulled out of the sender's account. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/dao_fork/transactions.py b/src/ethereum/dao_fork/transactions.py index d0634e2086..a9caf4990f 100644 --- a/src/ethereum/dao_fork/transactions.py +++ b/src/ethereum/dao_fork/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,138 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if v != 27 and v != 28: + raise InvalidSignatureError("bad v") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in the signature. + + The values that are used to compute the signing hash set the rules for a + transaction. For example, signing over the gas sets a limit for the + amount of money that is allowed to be pulled out of the sender's account. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) diff --git a/src/ethereum/exceptions.py b/src/ethereum/exceptions.py index 466778e924..8ee32d7b78 100644 --- a/src/ethereum/exceptions.py +++ b/src/ethereum/exceptions.py @@ -39,3 +39,9 @@ class InvalidSenderError(InvalidTransaction): Thrown when a transaction originates from an account that cannot send transactions. """ + + +class InvalidSignatureError(InvalidTransaction): + """ + Thrown when a transaction has an invalid signature. + """ diff --git a/src/ethereum/frontier/fork.py b/src/ethereum/frontier/fork.py index 69e1cbeb21..ea04ce5719 100644 --- a/src/ethereum/frontier/fork.py +++ b/src/ethereum/frontier/fork.py @@ -18,7 +18,6 @@ from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -38,10 +37,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -702,136 +701,6 @@ def process_transaction( return total_gas_used, output.logs -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - return Uint(TX_BASE_COST + data_cost) - - -def recover_sender(tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if v != 27 and v != 28: - raise InvalidBlock - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s >= SECP256K1N: - raise InvalidBlock - - public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in the signature. - - The values that are used to compute the signing hash set the rules for a - transaction. For example, signing over the gas sets a limit for the - amount of money that is allowed to be pulled out of the sender's account. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/frontier/transactions.py b/src/ethereum/frontier/transactions.py index b4073223dc..e208b4cfb7 100644 --- a/src/ethereum/frontier/transactions.py +++ b/src/ethereum/frontier/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -33,3 +38,133 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + return Uint(TX_BASE_COST + data_cost) + + +def recover_sender(tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if v != 27 and v != 28: + raise InvalidSignatureError("bad v") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s >= SECP256K1N: + raise InvalidSignatureError("bad s") + + public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in the signature. + + The values that are used to compute the signing hash set the rules for a + transaction. For example, signing over the gas sets a limit for the + amount of money that is allowed to be pulled out of the sender's account. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) diff --git a/src/ethereum/gray_glacier/fork.py b/src/ethereum/gray_glacier/fork.py index e92513a2b4..09bbc3d839 100644 --- a/src/ethereum/gray_glacier/fork.py +++ b/src/ethereum/gray_glacier/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,18 +38,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -866,263 +862,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - access_list_cost = 0 - if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: - access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - r, s = tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if isinstance(tx, LegacyTransaction): - v = tx.v - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, - s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), - ) - elif isinstance(tx, AccessListTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_2930(tx) - ) - elif isinstance(tx, FeeMarketTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_1559(tx) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - -def signing_hash_2930(tx: AccessListTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 2930 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x01" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 1559 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x02" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/gray_glacier/transactions.py b/src/ethereum/gray_glacier/transactions.py index 9f1180f5c6..10f52ad908 100644 --- a/src/ethereum/gray_glacier/transactions.py +++ b/src/ethereum/gray_glacier/transactions.py @@ -10,6 +10,10 @@ from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,260 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: raise TransactionTypeError(tx[0]) else: return tx + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + access_list_cost = 0 + if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) diff --git a/src/ethereum/homestead/fork.py b/src/ethereum/homestead/fork.py index bae77156b9..8e957c7424 100644 --- a/src/ethereum/homestead/fork.py +++ b/src/ethereum/homestead/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -38,11 +37,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -703,141 +701,6 @@ def process_transaction( return total_gas_used, output.logs -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if v != 27 and v != 28: - raise InvalidBlock - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in the signature. - - The values that are used to compute the signing hash set the rules for a - transaction. For example, signing over the gas sets a limit for the - amount of money that is allowed to be pulled out of the sender's account. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/homestead/transactions.py b/src/ethereum/homestead/transactions.py index d0634e2086..a9caf4990f 100644 --- a/src/ethereum/homestead/transactions.py +++ b/src/ethereum/homestead/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,138 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if v != 27 and v != 28: + raise InvalidSignatureError("bad v") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in the signature. + + The values that are used to compute the signing hash set the rules for a + transaction. For example, signing over the gas sets a limit for the + amount of money that is allowed to be pulled out of the sender's account. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) diff --git a/src/ethereum/istanbul/fork.py b/src/ethereum/istanbul/fork.py index a647f7193c..b9c6ed815c 100644 --- a/src/ethereum/istanbul/fork.py +++ b/src/ethereum/istanbul/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -723,180 +721,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) - ) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/istanbul/transactions.py b/src/ethereum/istanbul/transactions.py index d518ac9209..b086f8c88b 100644 --- a/src/ethereum/istanbul/transactions.py +++ b/src/ethereum/istanbul/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) diff --git a/src/ethereum/london/fork.py b/src/ethereum/london/fork.py index 2fa09d771a..f476ef5275 100644 --- a/src/ethereum/london/fork.py +++ b/src/ethereum/london/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,18 +38,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -872,263 +868,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - access_list_cost = 0 - if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: - access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - r, s = tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if isinstance(tx, LegacyTransaction): - v = tx.v - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, - s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), - ) - elif isinstance(tx, AccessListTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_2930(tx) - ) - elif isinstance(tx, FeeMarketTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_1559(tx) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - -def signing_hash_2930(tx: AccessListTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 2930 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x01" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 1559 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x02" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/london/transactions.py b/src/ethereum/london/transactions.py index 9f1180f5c6..10f52ad908 100644 --- a/src/ethereum/london/transactions.py +++ b/src/ethereum/london/transactions.py @@ -10,6 +10,10 @@ from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,260 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: raise TransactionTypeError(tx[0]) else: return tx + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + access_list_cost = 0 + if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) diff --git a/src/ethereum/muir_glacier/fork.py b/src/ethereum/muir_glacier/fork.py index ba21a83202..eaf167a7fc 100644 --- a/src/ethereum/muir_glacier/fork.py +++ b/src/ethereum/muir_glacier/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0 +from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -723,180 +721,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) - ) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/muir_glacier/transactions.py b/src/ethereum/muir_glacier/transactions.py index d518ac9209..b086f8c88b 100644 --- a/src/ethereum/muir_glacier/transactions.py +++ b/src/ethereum/muir_glacier/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) diff --git a/src/ethereum/paris/fork.py b/src/ethereum/paris/fork.py index afeb620480..e6ad598a54 100644 --- a/src/ethereum/paris/fork.py +++ b/src/ethereum/paris/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -37,18 +36,15 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -656,263 +652,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > tx.gas: - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - access_list_cost = 0 - if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: - access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - r, s = tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if isinstance(tx, LegacyTransaction): - v = tx.v - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, - s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), - ) - elif isinstance(tx, AccessListTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_2930(tx) - ) - elif isinstance(tx, FeeMarketTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_1559(tx) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - -def signing_hash_2930(tx: AccessListTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 2930 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x01" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 1559 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x02" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/paris/transactions.py b/src/ethereum/paris/transactions.py index 9f1180f5c6..70df38afe2 100644 --- a/src/ethereum/paris/transactions.py +++ b/src/ethereum/paris/transactions.py @@ -10,6 +10,10 @@ from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,260 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: raise TransactionTypeError(tx[0]) else: return tx + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > tx.gas: + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + access_list_cost = 0 + if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) diff --git a/src/ethereum/shanghai/fork.py b/src/ethereum/shanghai/fork.py index c969e3df2d..0133b64c87 100644 --- a/src/ethereum/shanghai/fork.py +++ b/src/ethereum/shanghai/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Tuple, Union -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -38,23 +37,19 @@ state_root, ) from .transactions import ( - TX_ACCESS_LIST_ADDRESS_COST, - TX_ACCESS_LIST_STORAGE_KEY_COST, - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, AccessListTransaction, FeeMarketTransaction, LegacyTransaction, Transaction, + calculate_intrinsic_cost, decode_transaction, encode_transaction, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message -from .vm.gas import init_code_cost -from .vm.interpreter import MAX_CODE_SIZE, process_message_call +from .vm.interpreter import process_message_call BASE_FEE_MAX_CHANGE_DENOMINATOR = Uint(8) ELASTICITY_MULTIPLIER = Uint(2) @@ -680,266 +675,6 @@ def process_transaction( return total_gas_used, output.logs, output.error -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > tx.gas: - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: - return False - - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) - else: - create_cost = 0 - - access_list_cost = 0 - if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): - for _address, keys in tx.access_list: - access_list_cost += TX_ACCESS_LIST_ADDRESS_COST - access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST - - return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - r, s = tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if isinstance(tx, LegacyTransaction): - v = tx.v - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, - s, - v - U256(35) - chain_id_x2, - signing_hash_155(tx, chain_id), - ) - elif isinstance(tx, AccessListTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_2930(tx) - ) - elif isinstance(tx, FeeMarketTransaction): - public_key = secp256k1_recover( - r, s, tx.y_parity, signing_hash_1559(tx) - ) - - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - -def signing_hash_2930(tx: AccessListTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 2930 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x01" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - -def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 1559 signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - b"\x02" - + rlp.encode( - ( - tx.chain_id, - tx.nonce, - tx.max_priority_fee_per_gas, - tx.max_fee_per_gas, - tx.gas, - tx.to, - tx.value, - tx.data, - tx.access_list, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/shanghai/transactions.py b/src/ethereum/shanghai/transactions.py index 9f1180f5c6..dad2bbd956 100644 --- a/src/ethereum/shanghai/transactions.py +++ b/src/ethereum/shanghai/transactions.py @@ -10,6 +10,10 @@ from ethereum_types.frozen import slotted_freezable from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + from .. import rlp from .exceptions import TransactionTypeError from .fork_types import Address @@ -113,3 +117,267 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction: raise TransactionTypeError(tx[0]) else: return tx + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + from .vm.interpreter import MAX_CODE_SIZE + + if calculate_intrinsic_cost(tx) > tx.gas: + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + if tx.to == Bytes0(b"") and len(tx.data) > 2 * MAX_CODE_SIZE: + return False + + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + from .vm.gas import init_code_cost + + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + int(init_code_cost(Uint(len(tx.data)))) + else: + create_cost = 0 + + access_list_cost = 0 + if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)): + for _address, keys in tx.access_list: + access_list_cost += TX_ACCESS_LIST_ADDRESS_COST + access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST + + return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + r, s = tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if isinstance(tx, LegacyTransaction): + v = tx.v + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, + s, + v - U256(35) - chain_id_x2, + signing_hash_155(tx, chain_id), + ) + elif isinstance(tx, AccessListTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_2930(tx) + ) + elif isinstance(tx, FeeMarketTransaction): + public_key = secp256k1_recover( + r, s, tx.y_parity, signing_hash_1559(tx) + ) + + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: LegacyTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: LegacyTransaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) + + +def signing_hash_2930(tx: AccessListTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 2930 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x01" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) + + +def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 1559 signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + b"\x02" + + rlp.encode( + ( + tx.chain_id, + tx.nonce, + tx.max_priority_fee_per_gas, + tx.max_fee_per_gas, + tx.gas, + tx.to, + tx.value, + tx.data, + tx.access_list, + ) + ) + ) diff --git a/src/ethereum/spurious_dragon/fork.py b/src/ethereum/spurious_dragon/fork.py index bb1c9bbbc1..3e358272fc 100644 --- a/src/ethereum/spurious_dragon/fork.py +++ b/src/ethereum/spurious_dragon/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -39,11 +38,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -718,180 +716,6 @@ def process_transaction( return total_gas_used, output.logs -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(chain_id: U64, tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - ID of the executing chain. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - if v == 27 or v == 28: - public_key = secp256k1_recover( - r, s, v - U256(27), signing_hash_pre155(tx) - ) - else: - chain_id_x2 = U256(chain_id) * U256(2) - if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: - raise InvalidBlock - public_key = secp256k1_recover( - r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) - ) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash_pre155(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in a legacy (pre EIP 155) signature. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - -def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: - """ - Compute the hash of a transaction used in a EIP 155 signature. - - Parameters - ---------- - tx : - Transaction of interest. - chain_id : - The id of the current chain. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - chain_id, - Uint(0), - Uint(0), - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/spurious_dragon/transactions.py b/src/ethereum/spurious_dragon/transactions.py index d0634e2086..da6aff2c1b 100644 --- a/src/ethereum/spurious_dragon/transactions.py +++ b/src/ethereum/spurious_dragon/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,177 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(chain_id: U64, tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + ID of the executing chain. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + if v == 27 or v == 28: + public_key = secp256k1_recover( + r, s, v - U256(27), signing_hash_pre155(tx) + ) + else: + chain_id_x2 = U256(chain_id) * U256(2) + if v != U256(35) + chain_id_x2 and v != U256(36) + chain_id_x2: + raise InvalidSignatureError("bad v") + public_key = secp256k1_recover( + r, s, v - U256(35) - chain_id_x2, signing_hash_155(tx, chain_id) + ) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash_pre155(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in a legacy (pre EIP 155) signature. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) + + +def signing_hash_155(tx: Transaction, chain_id: U64) -> Hash32: + """ + Compute the hash of a transaction used in a EIP 155 signature. + + Parameters + ---------- + tx : + Transaction of interest. + chain_id : + The id of the current chain. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + chain_id, + Uint(0), + Uint(0), + ) + ) + ) diff --git a/src/ethereum/tangerine_whistle/fork.py b/src/ethereum/tangerine_whistle/fork.py index bae77156b9..8e957c7424 100644 --- a/src/ethereum/tangerine_whistle/fork.py +++ b/src/ethereum/tangerine_whistle/fork.py @@ -15,10 +15,9 @@ from dataclasses import dataclass from typing import List, Optional, Set, Tuple -from ethereum_types.bytes import Bytes, Bytes0, Bytes32 +from ethereum_types.bytes import Bytes, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover from ethereum.crypto.hash import Hash32, keccak256 from ethereum.ethash import dataset_size, generate_cache, hashimoto_light from ethereum.exceptions import InvalidBlock, InvalidSenderError @@ -38,11 +37,10 @@ state_root, ) from .transactions import ( - TX_BASE_COST, - TX_CREATE_COST, - TX_DATA_COST_PER_NON_ZERO, - TX_DATA_COST_PER_ZERO, Transaction, + calculate_intrinsic_cost, + recover_sender, + validate_transaction, ) from .trie import Trie, root, trie_set from .utils.message import prepare_message @@ -703,141 +701,6 @@ def process_transaction( return total_gas_used, output.logs -def validate_transaction(tx: Transaction) -> bool: - """ - Verifies a transaction. - - The gas in a transaction gets used to pay for the intrinsic cost of - operations, therefore if there is insufficient gas then it would not - be possible to execute a transaction and it will be declared invalid. - - Additionally, the nonce of a transaction must not equal or exceed the - limit defined in `EIP-2681 `_. - In practice, defining the limit as ``2**64-1`` has no impact because - sending ``2**64-1`` transactions is improbable. It's not strictly - impossible though, ``2**64-1`` transactions is the entire capacity of the - Ethereum blockchain at 2022 gas limits for a little over 22 years. - - Parameters - ---------- - tx : - Transaction to validate. - - Returns - ------- - verified : `bool` - True if the transaction can be executed, or False otherwise. - """ - if calculate_intrinsic_cost(tx) > Uint(tx.gas): - return False - if tx.nonce >= U256(U64.MAX_VALUE): - return False - return True - - -def calculate_intrinsic_cost(tx: Transaction) -> Uint: - """ - Calculates the gas that is charged before execution is started. - - The intrinsic cost of the transaction is charged before execution has - begun. Functions/operations in the EVM cost money to execute so this - intrinsic cost is for the operations that need to be paid for as part of - the transaction. Data transfer, for example, is part of this intrinsic - cost. It costs ether to send data over the wire and that ether is - accounted for in the intrinsic cost calculated in this function. This - intrinsic cost must be calculated and paid for before execution in order - for all operations to be implemented. - - Parameters - ---------- - tx : - Transaction to compute the intrinsic cost of. - - Returns - ------- - verified : `ethereum.base_types.Uint` - The intrinsic cost of the transaction. - """ - data_cost = 0 - - for byte in tx.data: - if byte == 0: - data_cost += TX_DATA_COST_PER_ZERO - else: - data_cost += TX_DATA_COST_PER_NON_ZERO - - if tx.to == Bytes0(b""): - create_cost = TX_CREATE_COST - else: - create_cost = 0 - - return Uint(TX_BASE_COST + data_cost + create_cost) - - -def recover_sender(tx: Transaction) -> Address: - """ - Extracts the sender address from a transaction. - - The v, r, and s values are the three parts that make up the signature - of a transaction. In order to recover the sender of a transaction the two - components needed are the signature (``v``, ``r``, and ``s``) and the - signing hash of the transaction. The sender's public key can be obtained - with these two values and therefore the sender address can be retrieved. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - sender : `ethereum.fork_types.Address` - The address of the account that signed the transaction. - """ - v, r, s = tx.v, tx.r, tx.s - if v != 27 and v != 28: - raise InvalidBlock - if U256(0) >= r or r >= SECP256K1N: - raise InvalidBlock - if U256(0) >= s or s > SECP256K1N // U256(2): - raise InvalidBlock - - public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) - return Address(keccak256(public_key)[12:32]) - - -def signing_hash(tx: Transaction) -> Hash32: - """ - Compute the hash of a transaction used in the signature. - - The values that are used to compute the signing hash set the rules for a - transaction. For example, signing over the gas sets a limit for the - amount of money that is allowed to be pulled out of the sender's account. - - Parameters - ---------- - tx : - Transaction of interest. - - Returns - ------- - hash : `ethereum.crypto.hash.Hash32` - Hash of the transaction. - """ - return keccak256( - rlp.encode( - ( - tx.nonce, - tx.gas_price, - tx.gas, - tx.to, - tx.value, - tx.data, - ) - ) - ) - - def compute_header_hash(header: Header) -> Hash32: """ Computes the hash of a block header. diff --git a/src/ethereum/tangerine_whistle/transactions.py b/src/ethereum/tangerine_whistle/transactions.py index d0634e2086..a9caf4990f 100644 --- a/src/ethereum/tangerine_whistle/transactions.py +++ b/src/ethereum/tangerine_whistle/transactions.py @@ -8,8 +8,13 @@ from ethereum_types.bytes import Bytes, Bytes0 from ethereum_types.frozen import slotted_freezable -from ethereum_types.numeric import U256, Uint +from ethereum_types.numeric import U64, U256, Uint +from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover +from ethereum.crypto.hash import Hash32, keccak256 +from ethereum.exceptions import InvalidSignatureError + +from .. import rlp from .fork_types import Address TX_BASE_COST = 21000 @@ -34,3 +39,138 @@ class Transaction: v: U256 r: U256 s: U256 + + +def validate_transaction(tx: Transaction) -> bool: + """ + Verifies a transaction. + + The gas in a transaction gets used to pay for the intrinsic cost of + operations, therefore if there is insufficient gas then it would not + be possible to execute a transaction and it will be declared invalid. + + Additionally, the nonce of a transaction must not equal or exceed the + limit defined in `EIP-2681 `_. + In practice, defining the limit as ``2**64-1`` has no impact because + sending ``2**64-1`` transactions is improbable. It's not strictly + impossible though, ``2**64-1`` transactions is the entire capacity of the + Ethereum blockchain at 2022 gas limits for a little over 22 years. + + Parameters + ---------- + tx : + Transaction to validate. + + Returns + ------- + verified : `bool` + True if the transaction can be executed, or False otherwise. + """ + if calculate_intrinsic_cost(tx) > Uint(tx.gas): + return False + if tx.nonce >= U256(U64.MAX_VALUE): + return False + return True + + +def calculate_intrinsic_cost(tx: Transaction) -> Uint: + """ + Calculates the gas that is charged before execution is started. + + The intrinsic cost of the transaction is charged before execution has + begun. Functions/operations in the EVM cost money to execute so this + intrinsic cost is for the operations that need to be paid for as part of + the transaction. Data transfer, for example, is part of this intrinsic + cost. It costs ether to send data over the wire and that ether is + accounted for in the intrinsic cost calculated in this function. This + intrinsic cost must be calculated and paid for before execution in order + for all operations to be implemented. + + Parameters + ---------- + tx : + Transaction to compute the intrinsic cost of. + + Returns + ------- + verified : `ethereum.base_types.Uint` + The intrinsic cost of the transaction. + """ + data_cost = 0 + + for byte in tx.data: + if byte == 0: + data_cost += TX_DATA_COST_PER_ZERO + else: + data_cost += TX_DATA_COST_PER_NON_ZERO + + if tx.to == Bytes0(b""): + create_cost = TX_CREATE_COST + else: + create_cost = 0 + + return Uint(TX_BASE_COST + data_cost + create_cost) + + +def recover_sender(tx: Transaction) -> Address: + """ + Extracts the sender address from a transaction. + + The v, r, and s values are the three parts that make up the signature + of a transaction. In order to recover the sender of a transaction the two + components needed are the signature (``v``, ``r``, and ``s``) and the + signing hash of the transaction. The sender's public key can be obtained + with these two values and therefore the sender address can be retrieved. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + sender : `ethereum.fork_types.Address` + The address of the account that signed the transaction. + """ + v, r, s = tx.v, tx.r, tx.s + if v != 27 and v != 28: + raise InvalidSignatureError("bad v") + if U256(0) >= r or r >= SECP256K1N: + raise InvalidSignatureError("bad r") + if U256(0) >= s or s > SECP256K1N // U256(2): + raise InvalidSignatureError("bad s") + + public_key = secp256k1_recover(r, s, v - U256(27), signing_hash(tx)) + return Address(keccak256(public_key)[12:32]) + + +def signing_hash(tx: Transaction) -> Hash32: + """ + Compute the hash of a transaction used in the signature. + + The values that are used to compute the signing hash set the rules for a + transaction. For example, signing over the gas sets a limit for the + amount of money that is allowed to be pulled out of the sender's account. + + Parameters + ---------- + tx : + Transaction of interest. + + Returns + ------- + hash : `ethereum.crypto.hash.Hash32` + Hash of the transaction. + """ + return keccak256( + rlp.encode( + ( + tx.nonce, + tx.gas_price, + tx.gas, + tx.to, + tx.value, + tx.data, + ) + ) + ) diff --git a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py index 2b4d4e9ca9..5132577567 100644 --- a/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py +++ b/src/ethereum_spec_tools/evm_tools/loaders/fork_loader.py @@ -80,32 +80,32 @@ def make_receipt(self) -> Any: @property def signing_hash(self) -> Any: """signing_hash function of the fork""" - return self._module("fork").signing_hash + return self._module("transactions").signing_hash @property def signing_hash_pre155(self) -> Any: """signing_hash_pre155 function of the fork""" - return self._module("fork").signing_hash_pre155 + return self._module("transactions").signing_hash_pre155 @property def signing_hash_155(self) -> Any: """signing_hash_155 function of the fork""" - return self._module("fork").signing_hash_155 + return self._module("transactions").signing_hash_155 @property def signing_hash_2930(self) -> Any: """signing_hash_2930 function of the fork""" - return self._module("fork").signing_hash_2930 + return self._module("transactions").signing_hash_2930 @property def signing_hash_1559(self) -> Any: """signing_hash_1559 function of the fork""" - return self._module("fork").signing_hash_1559 + return self._module("transactions").signing_hash_1559 @property def signing_hash_4844(self) -> Any: """signing_hash_4844 function of the fork""" - return self._module("fork").signing_hash_4844 + return self._module("transactions").signing_hash_4844 @property def check_transaction(self) -> Any: diff --git a/src/ethereum_spec_tools/evm_tools/utils.py b/src/ethereum_spec_tools/evm_tools/utils.py index 46f6a5d253..258f9d1966 100644 --- a/src/ethereum_spec_tools/evm_tools/utils.py +++ b/src/ethereum_spec_tools/evm_tools/utils.py @@ -5,7 +5,16 @@ import json import logging import sys -from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + Sequence, + Tuple, + TypeVar, +) import coincurve from ethereum_types.numeric import U64, U256, Uint @@ -82,7 +91,9 @@ def ensure_success(f: Callable, *args: Any) -> Any: raise FatalException(e) -def get_module_name(forks: Any, options: Any, stdin: Any) -> Tuple[str, int]: +def get_module_name( + forks: Sequence[Hardfork], options: Any, stdin: Any +) -> Tuple[str, int]: """ Get the module name and the fork block for the given state fork. """