New Ethereum talks, every Monday. The week's conference uploads by event, in your inbox.

Verifier Alliance: inside of the contract verification pipeline

Devcon 7 SEAWed, Nov 13, 2024, 05:55 AM · 08:40

The talk will guide you through a smart-contract verification process step by step while introducing some technical details and challenges verification services have to handle. Will describe what we have learned building "Verifier Alliance" - a new collective that unites different verification providers to have an open and shared database of smart contracts (verifieralliance.org).

Transcript

All right, thanks for coming to my talk. So this is a talk on L2 composability from Collaborative Snarks, or in other words, how do we synchronously compose but also retain horizontal scalability at the same time? So Web3 is all about composability, at least in my view. It's about composable money, I guess. Traditional web applications have been independently operated.

You need to trust an operator. They communicate and interact asynchronously. And a lot of what we think about in terms of Web3 is the security or the decentralization. But in my view, the thing that comes from decentralization is enabling everyone to trust a common distributed virtual machine that the entire world can use and what is the consequence of that? What is the benefit of that?

Well, the benefit of that is that all applications can run on the same system and they can compose with each other and that's like the meaningful difference to an end user or one of the meaningful differences to an end user and that's what enables um you know so much innovation to thrive uh and as we call it ethereum's infinite garden to to thrive um and so um you know the the it even enables new applications that just never existed in the world of uh of asynchronous systems before such as a flash loan, where you can have a lending application that is designed completely independently of these decentralized exchanges, even two different decentralized exchanges, and now suddenly you can have a user create a single transaction that interacts with all these applications to borrow money, do some things with it, and return it all in the same transaction. And obviously there's so much more that you can do besides flash loans, but this is what I'd like to use as the example of what we can do with composability that we just couldn't do in the world before. Now, of course, roll-ups have been amazing for scaling Ethereum, making it more performant. They horizontally scale Ethereum. A multi-chain world was born out of this need to scale.

And the advantages that it brings are, one, charting of computation across different applications. It also allows powerful nodes, because the Internet is not homogeneous, but heterogeneous. And if you have millions of nodes participating in a system, then there's going to be more powerful ones and weaker ones. Well, rollups enable powerful nodes to help weaker nodes verify state. And it also allows for virtual machine diversity.

So there are many, many, many benefits. But one of the things that came out of a multi-chain world on Ethereum is that we no longer have composability of applications across these different chains. So what is composability? Composability isn't something that is perfectly well defined. I take sort of a holistic view on composability.

It could refer to things like bridging of assets, like moving ETH from one chain to another. There are higher forms of composability that can be enabled by sending cross-chain messages and having dependencies between actions on different chains. And you can have even cross-chain function calls, which are the way that applications interact within the same virtual machine, and one application can actually call a function on another application. And one way to look at how to define the highest form of composability which I call synchronous composability is through the framework of of ACID transactions in traditional database systems. So in a synchronously composable cluster of chains users should be able to express cross-chain transactions or intents, such as their intent to atomically swap something or borrow funds from one chain, do something another chain and return the money, that have this ACID property, which is atomicity, all parts of the transaction complete or none due.

There's no in-between state that's reached or observable. Consistency, so the rules of each chain are preserved. Isolation, different cross-chain transactions or transactions to independent chains, they don't interfere with each other. And so in particular what this means is that if you're in the middle of executing an atomic swap, another transaction shouldn't be able to observe the fact that that atomic swap, another transaction shouldn't be able to observe the fact that that atomic swap hasn't completed yet. So traditional atomic swaps do not have this isolation property because they involve locking an asset on one chain, waiting a long time for something to happen on the other chain, and then eventually resolving it.

In fact, atomic swaps have this liveness risk where if one of the chains loses liveness, you can even break the atomicity. And of course, durability, which is basically just that there's consensus on a persistent global transaction lock. So consistency and durability are just inherited from the fact that we're running on blockchains. But the two key things are atomicity and isolation, and those are not achieved by sort of asynchronous ways of composing different chains. So what is the easiest way to asynchronously pass messages between chains?

Well, it's via the layer one. So this is the rough idea. You have a roll-up. A roll-up can have basically a mailbox. I like to describe it as an outbox and an inbox.

And it can write messages to its outbox that should be read by the other chain. And you can actually have, you know, the sequencer of the chain basically just fill the inbox of one chain by reading from the outbox of the other chain and providing a proof to its inbox that can be verified against the Merkle root of the outbox of the other chain. Of course, there's different ways you can actually implement this. This would just be one way of implementing it. But this is, you know, essentially if you're familiar with, like, RIP 7755 or other things that are being discussed, like, this is roughly, you know, the most straightforward way of passing messages between rollups without introducing additional assumptions on off-chain oracles or anything.

