Skip to main content

EVM rollapps powered by Mina Protocol

· 12 min read

Cover image

ZK is poised to bring massive scalability to blockchains, enabling many use-cases that were previously too computationally expensive or required too many transactions to be viable before. However, ZK today often works in isolated environments which limits onboarding of users. Today, we'll cover how we've manage to connect EVM wallets (ex: MetaMask) to Mina Protocol so that users can interact with zkApps built using Mina Protocol without having to download a specialized wallet.

What kind of ZK are we talking about?

Large applications typically do not have all users interacting on the same global state directly. Rather, users are typically split into instances (ex: different servers for different countries) or are made to complete actions locally before only sharing the result publicly (ex: leaderboards on a game). In either case, note that from out outside point of view we do not attempt to monitor the full happenings of the system - we only care about the result (or at most an occasional synchronization checkpoints).

There are many different techniques to implement ZK proofs with different tradeoffs. Fortunately, there is an option that allows taking transactions in an external system and compressing what happened in that system down to a single constant-sized crypographic proof. The name of this technique is called Recursive SNARKs, and they form the basis of Mina Protocol, which is one of the few ZK protocol that is production-ready.

You can learn more about how we're contributing to scale Mina protocol in our previous blog post here.

Background: Using Mina with Ethereum cryptography

In order to make Mina protocol run efficiently, Mina protocol utilizes ZK-friendly cryptographic for every operation including signature schemes that wallets use to sign transactions and messages. This brings an adoption barrier, as it means users typically have to download separate purpose-built software (ex: Pallad wallet for Mina) to interact with the Mina protocol. While these purpose-built wallets will always provide the best experience, being able to onboard users with their existing crypto wallet makes a big difference for user onboarding.

Fortunately, Mina allows writing any computation as a ZK circuit in theory, and that involves ECDSA foreign curve support - the scheme used by standard EVM wallets. Therefore, it's possible to have a user sign a message with their EVM wallet, and then wrap that signature into a Mina-compatible ZK proof.

Fortunately, the team at o1Labs (the main R&D company behind Mina Protocol) has already built the ECDSA primitives and released them as part of the Mina protocol stack. However, that still leaves a very important question: how do we use these primitives in the most efficient way possible for the user experience we want to provide?

Desirable user experience

The success of decentralized games such as Tarochi shows how important it is to allow people to start playing games no matter which wallet they have installed (conceptually similar to account abstraction). Paima Engine lets players start playing immediately with a newly-generated wallet stored as website data, and then delegate authority to it from their long-term wallet once they're ready to commit, by signing a particular message. Thanks to Mina's ECDSA support, we can achieve a similar user onboarding experience by taking the same sort of signature and inputting it into the zero-knowledge system, proving what might be called a delegation order. This order is scoped to a particular application or game and its proof can be used in the zero-knowledge system just like the signature it is based on might be used in an EVM system.

In other words, players who have an Ethereum wallet in their browser can easily and safely log in to applications built on Mina and get started without having to install a separate Mina wallet right away.

Approach

One could say there are two ways of writing apps for Mina:

  1. zkApp: this is the closest to what you find with Solidity - contracts are written, then deployed to the chain where they can manage contract state
  2. ZkProgram: these are stateless programs that are not deployed to the chain, but whose results (zk proofs) can still be submitted as inputs to zkApp functions (see docs)

Therefore, similarly, there are two approaches for implementation a delegation scheme:

  1. zkApp: tracks delegation information as part of contract storage. Any dApp in the future can lookup the mapping in this contract
  2. ZkProgram: proves delegation. The proof can be reused as inputs to any contract where it is needed

During benchmarking, we made two key observations:

  1. Having nested Merkle tres (as shown in the graphic above) leads to very large circuit sizes. Therefore, to achieve a reasonable circuit size, one can flatten the Merkle tree. That is to say, instead of having nested Markle trees, you have a single Merkle tree where the leaf nodes are hash(Mina Address || EVM address). Therefore, we will use this flattened version in our benchmarks below. The downside of this approach is that it makes it harder to do computation over the aggregate data from a smart contract (ex: get all EVM addresses associated with a Mina address), but this is not a common use-case. Another approach to solving the large circuit size issue could be to use sideloading (more on this later), but this introduces extra conceptual complexity and will be strictly slower, and the flattened Merkle tree provides a nice lower-bound for the Merkle tree approach in our benchmark.
  2. We found that generating a ZkProgram proof of the delegation signature and storing it (~32KB) to use as an input to future recursive proofs won out over storing state on-chain in Mina using Merkle trees. This approach is much simpler, proves at about the same speed, and verifies faster, but is slightly slower to compile to a circuit on first use. This benchmark was done in Node, but results in the browser are similar.

