Skip to main content

Comparison of Circom Provers

· 7 min read
Vivian Jeng
Developer on the Mopro Team

Introduction

Throughout 2024, we compared various Groth16 provers for Circom. Our goal was to demonstrate that native provers (written in C++ or Rust) outperform snarkjs in terms of speed. Along the way, we uncovered some fascinating insights, which we’re excited to share with you in this post.

To understand a Groth16 prover, let’s break it down into two main components: witness generation and proof generation.

Witness Generation: This step involves processing inputs along with witness calculation functions to produce the necessary witness values for a circuit. It's a purely computational step and does not involve any zero-knowledge properties.

Proof Generation: Once the witness is generated, this step takes the witness and the zkey (generated by snarkjs) to compute the polynomial commitments and produce a succinct zero-knowledge proof.

Ideally, developers should have the flexibility to switch between different witness generation and proof generation implementations. This would allow them to leverage the fastest options available, optimizing performance and enhancing their development experience.

However, each of these tools presents unique challenges. In the following sections, we will delve into these challenges in detail and provide a comparison table for clarity.

Witness Generation

snarkjs

snarkjs is one of the most widely used tools for generating Groth16 proofs and witnesses. Written in JavaScript, it runs seamlessly across various environments, including browsers on both desktops and mobile devices. However, it faces performance challenges with large circuits. For instance, an RSA circuit can take around 15 seconds to process, while a more complex circuit like zk-email may require up to a minute to generate a proof. This highlights the need for optimized solutions, such as leveraging mobile-native capabilities and even mobile GPUs, to significantly enhance performance.

witnesscalc

witnesscalc is a lightweight, C++-based tool designed for efficient witness generation for circuits compiled with Circom. It offers a faster alternative to JavaScript-based tools like snarkjs. With cross-platform support and compatibility with other ZKP tools, Witnesscalc is ideal for handling performance-sensitive applications and large circuits.

While Witnesscalc performs exceptionally well with circuits such as RSA, Anon Aadhaar, Open Passport, and zkEmail, integrating it into Mopro presents challenges due to its C++ implementation, whereas Mopro is built on Rust. We are actively working to bridge this gap to leverage its performance benefits within the mobile proving ecosystem.

wasmer

One option available in Rust is circom-compat, maintained by the Arkworks team. This library uses the .wasm file generated by Circom and relies on the Rust crate wasmer to execute the witness generation. However, wasmer doesn’t run natively on devices—it creates a WebAssembly execution environment for the .wasm file. As a result, the performance of wasmer is comparable to the WebAssembly performance of snarkjs running in a browser.