Just using the L1. Now, the downside of this, of course, is that in order for Rollup B to read a message from Rollup A, it needs to wait for Rollup A to not only finalize its transactions by posting to Ethereum and waiting for Ethereum finality, which is about 12 to 15 minutes, but it needs to be able to trust that Merkle route, and so that requires waiting the settlement time of rollup A which could be multiple days if rollup A is an optimistic rollup. So this way of passing messages via the L1 takes a very long time. It can take multiple days. So cross-chain transactions, they have very high latency, long wait before a chain can read a message from another chain.

The other thing that asynchronous message passing via the L1 doesn't achieve is it doesn't achieve this asset property for cross-chain transactions. So there's another way of passing messages, potentially faster. And so this is a slight change. What we're going to do here is we won't wait for verifying a message against a Merkle root, but rather the sequencers will just be allowed to fill the inboxes with messages that were allegedly written to the outboxes of the other chains, and just sort of optimistically accept them, process them, and only at settlement time will we do anything to check that the messages that were written to the outbox of the first chain are consistent with the messages that were written to the inbox of the second chain. So this requires a change in the way that rollups settle.

It requires this coordinated settlement, or aggregated settlement, as some people call it. And this could, for example, be just implemented as a cross-check on the L1 between the Merkle roots of the two different mailboxes. Or we could involve some kind of aggregator that creates ZK proofs of the two different chains and actually proves the consistency of the mailboxes as well. Now, the advantage of this is that the rollups don't have to wait for multiple days in order to ingest a message from the other chain. Of course, if they're given an incorrect message, then they will experience...

I mean, when they go to settle, it won't work. So it's sort of like when you create an invalid block for a ZK roll-up, and then the proof can't be created, and then you would have to go back to where you were before. It's not technically a reorg, but the full nodes could become confused for some period of time. But optimistically, you would just be able to make progress really fast, right? And you still have the security of the L1.

Now, when you're passing messages this way, you're still not able to achieve ACID cross-chain transactions because, you know, let's say you were trying to use this in order to achieve some kind of atomic swap, you would send a message to another chain that you locked an asset, you would wait for a message back, you know, verifying some event on the other chain. In that interim time, you're in these locked intermediary states, that's observable, so you don't really have this ACID property. For this ACID property to hold, you need something called synchronous composability. Okay, so why are ACID cross-chain transactions so hard to achieve? Well, you need to be able to emulate multiple rounds of communication.

You need to be able to do something, send a message to another chain, and wait for a response back. And that all needs to happen within one block. That can't happen across multiple blocks. There's also an inherent dependency on the global ordering of transactions across all chains of the individual transactions on those chains. If you're sending multiple rounds of communication, then the relative ordering of these different transactions on the different chains will have an impact on what happens.

Now, of course, there's a naive solution, which is just to merge all chains into one, and that would certainly achieve this ACID property, right? That's a naive straw man. But the reason why this is hard to do in this, you know, is very much because of the constraint that we want to retain efficiency. We want to preserve horizontal scaling. So on its own, ACID is not hard to achieve.

It's hard to achieve it in a way that preserves scalability. And so one way to sort of make this a bit more precise is to view this through the lens of collaborative snarks. Snarks, for those of you who aren't familiar with the term, are just the technical term for ZK proofs. ZK proofs, at least the ones that we use in the blockchain world, are succinct, non-interactive proofs of knowledge. Rollups don't even use the zero knowledge property, so it's just sort of a misnomer that we ended up calling them ZK proofs.

But Snarks are the same things as ZK proofs. So there's this implicit, whenever you have chains interacting, there's this implicit unified virtual machine in which the cross-chain transactions, it's a tongue twister, take place. And so the goal is really to create some kind of single ZK proof of a state transition for a distributed system of virtual machines with this inter-process communication where we introduce one prover per virtual machine, and the total work done per prover should remain near constant as the number of interacting virtual machines grows. So that's sort of the goal of being able to preserve the scalability, the fact that the total work done per prover, per chain, remains near constant. It doesn't grow, you know, as linearly as the number of chains grows.

And in other words, the total amount of work being done in the system is linear, and the number of provers is not quadratic. So we actually can apply generic solutions to this. There are generic ways of just having all the provers for the different chains collaborate on creating a distributed SNARK for the unified VM that, you know, is their composition. You can use SNARK recursion for this. There's other ways of doing it through distributed SNARK protocols, like something called distributed sum check, distributed FFTs.