Notably, the ZkProgram wins out on 3 key aspects:

  1. It's easier to cache, which improves start time for recurring users
  2. It requires less network calls (we don't need to fetch the Merkle tree data from an indexer)
  3. Verification is faster
StepRecursive ZkProgramFlattened Merkle treeNote
Non-cacheable compile() time~4s~5s
Cacheable compile() time~25s~9s
Signature-to-proof time~15s~12-15s
(plus network overhead)
Cacheable; one-time cost per delegation order.
Proof verification time~0.5s2-4s
(plus network overhead)
Repeated each time the order is used within another Mina proof.

(Times from a recent desktop machine (i9-14900K). Old devices or mobile devices may, in some cases, be multiple times slower)

These results make sense if you think of function calls within Mina as working similar to ZkPrograms (it's a separate program you execute, get of proof of the result, and then return execution back to the main contract), and therefore using ZkProgram directly gives more flexibility and enables more optimization be explicitly making it stateless.

Perf improvement: server-side proof generation

Given proof generation times are relative high, and given that the ZkProgram's input (the Ethereum signature) is not required to be kept secret, this proof generation could be offloaded to a server (ex: a Proof Market) as you can give a server the signature and request it to generate the wrapped proof. This can help make the ZkProgram proof generation time even faster (even on limited hardware like mobile devices).

Perf improvement: side-loading the proof

One downside of any approach involving ZK-side verification of ECDSA is circuit size. The disk usage of a ZkProgram that proves the validity of just one signature is on the order of 730 MiB. Given the size, it's impractical to try and find a clever way to distribute parts of the circuit ahead-of-time, so we're stuck with compiling the circuit at proof time.

However, we cannot simply keep the circuit in storage or in memory, in its entirety, as the memory & storage implications are too big. Instead, we can leverage a "sideloaded" dynamic proof, which would allow ZK consumers of the delegation order to supply only the ~2KiB verification key of the delegation order program, rather than having to compile the whole 730 MiB circuit. This means we can just store the small verification key in localstorage.

Note: this approach of side-loading also be used to improve the Merkle tree approach by keeping the 2-level tree option. This is done by setting the 1st level to be Mina addresses as before, but setting the 2nd level to be verification keys of the 2nd level Merkle tree program. This helps alleviate the circuit size issue,.

Implementation

All told, an example of using Paima Engine's upcoming support for this process might look like:

// Imports and setup.
import { PublicKey } from 'o1js';
import { extractPublicKey } from '@metamask/eth-sig-util';
import { delegateEvmToMina, Ecdsa, Secp256k1 } from '@paima/mina-delegation';

// 1) Compile circuit

const { DelegationOrder, DelegationOrderProgram, DelegationOrderProof } =
// This prefix is shown to the user when signing and separates this proof from that for other applications.
delegateEvmToMina(`Example Game login: `);

await DelegationOrderProgram.compile();

// 2) Prepare a delegation order and ask the user to sign it with their EVM wallet

const target: PublicKey = /* ... */;
const data: Uint8Array = DelegationOrder.bytesToSign({ target });
const signature: string = await personal_sign(data); // However you communicate with a wallet.
const ethPublicKey = extractPublicKey({ data, signature });

// 3) Use Mina's ECDSA support to turn that signature into a ZK proof.

const order = new DelegationOrder({
target,
signer: Secp256k1.fromHex(ethPublicKey),
});
const proof = await DelegationOrderProgram.sign(order, Ecdsa.fromHex(signature));
const jsonProof = proof.toJSON();

// 4) `jsonProof` (which contains `order`) can be saved to localStorage and reused later.

Commercialization of this technology

Based on our findings, the ZkProgram with sideloading approach is a clear winner, which is why we're implementing into our @paima/mina-delegation as part of Paima Engine

We are also commercializing this approach by implementing it into our upcoming game Click & Moo


Click & Moo will be a fully decentralized idle game where you can idle (generate ZKified Verifiable Delay Function proofs) to earn Moolah. You can learn more about the architecture behind the game in our previous blog post on using Mina for games, and we will have more details released on our Twitter as well as on our blog as we get closer to release.

Our team is committed to continued work on improving usability of ZK in commercial applications, so definitely follow along as we keep pushing the boundary of what can be done with ZK cryptography.

Future work

Deeper EVM connection

Of course, connecting EVM wallets to Mina is the first step in connecting these ecosystems. The next step is to make state created on Mina accessible on the EVM. The difficult is that Mina uses custom cryptography for all operations including hashes (which means that Mina Merkle trees need to be converted to EVM-friendly Merkle trees to make accessing data efficient on the EVM side). There are currently two options for this:

  1. Self-hostable bridges (ex: mina bridge). This approach can be used for any EVM chain you like, but the downside is that it requires running your own infrastructure, and the proofs generated by the software are prohibitively expensive on EVM chains that do not either have very low gas fees or a way to write custom precompiles to support Mina's cryptography faster than what you get with native Solidity (ex: BOLD on Arbitrum).
  2. AVS (Actively Validated Service) systems (ex: Aligned Layer). This approach gives you cheap gas costs even on expensive chains (ex: Ethereum L1), but requires support from the AVS teams to support new chains. Additionally, the security of the AVS system depends on some new security system (ex: EigenLayer in the case of Aligned Layer).

In practice, for Aligned Layer, given they are aggressively adding new chains and they are based on EigenLayer for the security, so we consider them safe and easy to work with approach. Comparatively, the self-hostable bridge may be the better for specific use-cases, but we think Aligned Layer will the better option for the majority of projects. This is why for Click & Moo, we are planning to leverage Aligned Layer to connect the Mina-powered ZK parts of our game with Arbitrum (the largest L2 for Ethereum).

Scaling the games

Thanks to recursive SNARKs provided by Mina, a lot of the proof generation & aggregation can be done on the client side which massively reduces the onchain cost of building games like Click & Moo. However, we still need to combine aggregate proofs across multiple players into a single final proof when we want to send game state to an EVM chain. To achieve this, we plan to leverage Zeko which is a rollup framework for Mina Protocol. It will allow us to spin up ephemeral ZK rollups as needed to aggregate user transactions, while still fully supporting recursive SNARKs and all other Mina features so that which rollup is being used is transparent from the game perspective.

Learn more about Paima

Want to build your own game with Paima Engine? There's never been a better time to get started!


Want to just follow along? Find us on social media: