Skip to content

Commit 5b51fd3

Browse files
authored
Merge pull request #1613 from qbzzt/250514-attestation-relay
Tutorial for relaying arbitrary log messages (done with attestations)
2 parents 92f44f4 + d7460b0 commit 5b51fd3

File tree

6 files changed

+735
-0
lines changed

6 files changed

+735
-0
lines changed

pages/interop/tutorials/_meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"transfer-superchainERC20": "Transferring a SuperchainERC20",
55
"custom-superchain-erc20": "Custom SuperchainERC20 tokens",
66
"bridge-crosschain-eth": "Bridging native cross-chain ETH transfers",
7+
"verify-messages": "Verifying log entries",
78
"contract-calls": "Making crosschain contract calls (ping pong)",
89
"event-reads": "Making crosschain event reads (tic-tac-toe)",
910
"event-contests": "Deploying crosschain event composability (contests)",
Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
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

Comments
 (0)