So, you know, it's not typically the way that we think about it. We think about provers being sort of their job is to just create proofs for their own roll-up, but maybe the goal should actually just be that the overall work should, you know, be horizontally scalable. But I'm going to present today sort of what you can be viewed as a special case of this, but it's a lot closer to the model in which rollups interoperate and act today. And it's just a simple extension of the asynchronous message mailbox design that I presented at the beginning. So we still have the outbox and inboxes on the two different chains, but the key difference here is that instead of chains writing to their outbox and then in the next block being able to read a bunch of messages that were populated to their inbox, ostensibly that should match the messages written to the outbox to the other chain in the previous block, that's how we did it before, there's going to be a coordinator that can just figure out, okay, these are the messages that this other chain is writing to its outbox, by simulating the execution, and I'm going to provide you with those messages.

And so these two blocks are constructed at the very same time. Chains are really just verifying execution, they're not, they don't need to carry out the execution themselves, constructed at the very same time. Chains are really just verifying execution. They don't need to carry out the execution themselves. The execution is carried out by builders who are building the blocks, in this case a coordinator.

This coordinator simulates the execution. So I can walk through an example. You can have a transaction on rollup A that basically borrows funds from some contract. It burns them. It writes a message to its outbox that they were burned.

And then immediately reads a message from its inbox that something happened on the other side. A lot of things happened in the middle over there, but they just weren't observable to this chain. So it immediately reads that messages were then minted on the other side. In the meantime, what happened was on rollup B, there was a message that was provided in the inbox that said, oh, funds were burned on rollup A. It then did a bunch of things, maybe multiple swaps at desired prices, made some profit, and then burned the funds, wrote that message to its outbox.

That message was provided by the coordinator to the inbox of rollup A, causing this transaction to complete. the refunds were returned. Now, if there was any inconsistency, if some message was written to the outbox B but that wasn't put in the inbox A, then that would be caught at settlement time. As we showed, when the roll-ups settle, all we need to do is make sure that the outboxes of chain A matches the inbox of chain B and vice versa. The outbox of chain B matches the inbox of chain A.

So that provides you the intuition. We have a full protocol on this called Coordinated Inter-Rollup Communication. It's not fully general, but it covers most practical use cases of synchronous composability among independent chains, like flash loans, which I just showed the example of, It is not fully general, but it covers most practical use cases of synchronous composability among independent chains, like flash loans, which I just showed the example of, asset swaps, limit orders, basically anything that we can really think of. It would be interesting to see if people come up with use cases that are practical that aren't covered. It doesn't require any VM modifications to the chains.

You just need to implement these contracts as mailboxes as contracts. And it enables parallel proving. So each L2 prover just independently proves its own state. It proves what the Merkle root of its mailbox is. And then there's an aggregator that creates a simple aggregated proof whose complexity is completely independent of the complexity of each chain.

These mailboxes are implemented as contracts. They use authent are implemented as contracts. They use authenticated key value maps. And, you know, any contract can basically write to the app box or read from the inbox. But you index it by which contract wrote.

So it doesn't allow contracts to sort of conflict with each other and overwrite each other. And then you have the settlement layer contract that basically just verifies the correctness of state updates from each roll-up and checks the mailbox consistency by verifying this aggregated proof. So this is what it looks like. We have this aggregator, just like before. It's exactly like the asynchronous setting, it's just that we have this coordinator, which could be, for example, a shared sequencer that figures out what messages are being communicated between the two chains, could be multiple rounds of messages, and provides those messages ahead of time, pre-populates the inboxes of the two different chains with the messages that they should be receiving over multiple rounds of communication.

Now, one important variantant of roll-ups is that full nodes should always know the state. Once a sequencer posts a transaction ordering, full nodes should be able to immediately calculate the roll-up state. They shouldn't have to wait for L1 settlement. Waiting for L1 finality of the sequencer published blocks is generally sufficient. And if the sequencer is trusted not to change its mind about what it's going to post, then full nodes should be able to trust a pre-confirmation from the sequencer within seconds.

Now, the problem is that this protocol circ sort of breaks that property unless it you know, if done naively. And the issue is that because the rollups are now interacting, the full nodes of rollup A who are ostensibly only executing for rollup A, they don't really know the state of rollup A until they can trust that the messages the coordinator provided are correct. And it won't really know that until settlement time. So how do we, yeah, how do we solve that issue? are correct and it won't really know that until settlement time.

