Skip to content

Commit

Permalink
Update EIP-6493: Add chain_id and move tx type to start
Browse files Browse the repository at this point in the history
Merged by EIP-Bot.
  • Loading branch information
etan-status authored May 13, 2024
1 parent 4badb4a commit 7b83b10
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 164 deletions.
107 changes: 52 additions & 55 deletions EIPS/eip-6493.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,30 +88,38 @@ All SSZ transactions are represented as a single, normalized SSZ container. The
| `MAX_TRANSACTION_PAYLOAD_FIELDS` | `uint64(2**5)` (= 32) | Maximum number of fields to which `TransactionPayload` can ever grow in the future |
| `MAX_TRANSACTION_SIGNATURE_FIELDS` | `uint64(2**4)` (= 16) | Maximum number of fields to which `TransactionSignature` can ever grow in the future |

| Name | SSZ equivalent | Description |
| - | - | - |
| `ChainId` | `uint64` | [EIP-155](./eip-155.md) chain ID at time of signature |
| `FeePerGas` | `uint256` | Fee per unit of gas, cannot overflow across an entire block |

```python
class AccessTuple(Container):
address: ExecutionAddress
storage_keys: List[Hash32, MAX_ACCESS_LIST_STORAGE_KEYS]

class TransactionPayload(StableContainer[MAX_TRANSACTION_PAYLOAD_FIELDS]):
# EIP-2718
type_: TransactionType

# EIP-155
chain_id: Optional[ChainId]

nonce: uint64
max_fee_per_gas: uint256
max_fee_per_gas: FeePerGas
gas: uint64
to: Optional[ExecutionAddress]
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]

# EIP-2718
type_: Optional[TransactionType]

# EIP-2930
access_list: Optional[List[AccessTuple, MAX_ACCESS_LIST_SIZE]]

# EIP-1559
max_priority_fee_per_gas: Optional[uint256]
max_priority_fee_per_gas: Optional[FeePerGas]

# EIP-4844
max_fee_per_blob_gas: Optional[uint256]
max_fee_per_blob_gas: Optional[FeePerGas]
blob_versioned_hashes: Optional[List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK]]

class TransactionSignature(StableContainer[MAX_TRANSACTION_SIGNATURE_FIELDS]):
Expand All @@ -127,8 +135,9 @@ Valid transaction types can be defined using [EIP-7495](./eip-7495.md) `Variant`

```python
class ReplayableTransactionPayload(Variant[TransactionPayload]):
type_: TransactionType
nonce: uint64
max_fee_per_gas: uint256
max_fee_per_gas: FeePerGas
gas: uint64
to: Optional[ExecutionAddress]
value: uint256
Expand All @@ -139,90 +148,96 @@ class ReplayableSignedTransaction(SignedTransaction):
signature: TransactionSignature

