|
| 1 | +--- |
| 2 | +title: Verifying log entries |
| 3 | +description: >- |
| 4 | + Learn how to verify log entries on a different chain using CrossL2Inbox. |
| 5 | +lang: en-US |
| 6 | +content_type: tutorial |
| 7 | +topic: verify-message |
| 8 | +personas: |
| 9 | + - protocol-developer |
| 10 | + - chain-operator |
| 11 | + - app-developer |
| 12 | +categories: |
| 13 | + - protocol |
| 14 | + - interoperability |
| 15 | + - cross-chain-messaging |
| 16 | + - message-relaying |
| 17 | + - superchain |
| 18 | +is_imported_content: 'false' |
| 19 | +--- |
| 20 | + |
| 21 | +import { Callout, Steps, Tabs } from 'nextra/components' |
| 22 | + |
| 23 | +# Verifying log entries |
| 24 | + |
| 25 | +This guide shows how to verify log entries from a specific chain on a different chain in an interop cluster. |
| 26 | +This lets developers use interoperability with applications on a different chain, even if those applications were not written with interoperability in mind. |
| 27 | + |
| 28 | +To demonstrate this functionality, this guide uses an [attestation](https://attest.org/) from one chain on another. |
| 29 | + |
| 30 | +<Callout type="warning"> |
| 31 | + Because of [message expiration](/interop/message-expiration), this solution can only validate log messages emitted in the last seven days (on the Superchain, other interop clusters may vary). |
| 32 | +</Callout> |
| 33 | + |
| 34 | +## Overview |
| 35 | + |
| 36 | +<details> |
| 37 | + <summary>About this tutorial</summary> |
| 38 | + |
| 39 | + **What you'll learn** |
| 40 | + |
| 41 | + * How to verify log messages from one blockchain on another. |
| 42 | + * How to use the EAS SDK to attest for facts and add schemas from offchain code. |
| 43 | + |
| 44 | + **Technical knowledge** |
| 45 | + |
| 46 | + * Intermediate JavaScript knowledge |
| 47 | + * Understanding of smart contract development |
| 48 | + * Familiarity with blockchain concepts |
| 49 | + |
| 50 | + **Development environment** |
| 51 | + |
| 52 | + * Unix-like operating system (Linux, macOS, or WSL for Windows) |
| 53 | + * Node.js version 16 or higher |
| 54 | + * Git for version control |
| 55 | + |
| 56 | + **Required tools** |
| 57 | + |
| 58 | + The tutorial uses these primary tools: |
| 59 | + |
| 60 | + * Node: For running TypeScript code from the command line |
| 61 | + * Viem: For blockchain interaction |
| 62 | + * Foundry: For smart contract development |
| 63 | +</details> |
| 64 | + |
| 65 | +### What you'll build |
| 66 | + |
| 67 | +* A JavaScript program to create an attestation and then verify it on a different chain. |
| 68 | +* A Solidity contract that can verify attestations onchain. |
| 69 | + |
| 70 | +## Directions |
| 71 | + |
| 72 | +<Steps> |
| 73 | + ### Preparation |
| 74 | + |
| 75 | + 1. If you are using Supersim, set up the [SuperchainERC20 starter kit](/app-developers/starter-kit#setup). |
| 76 | + The `pnpm dev` step also starts Supersim. |
| 77 | + |
| 78 | + 2. Store the configuration in environment variables. |
| 79 | + |
| 80 | + <Tabs items={['Supersim', 'Devnets']}> |
| 81 | + <Tabs.Tab> |
| 82 | + ```sh |
| 83 | + export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 |
| 84 | + URL_CHAIN_A=http://127.0.0.1:9545 |
| 85 | + URL_CHAIN_B=http://127.0.0.1:9546 |
| 86 | + export CHAIN_B_ID=`cast chain-id --rpc-url $URL_CHAIN_B` |
| 87 | + ``` |
| 88 | + </Tabs.Tab> |
| 89 | + |
| 90 | + <Tabs.Tab> |
| 91 | + 1. Set `PRIVATE_KEY` to the private key for an address that has ETH on the two devnets. |
| 92 | + |
| 93 | + 2. Run these commands to specify the rest of the environment variables. |
| 94 | + |
| 95 | + ```sh |
| 96 | + export PRIVATE_KEY |
| 97 | + URL_CHAIN_A=https://interop-alpha-0.optimism.io |
| 98 | + URL_CHAIN_B=https://interop-alpha-1.optimism.io |
| 99 | + export CHAIN_B_ID=`cast chain-id --rpc-url $URL_CHAIN_B` |
| 100 | + ``` |
| 101 | + </Tabs.Tab> |
| 102 | + </Tabs> |
| 103 | + |
| 104 | + ### Create an attestation |
| 105 | + |
| 106 | + There are already attestations in the production chains. |
| 107 | + However, that may not be the case in the devnets, and it is definitely not the case in the Supersim instance you just started. |
| 108 | + |
| 109 | + To begin, send an attestation to one of the chains. |
| 110 | + |
| 111 | + 1. Create a new JavaScript project. |
| 112 | + |
| 113 | + ```sh |
| 114 | + mkdir -p verify-messages/offchain |
| 115 | + cd verify-messages/offchain |
| 116 | + npm init -y |
| 117 | + npm install @eth-optimism/viem @ethereum-attestation-service/eas-sdk ethers viem |
| 118 | + ``` |
| 119 | + |
| 120 | + 2. Create a file, `attest.mjs`. |
| 121 | + |
| 122 | + ```js file=<rootDir>/public/tutorials/attest.mjs hash=d626bf0d9a623a87f6c186df9ebe4be7 |
| 123 | + ``` |
| 124 | + |
| 125 | + <details> |
| 126 | + <summary>Explanation</summary> |
| 127 | + |
| 128 | + ```js file=<rootDir>/public/tutorials/attest.mjs#L23-L39 hash=e60b8cd94e438dee61d76a86b71ba0fd |
| 129 | + ``` |
| 130 | + |
| 131 | + The [EAS SDK](https://docs.attest.org/docs/developer-tools/eas-sdk) used for attestations uses [Ethers](https://docs.ethers.org/v6/) rather than [Viem](https://viem.sh/). |
| 132 | + This function lets us use [a Viem wallet](https://viem.sh/docs/clients/wallet) as [an Ethers signer](https://docs.ethers.org/v6/api/providers/#Signer). |
| 133 | + |
| 134 | + ```js file=<rootDir>/public/tutorials/attest.mjs#L60-L71 hash=b51f57bde9ff4f29ee476a0ccff6826c |
| 135 | + ``` |
| 136 | + |
| 137 | + Register the [EAS Schema](https://docs.attest.org/docs/tutorials/create-a-schema) if necessary. |
| 138 | + This schema ties Ethereum addresses to names. |
| 139 | + |
| 140 | + ```js file=<rootDir>/public/tutorials/attest.mjs#L73-L86 hash=e358a615a7ab6d28c6f3bba58b523f66 |
| 141 | + ``` |
| 142 | + |
| 143 | + [Attest](https://docs.attest.org/docs/tutorials/make-an-attestation) that the name for Ethereum address `0x0123456789012345678901234567890123456789` is `Bill Hamm`. |
| 144 | + |
| 145 | + ```js file=<rootDir>/public/tutorials/attest.mjs#L88-L90 hash=d31631fd94b69699177a1cccf4e4ec1a |
| 146 | + ``` |
| 147 | + |
| 148 | + This is the recommended way to execute the attestation transaction, which provides you with the attestation's UID. |
| 149 | + However, for our purpose we need the transaction receipt, which you do not get. |
| 150 | + Either search the transaction receipt for the log entry, or use the EAS SDK to create the transaction but then send it manually. |
| 151 | + The code here uses the second solution. |
| 152 | + |
| 153 | + ```js file=<rootDir>/public/tutorials/attest.mjs#L92-L94 hash=1452cf7f6e9e00d7b3cd3560ca355bd8 |
| 154 | + ``` |
| 155 | + |
| 156 | + Send the attestation transaction created by the EAS SDK manually (using viem's [`sendRawTransaction`](https://viem.sh/docs/actions/wallet/sendRawTransaction)) to obtain the transaction hash. |
| 157 | + </details> |
| 158 | + |
| 159 | + 3. Run the attestation program. |
| 160 | + |
| 161 | + ```sh |
| 162 | + node attest.mjs |
| 163 | + ``` |
| 164 | + |
| 165 | + 4. The program output includes an `export ATTEST_TXN` line. |
| 166 | + Run it to inform our verification code where to find the attestation. |
| 167 | + |
| 168 | + ### Offchain verification |
| 169 | + |
| 170 | + The next step is to call `CrossL2Inbox.verifyMessage`. |
| 171 | + For testing purposes, start by calling it offchain. |
| 172 | + |
| 173 | + 1. Create a file, `verify-attestation.mjs`. |
| 174 | + |
| 175 | + ```js file=<rootDir>/public/tutorials/verify-attestation.mjs hash=405d9493c3bf15f85aa9c24a936cf7a4 |
| 176 | + ``` |
| 177 | + |
| 178 | + <details> |
| 179 | + <summary>Explanation</summary> |
| 180 | + |
| 181 | + ```js file=<rootDir>/public/tutorials/verify-attestation.mjs#L36-L45 hash=2e0e75ab322c05fb2eb8177978f47c55 |
| 182 | + ``` |
| 183 | + |
| 184 | + To create the [executing message](/interop/message-passing#executing-message), we need several fields that appear in the log entry we are verifying. |
| 185 | + This is how we obtain the relevant log entry. |
| 186 | + |
| 187 | + The first topic in a log entry emitted by Solidity is the event type. |
| 188 | + [This event type](https://www.4byte.directory/event-signatures/?bytes_signature=0x8bf46bf4cfd674fa735a3d63ec1c9ad4153f033c290341f3a588b75685141b35) is emitted when an attestation is created. |
| 189 | + Of course, we only care about attestations created in the official attestations contract. |
| 190 | + |
| 191 | + ```js file=<rootDir>/public/tutorials/verify-attestation.mjs#L47 hash=2d9c5e52695aa90f1f0f9a5769f98814 |
| 192 | + ``` |
| 193 | + |
| 194 | + Part of testing a system is ensuring that it does not accept invalid input. |
| 195 | + You can uncomment this line to see that the verification fails with an incorrect log entry. |
| 196 | + |
| 197 | + ```js file=<rootDir>/public/tutorials/verify-attestation.mjs#L49-L51 hash=fbfb20fa0614cb9fb52ec678df6fffa9 |
| 198 | + ``` |
| 199 | + |
| 200 | + [Build the executing message](https://github.com/ethereum-optimism/ecosystem/blob/main/packages/viem/docs/actions/interop/functions/buildExecutingMessage.md). |
| 201 | + |
| 202 | + ```js file=<rootDir>/public/tutorials/verify-attestation.mjs#L61-L74 hash=5c410cd233b0e4e68f33170de7da3b7f |
| 203 | + ``` |
| 204 | + |
| 205 | + Call [`CrossL2Inbox.validateMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L68-L82). |
| 206 | + For some invalid inputs, this function reverts. |
| 207 | + If it reverts, it means that the log entry requested is not valid. |
| 208 | + |
| 209 | + ```js file=<rootDir>/public/tutorials/verify-attestation.mjs#L76-L84 hash=2476aec8e2b27708ce812797e9e5dfe7 |
| 210 | + ``` |
| 211 | + |
| 212 | + Wait for the transaction receipt for the executing message. |
| 213 | + The timeout is necessary because if the executing message is valid, but the log entry has not been seen yet, the sequencer does not put the transaction with the message into a block. |
| 214 | + The timeout distinguishes between a valid executing message pending inclusion and a non-existent log entry. |
| 215 | + |
| 216 | + ```js file=<rootDir>/public/tutorials/verify-attestation.mjs#L86-L91 hash=e870ee89daa5f2a31b0adc6a97f670a9 |
| 217 | + ``` |
| 218 | + |
| 219 | + Look for the log entry from `CrossL2Inbox`. |
| 220 | + To ensure this is the correct log entry, we look at the [message type](https://www.4byte.directory/event-signatures/?bytes_signature=0x5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba7) and check that the ID is the correct one. |
| 221 | + </details> |
| 222 | + |
| 223 | + 2. Execute this program. |
| 224 | + |
| 225 | + ```sh |
| 226 | + node verify-attestation.mjs |
| 227 | + ``` |
| 228 | + |
| 229 | + ### Onchain verification |
| 230 | + |
| 231 | + The code in the above example is not very useful because it is running offchain. |
| 232 | + Offchain code can talk to any chain it wants, so it can verify the attestation directly on the chain where it originated. |
| 233 | + The real value of interoperability is when on-chain code on one chain verifies information from another chain. |
| 234 | + Follow the steps below to build this functionality. |
| 235 | + |
| 236 | + Calculate the transaction access list offchain using `interop.buildExecutingMessage`, then inspect `relayParams` to determine the exact validation parameters. |
| 237 | + |
| 238 | + | Value | Location | Sample value | |
| 239 | + | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------- | |
| 240 | + | [Event signature](https://www.4byte.directory/event-signatures/?bytes_signature=8bf46bf4cfd674fa735a3d63ec1c9ad4153f033c290341f3a588b75685141b35) | `relayMessageParams.payload.slice(2,66)` | 8bf46bf4cfd674fa735a3d63ec1c9ad4153f033c290341f3a588b75685141b35 | |
| 241 | + | Recipient address | `relayMessageParams.payload.slice(90,130)` | 0123456789012345678901234567890123456789 | |
| 242 | + | Attester address | `relayMessageParams.payload.slice(154,194)` | f39fd6e51aad88f6f4ce6ab8827279cfffb92266 | |
| 243 | + | Schema | `relayMessageParams.payload.slice(194,258)` | 234dee4d3e6a625b4121e2042d6267058755e53a2ecc55555da51a1e6f06cc58 | |
| 244 | + | Attestation ID | `relayMessageParams.payload.slice(194,258)` | c88cbbc15b9fb4aa12f70c9c97e6c1dd733a29db2816142809504718d615ff9f | |
| 245 | + | Log entry origin | `relayMessageParams.id.origin` | 0x4200000000000000000000000000000000000021 | |
| 246 | + | Log entry location | `relayMessageParams.id.logIndex` | 0n | |
| 247 | + | Block number | `relayMessageParams.id.blockNumber` | 6746n | |
| 248 | + | Timestamp | `relayMessageParams.id.timestamp` | 1747518156n | |
| 249 | + | Chain Id | `relayMessageParams.id.chainId` | 901n | |
| 250 | + |
| 251 | + The attestation ID itself [is also a hash](https://github.com/ethereum-attestation-service/eas-contracts/blob/master/contracts/EAS.sol#L692-L711), but luckily most of the fields are either unused (such as `revocationTime`) or are already known to us (such as the schema). |
| 252 | + The sole exception is the data, which in our case includes the name being attested to. |
| 253 | + We can provide this data to the Solidity code as a parameter. |
| 254 | + |
| 255 | + 1. Create a new Foundry project. |
| 256 | + |
| 257 | + ```sh |
| 258 | + mkdir ../onchain |
| 259 | + cd ../onchain |
| 260 | + forge init |
| 261 | + ``` |
| 262 | + |
| 263 | + 2. Add the [EAS contracts](https://www.npmjs.com/package/@ethereum-attestation-service/eas-contracts) and Optimism contract to the project. |
| 264 | + |
| 265 | + ```sh |
| 266 | + cd lib |
| 267 | + npm install @ethereum-attestation-service/eas-contracts |
| 268 | + cd .. |
| 269 | + echo @ethereum-attestation-service/=lib/node_modules/@ethereum-attestation-service/ >> remappings.txt |
| 270 | + ``` |
| 271 | + |
| 272 | + 3. Create `src/Verifier.sol`. |
| 273 | + |
| 274 | + ```solidity file=<rootDir>/public/tutorials/Verifier.sol hash=2a05d192f106b2102bf01a30267557c2 |
| 275 | + ``` |
| 276 | + |
| 277 | + <details> |
| 278 | + <summary>Explanation</summary> |
| 279 | + |
| 280 | + ```solidity file=<rootDir>/public/tutorials/Verifier.sol#L6-L18 hash=64aecb7206a0fa13918ea9ac47ee3dc2 |
| 281 | + ``` |
| 282 | + |
| 283 | + At writing these definitions from [`ICrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/L2/ICrossL2Inbox.sol) are not yet part of the [npm package](https://www.npmjs.com/package/@eth-optimism/contracts), so we include them here for now. |
| 284 | + |
| 285 | + ```solidity file=<rootDir>/public/tutorials/Verifier.sol#L27-L46 hash=73afc3cedf0863c12150ffe6aaed676b |
| 286 | + ``` |
| 287 | + |
| 288 | + This function is part of the [EAS contract code](https://github.com/ethereum-attestation-service/eas-contracts/blob/master/contracts/EAS.sol#L692-L711) that calculates the attestation ID. |
| 289 | + |
| 290 | + ```solidity file=<rootDir>/public/tutorials/Verifier.sol#L48-L62 hash=7c912d2eb9fa21a3abf297f7c27a89d2 |
| 291 | + ``` |
| 292 | + |
| 293 | + Calculate the payload hash. |
| 294 | + |
| 295 | + ```solidity file=<rootDir>/public/tutorials/Verifier.sol#L64-L92 hash=80799162cceb4183360481672f3fb7d4 |
| 296 | + ``` |
| 297 | + |
| 298 | + This is the function that gets the attestation information, builds the call, and calls `CrossL2Inbox`. |
| 299 | + |
| 300 | + ```solidity file=<rootDir>/public/tutorials/Verifier.sol#L94 hash=b728334118d486dc5e020c5b84d41411 |
| 301 | + ``` |
| 302 | + |
| 303 | + This section is reserved for actions that depend on a successful attestation verification. |
| 304 | + In real-world applications, implement your core logic here. |
| 305 | + </details> |
| 306 | + |
| 307 | + 4. Deploy the new contract and preserve the address. |
| 308 | + |
| 309 | + ```sh |
| 310 | + export VERIFIER_ADDRESS=`forge create Verifier --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY --broadcast | awk '/Deployed to:/ {print $3}'` |
| 311 | + ``` |
| 312 | + |
| 313 | + 5. You can try to just call `verifyAttestation` directly, but without the correct access list it is guaranteed to fail. |
| 314 | + Instead, get back to the offchain part of the project. |
| 315 | + |
| 316 | + ```sh |
| 317 | + cd ../offchain |
| 318 | + ``` |
| 319 | + |
| 320 | + 6. Create a file, `onchain-verification.mjs`. |
| 321 | + |
| 322 | + ```javascript file=<rootDir>/public/tutorials/onchain-verification.mjs hash=0a71974948ba96b474d602a703ae7466 |
| 323 | + ``` |
| 324 | + |
| 325 | + <details> |
| 326 | + <summary>Explanation</summary> |
| 327 | + |
| 328 | + This is a modified version of the `verify-attestation.mjs` we used earlier, so we only go over the new parts. |
| 329 | + |
| 330 | + ```javascript file=<rootDir>/public/tutorials/onchain-verification.mjs#L11-L18 hash=e21e60f14d0d6a89e24ad74fcc3fde25 |
| 331 | + ``` |
| 332 | + |
| 333 | + Read the ABI for `Verifier` we created onchain. |
| 334 | + |
| 335 | + ```javascript file=<rootDir>/public/tutorials/onchain-verification.mjs#L57-L74 hash=456446f9f3aa7911b7e83a191102bdaf |
| 336 | + ``` |
| 337 | + |
| 338 | + Call `Verifier` with the information needed to verify the attestation. |
| 339 | + |
| 340 | + ```javascript file=<rootDir>/public/tutorials/onchain-verification.mjs#L76 hash=e32ded7cb46f9480474ad06d0f968ec1 |
| 341 | + ``` |
| 342 | + |
| 343 | + Report the transaction hash. |
| 344 | + </details> |
| 345 | + |
| 346 | + 7. Run the verification program. |
| 347 | + |
| 348 | + ```sh |
| 349 | + node onchain-verification.mjs |
| 350 | + ``` |
| 351 | + |
| 352 | + 8. The program output includes a `VERIFICATION_TRANSACTION_HASH=` line. |
| 353 | + Run it to keep track of the verification transaction. |
| 354 | + |
| 355 | + 9. See the receipt for the verification transaction. |
| 356 | + |
| 357 | + ```sh |
| 358 | + cast receipt $VERIFICATION_TRANSACTION_HASH --rpc-url $URL_CHAIN_B |
| 359 | + ``` |
| 360 | + |
| 361 | + 10. Modify the name in line 73 of `onchain-verification.mjs` and see that false attestations don't get verified. |
| 362 | +</Steps> |
| 363 | + |
| 364 | +## Next steps |
| 365 | + |
| 366 | +* Read the [Superchain Interop Explainer](/interop/explainer#faqs) or check out this [Superchain interop design video walk-thru](https://www.youtube.com/watch?v=FKc5RgjtGes). |
| 367 | +* Use [Supersim](/app-developers/tools/supersim), a local dev environment that simulates Superchain interop for testing applications against a local version of the Superchain. |
| 368 | +* Find a cool dapp that only works on a single blockchain and extend it to the entire interop cluster, at least for reading information. |
0 commit comments