So how do we solve that issue? That's problem number one. Problem number two is that normally you can trust a sequencer for fast confirmations. Here this coordinator, which is going to be coordinating the mailbox constructions across many, many different chains, becomes a single point of failure. And so, you know, maybe the full nodes of Arbitrum can trust the Arbitrum centralized sequencer, and the Optimism full nodes can trust the Optimism sequencer, but it's hard to trust one sequencer for confirmations when multiple chains are interacting.

The solution is to fold, one, make ZK-Proofs faster that can be communicated offline really fast, and two, use the fast confirmation layer. So a fast confirmation layer is basically a BFT consensus protocol that reaches finality faster than the L1 and ensures that whatever was finalized by the consensus protocol, you know, has to be what's eventually posted to the L1. Fast off-chain ZK proofs are interesting because the goal here is to optimize the end-to-end latency of proving. You know, and proving proof aggregation verification given the constraints on the bandwidth and compute power of each full node. And so it motivates different design constraints than when we're just trying to have ZK proofs optimized for L1 settlement.

Because the bandwidth and compute requirements of L2 nodes could be different. And so we could consider using SNARKs with larger communication, but faster proofs, like Orion, Breakdown, BaseFold, Blaze, or a number of other systems. So anyways, this is what it looks like putting it all together. You have some shared sequencer that coordinates the mailbox construction. The blocks are sent to a confirmation layer.

They're confirmed. Rollup A and Rollup B provers and create these proofs. And then you have some kind of aggregated settlement. And this is how you enable chains to either have very fast asynchronous communication or even synchronous communication that enables synchronous composability. So thank you very much, and I will go on to the questions.

Good stuff, Ben. Very technical. I think we've got time for a few questions. So the first one that's on there is the coordinator of two roll-ups needs to be synced to both of them, right? In other words, he needs to be executing both rollups himself.

That's correct. Okay. That was an easy one. Easy. And what happens if the message box says fork before settlement?

Wouldn't the nodes be reading just one rollup, be lost until settlement? Which one is this? Oh, now it's number two. Okay. What happens if the message box is fork?

Okay, social wants to be the shared player for all of us. I think I'm seeing different questions than you are. Oh, the second one. What happens if the message box is forked before settlement? So the message boxes don't fork before settlement.

I mean, so the chains need to have something that is used for their, you know, as their sort of finality layer. You can, of course, wait for L1 finality on the blocks that were published. So you publish the blocks to the L1. That's pre-settlement time. Or if you have a fast confirmation layer, such as the one that Espresso Network provides, right, then you would be publishing those blocks to that fast confirmation layer and then you would ensure that whatever is posted at settlement time is consistent with that.

Ultimately, you can define the rules or constraints of what constrains the roll-ups before settlement time, but at settlement time, you would be posting a collection of blocks for the different roll-ups that are consistent with each other in terms of these mailbox consistency checks and consistent with the rules of those roll-ups for what was finalized on different various layers before that settlement time. Okay, the next question. It voted up while it was up there. A little spicy. Espresso wants to be the shared layer for roll-ups but lacks credible neutrality.

It voted up while it was up there, a little spicy. Yeah. Espresso wants to be the shared layer for roll-ups, but lacks credible neutrality. How keen is Espresso on jointly designing the shared layer with roll-ups? So, yeah.

So Espresso has been designing this, you know, sort of confirmation layer, as we call it, which it doesn't do everything that I described today. It provides this fast confirmation layer that rollups can use. Today the Espresso network, in fact, is live on mainnet. And we have been building towards creating infrastructure that rollups can use to enhance their composability. And we've been doing it very much in collaboration and taking feedback from, you know, from roll-ups, just like DA layers, et cetera.

Okay, the next one, something that I've seen when I was briefly working with roll-ups on the DevOps side, is that how can cross-chain message passing happen if roll-ups have different latencies and hardly can be viewed as synchronized, especially with AMMs protocols being strictly arbitrage and asynchronous? It's like some that post once a day and some that post once an hour. Right, so how can cross-chain message passing happen if rollups have different latencies and hardly can be viewed as synchronized? So if rollups... So first of all, I presented two different kinds of modes of communication.

So there's asynchronous communication and synchronous communication. If the roll-ups is not about, it's not actually, it's not the latencies that matters. It's the block times of the roll-ups. So the block times are different, and you can still communicate asynchronously. If the block times are not synced, then you can't communicate synchronously.

So you would have to synchronize the block times, but you can also have different size blocks on the different rollups.

Automatic transcript — names and jargon may be misspelled.