diff --git a/specs/interop/predeploys.md b/specs/interop/predeploys.md index 817089830..e182e59ad 100644 --- a/specs/interop/predeploys.md +++ b/specs/interop/predeploys.md @@ -6,6 +6,10 @@ - [Overview](#overview) - [CrossL2Inbox](#crossl2inbox) + - [Access-list](#access-list) + - [type 1: Lookup identity](#type-1-lookup-identity) + - [type 2: Chain-ID extension](#type-2-chain-id-extension) + - [type 3: Checksum](#type-3-checksum) - [Functions](#functions) - [validateMessage](#validatemessage) - [`ExecutingMessage` Event](#executingmessage-event) @@ -86,6 +90,103 @@ To ensure safety of the protocol, the [Message Invariants](./messaging.md#messag [`Identifier`]: ./messaging.md#message-identifier +### Access-list + +Execution of messages is statically pre-declared in transactions, +to ensure the cross-chain validity can be verified outside the single-chain EVM environment constraints. + +After pre-verification of the access-list, the `CrossL2Inbox` can allow messages +to execute when there is a matching pre-verified access-list entry. + +Each executing message is declared with 3 typed access-list entries: +- 1: Lookup identity +- 2: Chain-ID extension +- 3: Checksum + +The type of entry is encoded in the first byte. +Type 0 is reserved, so valid access-list entries are always non-zero. + +Note that the access-list entries may be de-duplicated: +the same message may be executed multiple times. + +The access-list content might not always be a multiple of 3. + +The access-list content is ordered: +- after type 1, a type 2 or 3 entry is expected. +- after type 2, a type 3 entry is expected. + +Note that type 1 and 2 are only enforced out-of-protocol: +these provide a hint, for viable block-building, +to lookup data to determine the validity of the checksum without prior transaction execution. + +Not every access-list entry may be executed: +access-list content must not be used by applications to interpret results of transactions, +the `ExecutingMessage` event describes in detail what is executed. + +To prevent cross-contamination of access-list contents, +the checksum entry commits to the contents of the other entries. +The checksum will be invalid if the wrong entries are interpreted with it. + +The `CrossL2Inbox` only checks the checksum is present in the access-list: +the presence of other needed entries is enforced during pre-validation. + +#### type 1: Lookup identity + +Packed attributes for message lookup. +This type of entry serves as hint of the message identity, +for verification of the `checksum` and is not verified in the protocol state-transition or fork-choice. + +```text +0..1: type byte, always 0x01 +1..4: reserved, zeroed by default +4..12: big-endian uint64 chain ID +12..16: big-endian uint64, block number +16..24: big-endian uint64, timestamp +24..32: big-endian uint32, log index +``` + +Chain IDs larger than `uint64` are supported, with an additional chain-ID-extension entry. +The lower 64 bits of the chain-ID are always encoded in the lookup entry. + +#### type 2: Chain-ID extension + +Large `uint256` Chain IDs are represented with an extension entry, +included right after the lookup identity entry. +Like the lookup identity entry, this entry-type is not verified in the protocol state-transition or fork-choice. + +This extension entry does not have to be included for chain-IDs that fit in `uint64`. + +```text +0..1: type byte, always 0x02 +1..8: zero bytes +8..32: upper 24 bytes of big-endian uint256 chain-ID +``` + +#### type 3: Checksum + +The checksum is a versioned hash, committing to implied attributes. +These implied attributes are compared against the full version +of the executing message by recomputing the checksum from the full version. +The full version is retrieved based on the preceding lookup entry and optional chain-ID extension. + +The checksum is iteratively constructed: +this allows services to work with intermediate implied data. +E.g. the supervisor does not persist the `origin` or `msgHash`, +but does store a `logHash`. + +```text +# Syntax: +# H(bytes): keccak256 hash function +# ++: bytes concatenation +logHash = H(bytes20(idOrigin) ++ msgHash) +# This matches the trailing part of the lookupID +idPacked = bytes12(0) ++ idBlockNumber ++ idTimestamp ++ idLogIndex +idLogHash = H(logHash ++ idPacked) +bareChecksum = H(idLogHash ++ idChainID) +typeByte = 0x03 +checksum = typeByte ++ bareChecksum[1:] +``` + ### Functions #### validateMessage @@ -95,10 +196,10 @@ Emits the `ExecutingMessage` event to signal the transaction has a cross chain m The following fields are required for validating a cross chain message: -| Name | Type | Description | -| ---------- | ---------- | -------------------------------------------------------------------------- | -| `_id` | Identifier | A [`Identifier`] pointing to the initiating message. | -| `_msgHash` | `bytes32` | The keccak256 hash of the message payload matching the initiating message. | +| Name | Type | Description | +|------------|--------------|----------------------------------------------------------------------------| +| `_id` | `Identifier` | A [`Identifier`] pointing to the initiating message. | +| `_msgHash` | `bytes32` | The keccak256 hash of the message payload matching the initiating message. | ```solidity function validateMessage(Identifier calldata _id, bytes32 _msgHash) @@ -130,15 +231,23 @@ hash comparison. A simple implementation of the `validateMessage` function is included below. ```solidity - function validateMessage(Identifier calldata _id, bytes32 _msgHash) external { - // We need to know if this is being called on a depositTx - if (IL1BlockInterop(Predeploys.L1_BLOCK_ATTRIBUTES).isDeposit()) revert NoExecutingDeposits(); +function validateMessage(Identifier calldata _id, bytes32 _msgHash) external { + bytes32 checksum = calculateChecksum(_id, _msgHash); - emit ExecutingMessage(_msgHash, _id); - } + (bool _isSlotWarm,) = _isWarm(checksum); + + if (!_isSlotWarm) revert NonDeclaredExecutingMessage(); + + emit ExecutingMessage(_msgHash, _id); } ``` +`calculateChecksum` implements the checksum computation (including type-byte) as defined +in the [access-list checksum computation](#type-3-checksum) spec. + +`_isWarm` checks that the access-list prepared the `checksum` storage key to be warm. +**No other contract function may warm up this storage without message validation.** + An example of a custom entrypoint utilizing `validateMessage` to consume a known event. Note that in this example, the contract is consuming its own event from another chain, however **any** event emitted from **any** contract is consumable! @@ -253,7 +362,7 @@ In both cases, the source chain's chain id is required for security. Executing m assume the identity of an account because `msg.sender` will never be the identity that initiated the message, it will be the `L2ToL2CrossDomainMessenger` and users will need to callback to get the initiator of the message. -The `_destination` MUST NOT be the chainid of the local chain and a locally defined `nonce` MUST increment on +The `_destination` MUST NOT be the chain-ID of the local chain and a locally defined `nonce` MUST increment on every call to `sendMessage`. Note that `sendMessage` is not `payable`. diff --git a/specs/interop/supervisor.md b/specs/interop/supervisor.md index d374b0fb2..b3e33f850 100644 --- a/specs/interop/supervisor.md +++ b/specs/interop/supervisor.md @@ -22,9 +22,6 @@ - [`SuperRootResponse`](#superrootresponse) - [`SafetyLevel`](#safetylevel) - [Methods](#methods) - - [`supervisor_checkMessage`](#supervisor_checkmessage) - - [`supervisor_checkMessages`](#supervisor_checkmessages) - - [`supervisor_checkMessagesV2`](#supervisor_checkmessagesv2) - [`supervisor_crossDerivedToSource`](#supervisor_crossderivedtosource) - [`supervisor_localUnsafe`](#supervisor_localunsafe) - [`supervisor_crossSafe`](#supervisor_crosssafe) @@ -33,6 +30,11 @@ - [`supervisor_superRootAtTimestamp`](#supervisor_superrootattimestamp) - [`supervisor_syncStatus`](#supervisor_syncstatus) - [`supervisor_allSafeDerivedAt`](#supervisor_allsafederivedat) + - [`supervisor_checkAccessList`](#supervisor_checkaccesslist) + - [Access-list contents](#access-list-contents) + - [Access-list execution context](#access-list-execution-context) + - [Access-list checks](#access-list-checks) + - [`supervisor_checkAccessList` contents](#supervisor_checkaccesslist-contents) @@ -73,7 +75,10 @@ Describes the context for message verification. Specifically, this helps apply message-expiry rules on message checks. Object: -- `timestamp`: `HexUint64` +- `timestamp`: `HexUint64` - expected timestamp during message execution. +- `timeout`: `HexUint64` - optional, requests verification to still hold at `timestamp+timeout` (inclusive). + The message expiry-window may invalidate messages. + Default interpretation is a `0` timeout: what is valid at `timestamp` may not be valid at `timestamp+1`. #### `HexUint64` @@ -144,44 +149,14 @@ Corresponds to a verifier [SafetyLevel](./verifier.md#safety). `STRING`, one of: - `invalid` -- `unsafe` +- `unsafe`: equivalent to safety of the `latest` RPC label. - `cross-unsafe` - `local-safe` -- `safe` +- `safe`: matching cross-safe, named `safe` to match the RPC label. - `finalized` ### Methods -#### `supervisor_checkMessage` - -Checks the safety level of a specific message based on its identifier and message hash. -This RPC is useful for the block builder to determine if a message should be included in a block. - -Parameters: -- `identifier`: `Identifier` -- `payloadHash`: `Hash` -- `executingDescriptor`: `ExecutingDescriptor` - -Returns: `SafetyLevel` - -#### `supervisor_checkMessages` - -Parameters: -- `messages`: ARRAY of `Message` -- `minSafety`: `SafetyLevel` - -#### `supervisor_checkMessagesV2` - -Next version `supervisor_checkMessage`, -additionally verifying the message-expiry, by referencing when the execution happens. - -Parameters: -- `messages`: ARRAY of `Message` -- `minSafety`: `SafetyLevel` -- `executingDescriptor`: `ExecutingDescriptor` - applies as execution-context to all messages - -Returns: RPC error the minSafety is not met by one or more of the messages, with - #### `supervisor_crossDerivedToSource` Parameters: @@ -243,3 +218,65 @@ Parameters: Returns: derived blocks, mapped in a `OBJECT`: - key: `ChainID` - value: `BlockID` + +#### `supervisor_checkAccessList` + +Verifies if an access-list, as defined in [EIP-2930], references only valid messages. +Message execution in the [`CrossL2Inbox`] that is statically declared in the access-list will not revert. + +[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + +##### Access-list contents + +Only the [`CrossL2Inbox`] subset of the access-list in the transaction is required, +storage-access by other addresses is not included. + +Note that an access-list can contain multiple different storage key lists for the `CrossL2Inbox` address. +All storage keys applicable to the `CrossL2Inbox` MUST be joined together (preserving ordering), +missing storage-keys breaks inbox safety. + +**ALL storage-keys in the access-list for the `CrossL2Inbox` MUST be checked.** +If there is any unrecognized or invalid key, the access-list check MUST fail. + +[`CrossL2Inbox`]: ./predeploys.md#crossl2inbox + +##### Access-list execution context + +The provided execution-context is used to determine validity relative to the provided time constraints, +see [timestamp invariants](./derivation.md#invariants). + +Since messages expire, validity is not definitive. +To reserve validity for a longer time range, a non-zero `timeout` value can be used. +See [`ExecutingDescriptor`](#executingdescriptor) documentation. + +As block-builder a `timeout` of `0` should be used. + +As transaction pre-verifier, a `timeout` of `86400` (1 day) should be used. +The transaction should be re-verified or dropped after this time duration, +as it can no longer be safely included in the block due to message-expiry. + +##### Access-list checks + +The access-list check errors are not definite state-transition blockers, the RPC based checks can be extra conservative. +I.e. a message that is uncertain to meet the requested safety level may be denied. +Specifically, no attempt may be made to verify messages that are initiated and executed within the same timestamp, +these are `invalid` by default. +Advanced block-builders may still choose to include these messages by verifying the intra-block constraints. + +##### `supervisor_checkAccessList` contents + +Parameters: +- `inboxEntries`: `ARRAY` of `Hash` - statically declared `CrossL2Inbox` access entries. +- `minSafety`: `SafetyLevel` - minimum required safety, one of: + - `unsafe`: the message exists. + - `cross-unsafe`: the message exists in a cross-unsafe block. + - `local-safe`: the message exists in a local-safe block, not yet cross-verified. + - `safe`: the message exists in a derived block that is cross-verified. + - `finalized`: the message exists in a finalized block. + - Other safety levels are invalid and result in an error. +- `executingDescriptor`: `ExecutingDescriptor` - applies as execution-context to all messages. + +Returns: RPC error if the `minSafety` is not met by one or more of the access entries. + +The access-list entries represent messages, and may be incomplete or malformed. +Malformed access-lists result in an RPC error.