Yoav Weiss / DCxPrague
DCxPrague 2023 · November 2023 · 22:15
Yoav Weiss explains why account abstraction is hard to build without giving up censorship resistance. Smart contract wallets cannot transact on their own, and the usual fix, a centralized relayer for meta-transactions, exposes users to downtime, front-running, censorship and privacy leaks. He shows how decentralized relay networks such as the Gas Station Network can be griefed by non-deterministic accounts, and how ERC-4337 restricts account validation, for example by returning a valid time range instead of reading the timestamp, so the system needs no centralized or permissioned components.
Speakers
Topics
From DCxPrague 2023, hosted by DCxPrague 2023.
Transcript
all right can you hear me yeah all right so so today I'm going to talk about about account obstruction but not H not as usual about use cases but to focus on H on a censorship resistance and why it is harder with the account obstruction so in order to in order to understand this we need to think what what is an account and one way to look at an account is is uh the entity that can operate on chain as a that can operate on chain directly without any intermediaries an EA satisfies this requirement with an EA externally owned account you can sign the transaction you
send it to a mempool and it gets included a smart contact wallet unfortunately doesn't uh doesn't satisfy this requirement and you cannot use um you cannot use it directly on chain you need the help of another account of an EA in order to operate it and this creates a big ux problem because we're not going to be able to bring the next billion users that we like to talk about if we need them to H keep two funded accounts one for gas one for everything else and to sign each transaction twice once for the Gas payment and once for the actual transaction so um what can we do so the common approach until ERC 4337
was to use a central ized centralized relayer uh and chances are that if you're using a non 437 smart account that you are using such centralized relay the way these relays work is uh by having um is by sending what's called a meta transaction basically the the user signs a transaction that cannot be used on chain directly it sends the transaction to a send a transaction to a to a relay the relay signs the trans transaction with an EA that is funded it's owned by the relay by the Relay operator puts it on chain then this this meta transaction gets peeled off by the um by the smart contract wallet which
handles the transaction and then reimburses the reimburses the relay and uh now that's uh this approach works but clearly it's not great for sensorship resistance because I mean it works as long as it works but what happens if for example the company of the the company that runs the relay goes bust so uh they stop paying their Amazon bill and suddenly you're trying to transact to your account and it doesn't go through so there's a so there may be a liveness issue or maybe uh maybe the server maybe the server gets attacked someone someone attempts to denial to do a denial of service against the server at the at the worst possible time for
you and that's exactly when it's going to happen because it's not random uh then the server operator could also extract me because uh you know they they get to delay your transactions when there is price action in order to for run you with 100% success because you cannot win a race against the server you interacting with so there's the there's this issue and of course the plain old the know simple sensorship because the server is operated by human and humans can be coerced so someone might get the server to sensor you at the worst possible time or there's just the Privacy issue where the server the server keeps loged so it knows your IP
address which is associated with your real identity and it sees your transactions and you don't know who they're sharing it with so we need a better we need a better approach now the naive solution would be let's have many servers let's say run multiple servers but that's not really decentralized because if these servers are operated by the same entity then uh they are going to censor you together they're going to go down together so the next logical step would be let's make it a decentralized and incentivized network which is what we did a few years ago with the GSN the the gas station network so there's a network of there's a network of relays that uh that get
paid to do the work it's a permissionless anyone can join and you can use it to uh you can use it to send meta transactions however there's a catch there's a catch when using this H to uh to manage a smart account what if the account behaves inconsistently maybe it's not deterministic so when the server uh when the relay when the relay sees the transaction it looks like it's going to H it looks like it's going to reimburse it and to pay to pay it but uh when you put it on chain it actually doesn't it detects that it is now running on chain and it doesn't pay so the Rel can lose money and we we need to mitigate this uh we need to mitigate this
risk so there are several mitigations that may seem possible but each of them actually hurts sensorship resistance for example we could have um so we could we could keep a white list of known account implementations so the server only agrees to serve these accounts that it knows are not going to grief it but then who gets to decide which which account implementations are legitimate and which ones should not be served so that's not a great approach another one is to whitel list users let's have the users open a username with a capture or something H open a username on the server and if a user misbehaves and doesn't pay even just once you stop
serving that user so that works but it's a permission system and if you can block a user H for not paying then you can also block the user for other for other reasons so we are back to the sensorship problem or you could just try to keep reputation for accounts don't don't require users but if an account doesn't pay then H you stop serving it that approach doesn't work because of H because of a lack of Cil resistance anyone can spin up any number of accounts and keep gripping griffing the server until it's out of funds so the server might try to uh might try to be clever to simulate the transaction make sure that it pays and
then use something flash Bots send it to the flashboot relay and ask the relay to only include it if it's if it pays if it doesn't uh if it if it really pays the fee so that would work until someone is determined to attack it but when someone does want to censor it they're going to flood it with the non-deterministic transactions that are going to not pay on chain and flashboot will spend a lot of CPU simulating it but never but these transactions will never never be included after a while uh flash boots will h block this uh will block this relay and we are back to square one so clearly we need a uh we need a better approach we need an approach that
takes these things into account otherwise we end up either with a vulnerable Network or with one that is permissioned and uh cannot uh and cannot offer a sensorship resistance so ERC 4337 aims exactly to solve this problem it focuses on fully abstracting uh on fully abstracting the account without compromising on a sensorship resistance so there are no centralized components there's no uh there are no centralized component and nothing is permissioned in the system and because it's h because it's a full account obstruction it means that H the validation is actually an evm bite
code getting executed which opens up many attack vectors that we need to address right so what does the protocol need to achieve in order to solve this first of all we need it to be permissionless for bundlers anyone should be able to add bundlers to the mol and uh these bunders should these bunders should be able to offer their services but not to uh do anything bad to the user operations and we need it to be permissionless for users any user should be able to propagate something into the mol and no one should be able to censor it we need to ensure that bundlers are uh that bundlers get compensated for the work as long as they do it correctly and they cannot be
griefed because if they can be griefed then H if they can be griefed they will lose money they will go down and we will not have a network and we need to protect the entire network against denial of service attacks which are uh which can be surprisingly easy to perform against such a network so uh okay so so first of all what we needed to do I mean how do we achieve this the the one thing we had to do first is to split the transaction to two phases we have a validation phase and execution phase in validation you have um so in validation you could have a separate gas limit so let's say you have a 5 million 5 million gas transaction
that would be very expensive to simulate but you can limit it you can say that it has a validation gas of only 200k which means that the amount of CP you you have to spend simulating the transaction to decide whether it's uh whether you should include it is is very limited so the CPU impact the CP CPU impact per transaction is much is much lower this way then we need to also uh we need to also make sure the transaction is deterministic if it is valid if it's valid in simulation it needs to also be valid on chain that means that we cannot allow environment access such as block number during uh H during validation you can use any op code you lack during
execution but certain op codes should not be used during validation and you really don't need them I mean you don't need to look at a block number to validate your signature so um so otherwise you could have a you could have a transaction that uh you could have an account that is for example valid during even blocks and invalid during odd blocks so now someone could propagate a transaction to the mol that is currently valid because the last block was a was a an even one but then when someone tries to include it on chain in the next block which is odd it's going to revert so we prevent that by not accepting the transaction if it accesses the block number even if it's uh uh even even if it would currently be
valid so there's so we have a list of these environment op codes that are banned H another problem we had to deal with is mass invalidation that means a doing performing a of service attack against the entire network by sending a very large number of transactions that are valid so they do get propagated but then invalidate all of them by front running them with one transaction that makes a state change making them all invalid on chain so that would keep the that would keep all the nodes constantly busy doing unpaid work and unable to perform any any paid work so to prevent that we needed to enforce certain storage rules
such as uh the account should not during validation the account can access its own storage or storage associated with this address such as a mapping of uh mapping of this address in other contracts but it cannot access anything associated with other accounts so this means that um uh this means that in order to invalidate in order to invalidate end transactions you need to write to to actually write on chain to end slots and that's extremely expensive so this attack doesn't scale then we also need to make sure that if a transaction is valid alone it is also valid on chain together with other uh with other transactions in the
same bundle because otherwise someone could produce a transaction that invalidates another transaction now we already established in the previous rule that transactions that validations cannot during validation transactions cannot access ER the storage associated with another account so validations cannot invalidate each other however execution can so if for example we had a transaction that uh we have transaction one which during its execution modifies a state that transaction two uh validation we look at and then transaction one can invalidate transaction two so this would make it very hard to build a to build a valid
block or to build a a a valid bundle to to prevent that we perform all the validations before any execution so the validations cannot invalidate each other and by the time any execution runs everybody all the accounts already agreed to pay for the gas so now if they're going to mess with each other they could revert each other's transaction but still they pay the gas so the network is safe against this and out of service now uh there are some Global entities in the system such as a pay masters a global entity means that uh it could The Entity could be used in the context of multiple accounts at the same time for example a pay Master sponsoring the transactions of multiple uh of
multiple accounts during the same bundle but this means that if it is allowed to access its own storage it could keep a flag to detect that it's running in a bundle that it gets called twice and then uh and then invalidate the bundle since we cannot uh fully prevent this attack we had to uh um we had to also add an enforcement through a reputation so if it causes so if a pay Master if a if if such an entity cause causes a lot of validation uh then it will get throttled it will get throttled by the by the mol so nodes will stop propagating and serving it but uh it wouldn't work if if we don't have Cil resistance because
then someone can keep deploying more and more of these entities so introduce staking now it's not classical staking sense that it cannot get uh you cannot get slashed but it has an unstake DeLay So it serves as Cil resistance you can uh uh if you want to if you get throttled if a pay Master get throttled and it wants to deploy with a new address it has to either wait for the unstake delay in order to restake or it needs to stake an additional amount so to sustain the attack you need to keep staking more and more funds which makes it not scale so I think for the sake of time I'm going to skip some uh to skip some some other attacks but as you can see we have we had to cover numerous uh attack vectors and try to reach to a to
a minimum set of restrictions during validation to keep everything uh permissionless and uh still safe against attacks right U I'm skipping a bit but um so why do why do all the bundlers need to to implement exactly the same protection technically you could have a slightly different rules that still achieve the same level of protection so the reason the reason to have this as a standard where everyone is enforc the same rules in is related to another form of Dos against the against the network because let's say if you wanted to uh if you wanted to Dos this peer-to-peer
Network and remember that it's permissionless you could set a node that keeps spamming other nodes sending them a lot of invalid transactions that use that all use the maximum uh the maximum allowed validation G and then say no it's not valid and then you could keep the network busy but um so we need to prevent that and the way nodes handle it is if they receive a transaction that is valid the transactions are always propagated together with the block hash they were validated against and if if as a node you receive a you receive a transaction you see that it's invalid when simulated against the same uh the same St the same block hash it means
that someone is trying to Dos you because there is no legitimate reason for it not to be uh not to be valid so in that case you treat it as a spammer and you block it now what happens if the BL if uh we have slightly different implementations of the rules in different bundlers what would happen is the bundler someone would propagate a transaction that is valid for some but invalid for others so they would treat each other as spammers disconnect from each other and we end up with a lot of small fragmented Mol instead of one Big M Pool is that a problem so it is especially if you if you think about a sensorship resistance because each such M Pool is small it has less nodes so the less nodes you have in
the M Pool the easier it is to sensor you it becomes Much More Much More centralized we also lose the robustness of the multic client approach because ERC 437 took the same Approach at ethereum where we have multiple clients uh we have like a multiple bundler implementations with different programming languages so it's unlikely that the same bug will exist everywhere but if each implementation has its own rules then we're going to end up with a a mol per implementation so now a single software bug could take down an entire M Pool and the users will not be able to propagate it and we also lose Economic Security because now each M Pool only has a small subset of the of H of all the of all the available transactions so
it cannot construct the most profitable bundle which means that there is less incentive to participate in it so you get less participants so to prevent this we need to have a multiple implementations of the bundler but only compatible ones and ethereum needed to deal with the same problem and found solution for it because ethereum has multiple clients but it uses um but it has very clear specs of how they should behave and we are a and we have a lot of testing we have a reference implementation we have compatibility tests we perform fuzzing to try to detect cases where it would break consensus so ERC 457 takes exactly the
same approach we have a we have a specs for the rules we have a reference implementation we have compatibility tests and once the peer-to-peer network is up we're going to fast the hell out of it so to conclude uh we see that it's really easy to design an account obstruction protocol if you don't care about account obstruction but once you start thinking about it and going down the rabbit hole of decentralization it becomes infinitely harder and that's why it took us a couple of years to get it right and hopefully we did um now uh smart account developers wallet developers generally don't care about censorship resistant they they would they could even use a centralized
RPC they don't care about it they only care about user features and that's okay because they're a product as a product they're building a product when you're building a product you care about user features you don't think about worst case scenario that your users don't care about until it happens so in order to get sensorship resistance they could just use an SDK that makes full use of ERC 4337 and then they get sensorship resistance for free and uh but this free sensorship resistance actually is provided by the bundlers so it can it only works if you're using a compatible bundler which which uh unfortunately not all bundlers at the moment are so if you are going to run a bundler you should pick one from the list there's a there's a list that gets generated by the tests
and it shows you which ones pass the test so you should H choose one that is compatible and then soon when the mol the peer-to-peer Network comes up it will be able to participate and get the full benefit of it uh right and uh do we still have time for questions or are we done can do one question go thanks um thanks for the presentation um in one of the middle slides you mentioned there that the validators should not be using all the op codes as specifically those which are relevant to the environment right and you specifically mentioned the block ID or block number up code right um I guess
that you could also count the time stamp of code in in the environment you know uh set of the op codes uh the time stamp is often used in approvals right or in in whatever which is related to the time frame so uh so this is there is quite a significant application usage of the time stamp um how would you reflect this right so excellent question we also had to think about it in the first version uh two years ago we didn't and you couldn't check the time stamp because if you could check the time stamp then you could uh create non deterministic Behavior then we realize that uh the validation function could return a Time
range so it you don't need to check the time instead you send instead you return H your the return value for the validation function says a and okay it's valid if the time range is between this and that so now you can Implement any logic you like but instead of checking the time stamp you return the time stamp that you expect so now you cannot use it for non- terministic Behavior but you can use it to implement any time based validation right thank you very [Applause] much