class LegacyTransactionPayload(Variant[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fee_per_gas: uint256
max_fee_per_gas: FeePerGas
gas: uint64
to: Optional[ExecutionAddress]
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
type_: TransactionType

class LegacySignedTransaction(SignedTransaction):
payload: LegacyTransactionPayload
signature: TransactionSignature

class Eip2930TransactionPayload(Variant[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fee_per_gas: uint256
max_fee_per_gas: FeePerGas
gas: uint64
to: Optional[ExecutionAddress]
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
type_: TransactionType
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]

class Eip2930SignedTransaction(SignedTransaction):
payload: Eip2930TransactionPayload
signature: TransactionSignature

class Eip1559TransactionPayload(Variant[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fee_per_gas: uint256
max_fee_per_gas: FeePerGas
gas: uint64
to: Optional[ExecutionAddress]
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
type_: TransactionType
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_priority_fee_per_gas: uint256
max_priority_fee_per_gas: FeePerGas

class Eip1559SignedTransaction(SignedTransaction):
payload: Eip1559TransactionPayload
signature: TransactionSignature

class Eip4844TransactionPayload(Variant[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fee_per_gas: uint256
max_fee_per_gas: FeePerGas
gas: uint64
to: ExecutionAddress
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
type_: TransactionType
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_priority_fee_per_gas: uint256
max_fee_per_blob_gas: uint256
max_priority_fee_per_gas: FeePerGas
max_fee_per_blob_gas: FeePerGas
blob_versioned_hashes: List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK]

class Eip4844SignedTransaction(SignedTransaction):
payload: Eip4844TransactionPayload
signature: TransactionSignature

class BasicTransactionPayload(Variant[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fee_per_gas: uint256
max_fee_per_gas: FeePerGas
gas: uint64
to: Optional[ExecutionAddress]
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
type_: TransactionType
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_priority_fee_per_gas: uint256
max_priority_fee_per_gas: FeePerGas

class BasicSignedTransaction(SignedTransaction):
payload: BasicTransactionPayload
signature: TransactionSignature

class BlobTransactionPayload(Variant[TransactionPayload]):
type_: TransactionType
chain_id: ChainId
nonce: uint64
max_fee_per_gas: uint256
max_fee_per_gas: FeePerGas
gas: uint64
to: ExecutionAddress
value: uint256
input_: ByteList[MAX_CALLDATA_SIZE]
type_: TransactionType
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
max_priority_fee_per_gas: uint256
max_fee_per_blob_gas: uint256
max_priority_fee_per_gas: FeePerGas
max_fee_per_blob_gas: FeePerGas
blob_versioned_hashes: List[VersionedHash, MAX_BLOB_COMMITMENTS_PER_BLOCK]

class BlobSignedTransaction(SignedTransaction):
Expand All @@ -246,10 +261,9 @@ class AnySignedTransaction(OneOf[SignedTransaction]):
if value.payload.type_ == TRANSACTION_TYPE_EIP2930:
return Eip2930SignedTransaction

if value.payload.type_ == TRANSACTION_TYPE_LEGACY:
if value.payload.chain_id is not None:
return LegacySignedTransaction

assert value.payload.type_ is None
return ReplayableSignedTransaction
```

Expand All @@ -265,37 +279,23 @@ Such changes [do not affect](./eip-7495.md) how existing transactions serialize

### Transaction signature scheme

When an SSZ transaction is signed, additional information is mixed into the `sig_hash` to uniquely identify the underlying SSZ scheme as well as the operating network. This prevents hash collisions when different networks extend their corresponding `SignedTransaction` SSZ definition in incompatible ways.
When an SSZ transaction is signed, additional information is mixed into the `sig_hash` to uniquely identify the underlying specifications. If other networks define additional transaction types, they MUST use a different `DomainType` for signing SSZ data.

| Name | SSZ equivalent | Description |
| Name | Value | Description |
| - | - | - |
| `ChainId` | `uint256` | [EIP-155](./eip-155.md) chain ID at time of signature |

The following helper function computes the [`Domain`](https://github.com/ethereum/consensus-specs/blob/ef434e87165e9a4c82a99f54ffd4974ae113f732/specs/phase0/beacon-chain.md#custom-types) for signing an SSZ transaction for a particular network.

```python
class TransactionDomainData(Container):
type_: TransactionType
chain_id: ChainId

def compute_ssz_transaction_domain(chain_id: ChainId) -> Domain:
return Domain(TransactionDomainData(
type_=TRANSACTION_TYPE_SSZ,
chain_id=chain_id,
).hash_tree_root())
```
| `DOMAIN_TRANSACTION_SSZ` | `DomainType('0x04000080)` | [`DomainType`](https://github.com/ethereum/consensus-specs/blob/ef434e87165e9a4c82a99f54ffd4974ae113f732/specs/phase0/beacon-chain.md#custom-types) for signing SSZ transactions compatible with this EIP |

The hash to sign `sig_hash` and the unique transaction identifier `tx_hash` are computed using [`hash_tree_root`](https://github.com/ethereum/consensus-specs/blob/ef434e87165e9a4c82a99f54ffd4974ae113f732/ssz/simple-serialize.md#merkleization).

```python
class SigningData(Container):
class ExecutionSigningData(Container):
object_root: Root
domain: Domain
domain_type: DomainType

def compute_ssz_sig_hash(payload: TransactionPayload, chain_id: ChainId) -> Hash32:
return Hash32(SigningData(
def compute_ssz_sig_hash(payload: TransactionPayload) -> Hash32:
return Hash32(ExecutionSigningData(
object_root=payload.hash_tree_root(),
domain=compute_ssz_transaction_domain(chain_id),
domain=DOMAIN_TRANSACTION_SSZ,
).hash_tree_root())

def compute_ssz_tx_hash(tx: SignedTransaction) -> Hash32:
Expand Down Expand Up @@ -335,12 +335,11 @@ def ecdsa_recover_from_address(signature: ByteVector[ECDSA_SIGNATURE_SIZE],
uncompressed = public_key.serialize(compressed=False)
return ExecutionAddress(keccak(uncompressed[1:])[12:])

def validate_transaction(tx: AnySignedTransaction,
chain_id: ChainId):
def validate_transaction(tx: AnySignedTransaction):
ecdsa_validate_signature(tx.signature.ecdsa_signature)
assert tx.signature.from_ == ecdsa_recover_from_address(
tx.signature.ecdsa_signature,
compute_sig_hash(tx, chain_id),
compute_sig_hash(tx),
)
```

Expand Down Expand Up @@ -487,12 +486,10 @@ Computing the address of a newly created contract requires RLP encoding and kecc

Even though the `contract_address` is statically determinable from the corresponding `SignedTransaction` alone, including it in the `Receipt` allows the mechanism by which it is computed to change in the future.

### Why the `TransactionDomainData`?
### Why the `ExecutionSigningData`?

If other SSZ objects are being signed in the future, e.g., messages, it must be ensured that their hashes do not collide with transaction `sig_hash`. Mixing in a constant that indicates that `sig_hash` pertains to an SSZ transaction prevents such hash collisions.

Mixing the chain ID into the `TransactionDomainData` further allows dropping the chain ID in the payload of each transaction, reducing their size.

### What about EIP-2718 transaction types?

All SSZ transactions (including future ones) share the single [EIP-2718](./eip-2718.md) transaction type `TRANSACTION_TYPE_SSZ`. Future features can introduce new optional fields as well as new allowed combination of optional fields, as determined by `select_variant` in `AnySignedTransaction`.
Expand Down
29 changes: 12 additions & 17 deletions assets/eip-6493/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
from rlp_types import *
from ssz_types import *

def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes,
chain_id: ChainId) -> AnySignedTransaction:
def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes) -> AnySignedTransaction:
type_ = pre_bytes[0]

if type_ == 0x03: # EIP-4844
pre = decode(pre_bytes[1:], Eip4844SignedRlpTransaction)
assert pre.chain_id == chain_id

assert pre.signature_y_parity in (0, 1)
ecdsa_signature = ecdsa_pack_signature(
pre.signature_y_parity != 0,
Expand All @@ -21,13 +18,14 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes,

return Eip4844SignedTransaction(
payload=Eip4844TransactionPayload(
type_=TRANSACTION_TYPE_EIP4844,
chain_id=pre.chain_id,
nonce=pre.nonce,
max_fee_per_gas=pre.max_fee_per_gas,
gas=pre.gas_limit,
to=ExecutionAddress(pre.destination),
value=pre.amount,
input_=pre.data,
type_=TRANSACTION_TYPE_EIP4844,
access_list=[AccessTuple(
address=access_tuple[0],
storage_keys=access_tuple[1]
Expand All @@ -44,8 +42,6 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes,

if type_ == 0x02: # EIP-1559
pre = decode(pre_bytes[1:], Eip1559SignedRlpTransaction)
assert pre.chain_id == chain_id

assert pre.signature_y_parity in (0, 1)
ecdsa_signature = ecdsa_pack_signature(
pre.signature_y_parity != 0,
Expand All @@ -56,13 +52,14 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes,

return Eip1559SignedTransaction(
payload=Eip1559TransactionPayload(
type_=TRANSACTION_TYPE_EIP1559,
chain_id=pre.chain_id,
nonce=pre.nonce,
max_fee_per_gas=pre.max_fee_per_gas,
gas=pre.gas_limit,
to=ExecutionAddress(pre.destination) if len(pre.destination) > 0 else None,
value=pre.amount,
input_=pre.data,
type_=TRANSACTION_TYPE_EIP1559,
access_list=[AccessTuple(
address=access_tuple[0],
storage_keys=access_tuple[1]
Expand All @@ -77,8 +74,6 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes,

if type_ == 0x01: # EIP-2930
pre = decode(pre_bytes[1:], Eip2930SignedRlpTransaction)
assert pre.chainId == chain_id

assert pre.signatureYParity in (0, 1)
ecdsa_signature = ecdsa_pack_signature(
pre.signatureYParity != 0,
Expand All @@ -89,13 +84,14 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes,

return Eip2930SignedTransaction(
payload=Eip2930TransactionPayload(
type_=TRANSACTION_TYPE_EIP2930,
chain_id=pre.chainId,
nonce=pre.nonce,
max_fee_per_gas=pre.gasPrice,
gas=pre.gasLimit,
to=ExecutionAddress(pre.to) if len(pre.to) > 0 else None,
value=pre.value,
input_=pre.data,
type_=TRANSACTION_TYPE_EIP2930,
access_list=[AccessTuple(
address=access_tuple[0],
storage_keys=access_tuple[1]
Expand All @@ -109,26 +105,25 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes,

if 0xc0 <= type_ <= 0xfe: # Legacy
pre = decode(pre_bytes, LegacySignedRlpTransaction)

if pre.v not in (27, 28): # EIP-155
assert pre.v in (2 * chain_id + 35, 2 * chain_id + 36)
ecdsa_signature = ecdsa_pack_signature(
(pre.v & 0x1) == 0,
pre.r,
pre.s,
)
from_ = ecdsa_recover_from_address(ecdsa_signature, compute_legacy_sig_hash(pre))

if (pre.v not in (27, 28)):
if (pre.v not in (27, 28)): # EIP-155
chain_id = ((pre.v - 35) >> 1)
return LegacySignedTransaction(
payload=LegacyTransactionPayload(
type_=TRANSACTION_TYPE_LEGACY,
chain_id=chain_id,
nonce=pre.nonce,
max_fee_per_gas=pre.gasprice,
gas=pre.startgas,
to=ExecutionAddress(pre.to) if len(pre.to) > 0 else None,
value=pre.value,
input_=pre.data,
type_=TRANSACTION_TYPE_LEGACY,
),
signature=TransactionSignature(
from_=from_,
Expand All @@ -138,6 +133,7 @@ def upgrade_rlp_transaction_to_ssz(pre_bytes: bytes,

return ReplayableSignedTransaction(
payload=ReplayableTransactionPayload(
type_=TRANSACTION_TYPE_LEGACY,
nonce=pre.nonce,
max_fee_per_gas=pre.gasprice,
gas=pre.startgas,
Expand Down Expand Up @@ -213,7 +209,6 @@ def upgrade_rlp_receipt_to_ssz(pre_bytes: bytes,
)

def upgrade_rlp_receipts_to_ssz(pre_bytes_list: PyList[bytes],
chain_id: ChainId,
transactions: PyList[AnySignedTransaction]) -> PyList[AnyReceipt]:
receipts = []
cumulative_gas_used = 0
Expand Down
Loading

0 comments on commit 7b83b10

Please sign in to comment.