Initially, we encountered memory issues with wasmer during implementation (issue #1). Later, we discovered that the Apple App Store does not support any wasmer functions or frameworks, making it impossible to publish apps using this solution on the App Store or TestFlight (issue #107). As a result, we decided to abandon this approach for Mopro.

circom-witness-rs

Another Rust-based option is circom-witness-rs, developed by the Worldcoin team. Unlike solutions that rely on WebAssembly (wasm) output from the Circom compiler, this tool directly utilizes .cpp and .dat files generated by Circom. It employs the cxx crate to execute functions within the .cpp files, enhanced with optimizations such as dead code elimination. This approach has demonstrated excellent performance, particularly with Semaphore circuits. However, we discovered that it encounters compatibility issues with certain circuits, such as RSA, limiting its applicability for broader use cases.

circom-witnesscalc

The team at iden3 took over this project and began maintaining it under the name circom-witnesscalc. While it heavily draws inspiration from circom-witness-rs, it inherits the same limitation—it does not support RSA circuits. For more details, refer to the "Unimplemented Features" section in the README.

rust-witness

Currently, Mopro utilizes a tool called rust-witness, developed by a member of the Mopro team. This tool leverages w2c2 to translate WebAssembly (.wasm) files into portable C code. By transpiling .wasm files from Circom into C binaries, rust-witness has demonstrated compatibility across all circuits and platforms tested so far, including desktop, iOS, and Android. Additionally, its performance has shown to be slightly better than that of wasmer.

Proof Generation

snarkjs

As mentioned earlier, snarkjs is the most commonly used tool for generating Groth16 proofs. However, its performance still has room for improvement.

rapidsnark

Rapidsnark, developed by the iden3 team, is an alternative to snarkjs designed to deliver faster Groth16 proof generation. Similar to witnesscalc, it is written in C++. While it shows promising performance, we are still working on integrating it into Mopro.

ark-works

The primary Rust-based option is circom-compat, maintained by the Arkworks team. Arkworks is a Rust ecosystem designed for programmable cryptography, deliberately avoiding dependencies on native libraries like gmp. In our experiments, Arkworks has proven to work seamlessly with all circuits and platforms. If you have Rust installed, you can easily execute Groth16 proving using Arkworks without any issues. As a result, Mopro has adopted this approach to generate proofs for cross-platform applications.

Comparison Table

Here, we present a table comparing different witness generators and proof generators to provide a clearer understanding of their features and performance.

In this comparison, we use circom-witnesscalc as a representative for both circom-witness-rs and circom-witnesscalc, as they share fundamentally similar implementations and characteristics.

Witness Generatorsnarkjswitnesscalcwasmercircom-witnesscalcrust-witness
Performanceslowthe fastest 🚀slowsometimes fastest 🚀slightly faster than snarkjs
Supported CircuitsallallallRSA not supportedall
LanguageJavaScriptC++RustRustRust
Browser
Desktop
iOS
Android
Mopro Support⚠️ WIP 1❌ Abandoned⚠️ Possible 2
Proof Generatorsnarkjsrapidsnarkarkworks
Performanceslowthe fastest 🚀fast
Supported Circuitsallallall
LanguageJavaScriptC++Rust
Browser3
Desktop
iOS
Android
Mopro Support⚠️ WIP 4

Conclusion

In conclusion, we found that the witnesscalc and rapidsnark stack offers the best performance, but integrating it into Rust presents significant challenges. These tools rely heavily on C++ and native dependencies like gmp, cmake, and nasm. Our goal is to integrate these tools into Rust to make them more accessible for application development. Similar to how snarkjs seamlessly integrates into JavaScript projects like Semaphore and ZuPass, having a Rust-compatible stack would simplify building cross-platform applications. Providing only an executable limits flexibility and usability for developers. In 2025, we are prioritizing efforts to enable seamless integration of these tools into Rust or to provide templates for customized circuits.

We recognize the difficulty in choosing the right tools and are committed to supporting developers in this journey. If you need assistance, feel free to reach out to the Mopro team on Telegram: @zkmopro.

Footnotes

  1. We are actively working on integrating witnesscalc into Mopro. Please refer to issue #284

  2. Please refer to PR #255 to see how to use circom-witnesscalc with Mopro.

  3. waku-org has investigated this approach; however, it does not outperform snarkjs in terms of performance. Please refer to this comment for more details.

  4. We are actively working on integrating rapidsnark into Mopro. Please refer to issue #285

Reflecting on 2024 - The Mopro Retrospective

· 5 min read
Vivian Jeng
Developer on the Mopro Team

It has been a remarkable year for the Mopro project. We’ve successfully transitioned from a proof of concept to a ready-to-use solution, attracting significant interest from various projects.

Here are the milestones we’ve achieved this year, along with key reflections on our journey.

Optimizing Developer Workflow

We streamlined the development process through significant codebase refactoring. By merging mopro-core and mopro-ffi into a single mopro-ffi folder and consolidating the iOS, Android, and web apps into a test-e2e folder, we reduced folder depth, making it easier for contributors to locate functions.

Additionally, we removed the circuits compilation and trusted setup processes, along with unused toolchain targets. These optimizations have drastically improved our CI workflow, reducing the peak runtime from around 1 hour to just 10 minutes (10 times faster)!

We also enhanced the Mopro CLI, significantly reducing the time required for setup and usage. As mentioned earlier, users no longer need to install unnecessary toolchains or download unused circuits from the Mopro repository.

Looking ahead to 2025, we plan to make the CLI even more accessible by providing precompiled binaries for download, eliminating the need for git clone during installation.

Users can now quickly clone the repository and build an iOS or Android project in just three commands—mopro init, mopro build, and mopro create—all within 3 minutes! For detailed instructions, check out the Getting Started section.

Enabling Multi-Platform Support

In addition to Swift for iOS and Kotlin for Android, we’ve now created templates for cross-platform frameworks like React Native and Flutter. The CLI has been updated to support these platforms, and the documentation has been refreshed to reflect these enhancements.

Please refer to the following resources:

Additionally, Mopro now supports WASM for web browsers. We provide wasm-bindgen for the Halo2 prover, enabling developers to use the Mopro CLI to generate website templates with bindings. This significantly reduces the time spent navigating the outdated Halo2 tutorial available at Using halo2 in WASM (It was authored in 2022).

Please refer to the following resources to learn how to use Mopro for building WASM applications for web browsers:

Expanding Compatibility with General Rust Functions

We realized that generating and verifying proofs alone isn’t sufficient for application developers. To address this, we made the Mopro template compatible with any Rust crate or function, allowing developers to customize the UDL file to suit their specific needs.

For instance, if a developer needs a Poseidon hash function but neither Swift nor Kotlin provides a Poseidon hash library, they can integrate a Rust Poseidon crate. First, they define the function API in Rust, such as:

pub fn poseidon(input: Vec<u8>) -> Vec<u8>{
// Poseidon hash implementation
}

Next, they define the corresponding API in the UDL file. Please refer to uniffi-rs to learn more about the UDL file format.

For example:

namespace mopro {
// functions...
bytes poseidon(bytes input);
}

By running mopro build again, the developer can generate Swift and/or Kotlin bindings for the Poseidon hash function. They can then easily call the function in Swift or Kotlin like this:

let hash = poseidon(input: input)

or in kotlin

val hash = poseidon(input)

Additionally, this approach is compatible with WASM for browsers. You can define a function in Rust as follows:

use serde_wasm_bindgen::to_value;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn poseidon(input: JsValue) -> Result<JsValue, JsValue> {
// Poseidon function implementation
to_value(...)
}

Then, by running mopro build again with the web target, you can generate the necessary bindings for the web. Once built, you can call the poseidon function directly in JavaScript, making it seamlessly accessible in browser-based applications.

The Rise of New ZK Mobile Apps

This year, we’ve seen a growing number of ZK mobile apps being developed. Some notable examples include:

  1. World ID
  2. Anon Aadhaar
  3. Open Passport
  4. Myna Wallet
  5. FreedomTool

These apps benefit significantly from mobile-native proving compared to using tools like snarkjs. For instance, Anon Aadhaar achieves up to 8x faster performance with rapidsnark compared to snarkjs.

For more details on the benchmarks, please refer to the benchmark section.

While we’ve provided the Mopro stack with rust-witness and ark-works, most applications are leveraging the witnesscalc and rapidsnark stack for faster proving, particularly with RSA circuits.

Given the adoption trends and benchmark results, we've recognized the need to prioritize improving rapidsnark integration and further enhancing the developer experience. This will be a key focus in Q1 of 2025.

We’re excited to see even more ZK mobile-native apps emerge in the near future, delivering improved performance and enhanced user experiences.

Final Thoughts and Looking Ahead

The Mopro tool has become more robust, now supporting multiple platforms. However, our vision extends further—we aim to develop a mobile-native ecosystem as comprehensive and developer-friendly as the JavaScript/TypeScript ecosystem, empowering developers to seamlessly build innovative apps.

As we look to the future, we encourage developers to explore the opportunities in building ZK mobile applications. By leveraging mobile-native proving, you can create apps that are not only faster but also more accessible to users worldwide. Let’s work together to shape the next wave of ZK technology and bring its benefits to mobile platforms!