A Criticism of LUCID and Encryption-Scheme-Agnostic Encrypted Mempool Designs
Purpose of this Document and Disclaimer
EIP-8184 (LUCID) is a recent proposal for an encrypted mempool design. Encrypted mempools aim to protect users from bad forms of MEV (such as frontrunning and sandwiching) and solve a very real, user-facing problem. Unfortunately, designing a secure encrypted mempool is a difficult problem, and the “dream version” of it requires cryptographic tools, namely some form of good enough threshold encryption, that we do not (yet?) have.
LUCID was written with the clear aim to provide the best possible scheme given this lack of good enough threshold cryptography – but this comes with serious downsides and limitations.
In this post, we collect and document these downsides and limitations, including pitfalls in the countermeasures that LUCID uses to mitigate them. The goal is a reference document that gathers the negatives of LUCID in one place, easily accessible and easy to understand. The target audiences include:
- People who want to work further on encrypted mempools and seek to mitigate the downsides presented here.
- Core devs who eventually need to decide whether to include LUCID as part of the EIP process.
- Potential users who want to know the “fine print” of LUCID.
We want to stress that the most important limitations of LUCID described below ultimately stem from the lack of good enough cryptography and are acknowledged in the LUCID EIP itself. In particular, the single biggest weakness is the reveal optionality by the transaction sender. We believe that any encrypted mempool design will have that issue as long as we lack sufficiently good cryptographic tools[1].
Disclaimer
The author does not claim that the points of criticism collected here are new; as mentioned above, several issues are even acknowledged in the EIP itself.
Also, while the criticism raised here sounds (by design) very negative, the author does not hold a strong pro- or contra opinion about including the LUCID EIP (after some easy bugfixes/clarifications): given the current state of the art on the cryptography side, a LUCID-style design may indeed be the best we can do for now, and the use case for an encrypted mempool is very strong. However, we believe it is important to make such a difficult decision – which, realistically, is (some variant of) LUCID or nothing for now – while being very well informed about the downsides.
What is LUCID?
LUCID is an EIP for an encrypted mempool design. It aims to let users submit a transaction tx (say, an ETH → USDC swap) to the public mempool and have it included such that no other party can see the content of the transaction before inclusion[2]. This eliminates certain forms of toxic MEV, such as frontrunning the swap.
Let us give a brief, high-level (and somewhat simplified – in particular, we largely omit bundles) outline of how LUCID works. We refer to the EIP for more details. Note that our explanation here differs quite a bit from the one given in the EIP, due to our different focus.
In LUCID, a user at first submits only a commitment to tx to the mempool, and buys a transaction ticket that is included in slot n. Buying a transaction ticket essentially means that the user obtains the guarantee that their committed-to transaction will be included at a determined point in time, provided they comply with the protocol rules. After the block payload of slot n is fixed and includes the transaction ticket, the user opens the commitment and reveals tx. All such revealed LUCID transactions are then executed in a predefined order between slot n and slot n+1. Formally, LUCID forces the builder of the next slot n+1 to include all correctly revealed transactions top-of-block. For our discussion, however, it is more helpful to think of the revealed LUCID transactions as being executed between the slots.
Where is the Encryption here?
The astute reader might have noticed that there is no mention of encryption in the description above, only a commitment. This is because LUCID is in fact not an encrypted mempool, at least in the sense commonly understood in the literature; rather, it tries to achieve those goals via a commit-and-reveal scheme. LUCID uses encryption only in the following ways:
- Rather than committing to
txand revealing it later, LUCID publishes an encryption oftxand commits to (and later reveals) a short key that decrypts it. This has the huge advantage of moving the bulk of the communication to an earlier point in time, leaving only small messages for the latency-critical reveal, at the small cost of leaking some message-length metadata earlier. However, this optimization does not affect the arguments of this post, so we consider the simpler version. - The protocol does not really care where the reveal messages come from, and LUCID adds rudimentary support for delegating the reveal, in the sense that the user can designate an external party that gets paid for it. How the user enables this external party to actually do so is off-protocol; it likely involves some kind of trust relationship in both directions and/or may well involve actual encryption – e.g., the user may threshold-encrypt toward a committee.
Reveal Optionality by the Transaction Sender
The big problem inherent in a commit-and-reveal scheme (and, as we argued above, likely inherent to any solution without the cryptographic tools we do not yet have) is that the sender can simply choose not to reveal. Even worse, they may choose whether to reveal after seeing the reveals of other parties. Concretely, several attack scenarios exemplify this issue.
Probabilistic Frontrunning
An attacker who has a solid guess about an honest user’s committed-to transaction tx (e.g., a trade between a common pair on a DEX) – but is not sure – might insert a frontrunning transaction tx_attack immediately preceding tx, where both tx and tx_attack use LUCID. After tx is revealed, the attacker reveals tx_attack if their guess was correct and does not reveal otherwise. Note that the attacker may also insert k preceding transactions corresponding to 2^k possible reveal-or-not combinations.
Latency-Sensitive Transactions
Because LUCID effectively creates a new phase between slot n and slot n+1 in which transactions are executed, there is a strong incentive for latency-sensitive use cases to use LUCID and exploit its optionality. Consider DEX-CEX arbitrage: an arbitrageur may commit at time t_1 to a LUCID transaction that performs DEX-CEX arbitrage, and then at time t_2 decide whether to reveal it, depending on price fluctuations between t_1 and t_2. Here t_1 is the latest time at which a transaction can be submitted during slot n-1 for slot n, and t_2 is the latest time a reveal is possible (for the transaction to be executed between slot n and slot n+1). Effectively, LUCID adds another timestamp at t_2 where there is an opportunity to clear CEX-DEX price differences (before LUCID, the next timestamp would have been when the window for slot n+1 closes). Arbitrageurs are financially incentivized to use this timestamp, and to use optionality to account for multiple potential versions of the price fluctuations. Note that this kind of optionality is not actually bad from an economic point of view: it allows a faster price signal to enter the chain, and fees for non-reveal burn the MEV. The issue is simply that there is an incentive to use LUCID in a way that involves not complying with the intended rules (by not revealing) and that competes with other use cases, driving prices up.
Countermeasures
LUCID provides several countermeasures, but these do not fully prevent optionality-related issues; they only make exploiting optionality more costly. Notably, LUCID applies a penalty for non-reveal, and it allows each transaction (or bundle) to limit the number of preceding LUCID transactions (or bundles). The issue is that these countermeasures do not prevent the attacks, they merely make them harder, which may well be insufficient. In the case of probabilistic frontrunning, one problem is that the legitimate sender has to pay a sufficiently high top-of-block fee (and essentially escrow a 128-fold multiple of it for a short period of time) to deter the attacker, which they might not be willing to do. Also, the necessary deterrent depends on the uncertainty about the plaintext of the attacked transaction from the attacker’s point of view, which is hard to estimate for the legitimate user. The limit on preceding transactions does not scale well with heavy usage of LUCID for regular use cases such as swaps, in particular because the latency-sensitive transactions discussed above compete for the top spots in the list of LUCID transactions. Note that bundles (the ability to collect multiple transactions for slightly better communication efficiency) aggregate their deterrent, but that might not help too much: a bundle may be more worthwhile to attack, because there is a higher chance that any given trading pair is present in it, so a probabilistic attack also has a greater chance of success. Additionally, there may be a free-loading problem with setting a bundle’s aggregate top-of-block fee if the constituent transactions of a bundle belong to different senders.
Builder Optionality
The design of LUCID intends to force the builder of slot n+1 to include top-of-block those transactions that were revealed during slot n. This is enforced via the Payload-Timeliness Committee (PTC), which votes on the availability of these reveals: roughly speaking, if more than 50% of the committee vote to have seen the reveal in time, the builder must include it and if more than 50% vote to not have seen it, the builder must not include it. The problem is that there is some leeway for reveals that arrive too late and/or for equivocated votes. In fact, if an attacker engineers a 45%/45% split between the votes and equivocates the remaining 10%, then the next builder may choose to include or not, thereby also gaining (another instance of) optionality. This can be used to amplify the gains from CEX-DEX arbitrage by making choices later. Indeed, those reveals are likely to come with a close-to-50% split anyway, as the arbitrageurs are incentivized to decide as late as possible. A consequence is that any PTC committee member who has a chance to become the next slot’s builder is actually incentivized to equivocate and collaborate with arbitrageurs for a fee. This also means that revealing your transaction late might not protect against probabilistic frontrunning attacks.
Note: equivocations are not penalized, as that part of the design was taken from FOCIL, whose security goals are not really threatened by equivocations in the same way. In FOCIL, which aims to enhance censorship resistance, we only need to ensure that transactions that were made widely available indeed do get included. We do not really care about the converse there (i.e. that transactions not made widely available are not included). For LUCID, we now need to care about this converse direction, as it relates to builder optionality.
UX Issues
Sender Metadata
The committed transactions in LUCID do not need to come from the same address as the one that performs the actual transaction. In fact, to prevent sender metadata from leaking, we want the committed transaction to come from an unlinkable address. In light of the probabilistic frontrunning problem, leaking which user a given LUCID transaction belongs to is actually fatal, since it gives frontrunners a very good idea of what the transaction content would be and whether it is worthwhile to attack. This makes LUCID hard to use correctly. The user either needs to send some funds for gas to a one-time address via a privacy pool and with a sufficient time delay, or has the transaction enter via some third-party relay that it pays off-chain. A typical scenario for the latter would be using your balance on a CEX to have the CEX send the committed transaction on your behalf. The third party does not need to know the transaction plaintext, but this still runs somewhat counter to the goal of not requiring any other party and of having a public mempool. Note that the third party can still sell you out by linking your transaction to you.
The ideal solution essentially would be to allow paying directly from a privacy pool using some form of sufficiently powerful account abstraction. Note that this avoids having to create a new address for each transaction.
Too Many Options – Let the Wallets Decide?
To use LUCID correctly, the user needs some kind of unlinkable address. The countermeasures to probabilistic frontrunning require users to set a priority fee accordingly (to be at the top of the LUCID transactions) and/or to limit the number of preceding transactions. The user also needs to choose whether to delegate the reveal or do it themselves. We seriously doubt that normal users are able to do all this.
A consequence is that wallets will probably make these decisions for users, for better or worse. In the case of reveal delegation, there is a serious risk that wallets will treat it as a funding opportunity for themselves rather than defaulting users to the best choice: the wallet developers might run a reveal service themselves (or be paid by one) and default to using it, thereby collecting fees, even when that is not the best choice for users. As for the priority fees and the limit on preceding transactions, it is also questionable whether wallets would get these right even if they tried.
Cryptographic Agnosticism / Flexibility
One selling point of LUCID is that it is agnostic to any particular kind of encryption mechanism, and that this happens off-chain. While this is definitely good in the sense that it saves us from having to implement a concrete cryptographic (threshold) encryption scheme, it feels like a bad idea for several reasons.
Why Delegate At All?
It is unclear why a user would want to delegate the reveal at all. The only real advantage is that the reveal is somewhat latency-critical and a third party might handle it better; however, this hardly justifies an additional trust assumption, and we want to get rid of trusted relays. In addition, if that party runs threshold encryption and decrypts via committee, this adds considerable latency, which somewhat defeats the purpose. Without a compelling use case for why delegation would actually be a good choice (and “funding a wallet” is not a convincing one), this is not very persuasive. From a UX point of view, while delegation allows fire-and-forget transaction sending, most users want to watch the chain for inclusion anyway – and this also allows a later reveal by the wallet without trusting a third party.
Cryptographic Agility for the Bad Guys
Generally speaking, cryptographic flexibility can be good if it allows informed users to make the best choice for their use case and required security. However, it is bad if it is the adversary who chooses the scheme. We are in the second case here: the adversary (say, one sending a probabilistic frontrunning transaction) will simply choose not to use any encryption at all and self-reveal (or not).
How Future-Proof is the Design?
One hope might be that we could deploy LUCID now and later enshrine a particular threshold scheme, forcing users to use it rather than self-reveal (thereby removing the optionality). There are several issues with this:
- It would require deprecating existing delegation options, which by then will have become vested economic interests.
- LUCID likely does not fit such a design well: the reveal messages that LUCID expects are the individual plaintexts (or a transaction-specific key that decrypts one particular previously sent ciphertext to a plaintext transaction). This does not fit well with a potential future threshold encryption scheme where a single key k might decrypt multiple ciphertexts. If we plugged this into LUCID, we would not be able to distribute k; instead we would have to distribute the result of applying k to each ciphertext separately, which is wasteful.
- There might be a need to adapt the encrypted mempool design to the underlying cryptographic mechanisms. As an example, the author is currently investigating whether a good encrypted mempool design can be built from threshold IBE without so-called batching, using some of the ideas from LUCID. (This is work in progress and, at any rate, will not immediately give us a good alternative scheme, but only reduces the requirements on the cryptography side a little bit.) Such a design requires changes to the encrypted mempool to fit the limitations of the encryption scheme, and could not simply be plugged into LUCID.
To elaborate on the last point a bit more: if we just force users to use a designated enshrined threshold scheme within LUCID, this would only address the optionality by the transaction sender. It would not help with the builder optionality problem we explained above. Indeed, the author’s work-in-progress design, which is heavily inspired by LUCID, needs to run an extra round of the consensus protocol to reach agreement on which reveals (or encryptions) were made widely available in order to prevent builder optionality; furthermore, the extra round means we require more careful deliberations regarding replay attacks across reorgs and missed slots.
We want to clarify that in LUCID’s case, running such an extra round is probably too costly in terms of complexity and UX to justify it: the builder optionality problem only adds a little compared to the sender optionality problem. However, this changes if we could get rid of the sender optionality problem.
Bad Memes
As a closing remark: we also need to be very careful about how LUCID (or encrypted mempools in general) are communicated. As argued above, LUCID is hard to use correctly, and it does not prevent frontrunning – it only makes it harder, and how much harder depends on several factors that are realistically too hard to explain to an average user.
There is a very serious risk that the information that reaches users will be “Ethereum now has an encrypted mempool. You don’t need to care about frontrunning anymore.” If a user gets frontrun even while using LUCID, they will likely blame Ethereum for failing to protect them and write very, very angry posts on X/Twitter.
In a way, promising something and not fully holding up to it can be worse than not making the promise in the first place – and we are nowhere near fully in control of what information ends up with users.
[Edit: Removed a “Coordination Issue” item, that arose solely due to a misunderstanding/ambiguity in the EIP. We are trying to clarify this in the EIP
Edit(2): Fixed a mistake in “Countermeasures”, which was talking about gas, in response to a reply by Anders. Also clarified why bundles might not help as much as one might think.]
To avoid the reveal optionality by the transaction sender, we need some way to later decrypt a message without the sender’s participation. The essential options here from the cryptography side are
(i) Using some form of time-based cryptography, where we try to ensure that decryption takes some minimum time t_1 even by a powerful adversary, but also can be done within some allowed time t_2>t_1 by everyone. In our use case, the gap between t_1 and t_2 is prohibitively small, essentially ruling that approach out.
(ii) Using some form of trusted execution environment (TEE), where the keys for decryption are securely stored and only used in some correct way (e.g. the TEE refuses to decrypt unless we present some timestamp signed by a majority of validators). These approaches have a questionable security track record.
(iii) Using threshold encryption, where we encrypt towards a committee of validators (where we assume some honest majority), who each hold a share of some form of decryption key and can only jointly decrypt. This replaces the need to trust a single party by the more acceptable assumption that a majority of validators is honest. Unfortunately, the list of desiderata for such a threshold encryption scheme is too big (sufficient security, post-quantum security, adequate functionality, various measures of efficiency and low communication) and no currently available scheme fits the bill. ↩︎A more precise security property is that no other party can learn the content of the transaction
txand use this knowledge to insert a transaction beforetx. Indeed, in LUCID the transaction content becomes known before it is “officially” executed, but at the point where the content becomes known, all transactions up to the official execution oftxare already fixed. ↩︎