Skip to main content
Version: 0.1.0

Manual Setup for Android/iOS Bindings

This tutorial provides step-by-step instructions to manually build static libraries with Circom and Halo2 adapters for Android and iOS. It focuses on a hands-on approach for developers who prefer or require manual setup.

Make sure you've installed the prerequisites.

Setup Circom-Based rust project

Demo video of this tutorial

Mopro works by providing a static library and an interface for your app to build proofs. Before you start this tutorial you should have a zkey and wasm file generated by circom.

To get started we'll make a new rust project that builds this library. Run the following commands in your terminal:

mkdir mopro-example-app
cd mopro-example-app
cargo init --lib

This will create a new rust project in the current directory. Now we'll add some dependencies to this project. Edit your Cargo.toml so that it looks like the following:

[package]
name = "mopro-example-app"
version = "0.1.0"
edition = "2021"

# We're going to build a static library named mopro_bindings
# This library name should not be changed
[lib]
crate-type = ["lib", "cdylib", "staticlib"]
name = "mopro_bindings"

# We're going to build support for circom proofs only for this example
[features]
default = ["mopro-ffi/circom"]

[dependencies]
mopro-ffi = { git = "https://github.com/zkmopro/mopro.git", branch = "main" }
rust-witness = "0.1.1"
uniffi = { version = "0.28", features = ["cli"] }
num-bigint = "0.4.0"

[build-dependencies]
mopro-ffi = { git = "https://github.com/zkmopro/mopro.git", branch = "main" }
rust-witness = "0.1.1"
uniffi = { version = "0.28", features = ["build"] }

# TODO: fix this
[patch.crates-io]
ark-circom = { git = "https://github.com/zkmopro/circom-compat.git", version = "0.1.0", branch = "wasm-delete" }

Now you should copy your wasm and zkey files somewhere in the project folder. For this tutorial we'll assume you placed them in test-vectors/circom.

info

Download example multiplier2 wasm and zkey here:

Now we need to add four rust files. First we'll add a build.rs in the main project folder. This file should contain the following:

use std::path::Path;
fn main() {
// We're going to transpile the wasm witness generators to C
// Change this to where you put your zkeys and wasm files
rust_witness::transpile::transpile_wasm("./test-vectors/circom".to_string());
// This is writing the UDL file which defines the functions exposed
// to your app. We have pre-generated this file for you
// This file must be written to ./src
let udl_path = Path::new("src/mopro.udl");
if !udl_path.exists() {
std::fs::write(udl_path, mopro_ffi::app_config::UDL).expect("Failed to write UDL");
}
// Finally initialize uniffi and build the scaffolding into the
// rust binary
uniffi::generate_scaffolding(udl_path.to_str().unwrap()).unwrap();
}

Second we'll change the file at ./src/lib.rs to look like the following:

// Here we're generating the C witness generator functions
// for a circuit named `multiplier2`.
// Your circuit name will be the name of the wasm file all lowercase
// with spaces, dashes and underscores removed
//
// e.g.
// multiplier2 -> multiplier2
// keccak_256_256_main -> keccak256256main
// aadhaar-verifier -> aadhaarverifier
rust_witness::witness!(multiplier2);

// Here we're calling a macro exported by uniffi. This macro will
// write some functions and bind them to the uniffi UDL file. These
// functions will invoke the `get_circom_wtns_fn` generated below.
mopro_ffi::app!();

// This macro is used to define the `get_circom_wtns_fn` function
// which defines a mapping between zkey filename and witness generator.
// You can pass multiple comma seperated `(filename, witness_function)` pairs to it.
// You can read in the `circom` doc section how you can manually set this function.
// One way to create the witness generator function is to use the `rust_witness!` above.
mopro_ffi::set_circom_circuits! {
("multiplier2_final.zkey", multiplier2_witness),
}

Finally we'll add a new file at src/bin/ios.rs:

fn main() {
// A simple wrapper around a build command provided by mopro.
// In the future this will likely be published in the mopro crate itself.
mopro_ffi::app_config::ios::build();
}

and another at src/bin/android.rs:

fn main() {
// A simple wrapper around a build command provided by mopro.
// In the future this will likely be published in the mopro crate itself.
mopro_ffi::app_config::android::build();
}

Now you're ready to build your static library! You should be able to run either

cargo run --bin ios # Debug mode

or

cargo run --bin android # Debug mode

to build the corresponding static library. Move on to iOS setup or Android setup to begin integrating in an app.

info

To achieve optimal performance, it's recommended to build and run your application in release mode. You can do this by setting the CONFIGURATION environment variable to release before running your commands.

Example usage

CONFIGURATION=release cargo run --bin ios # Release mode

or

CONFIGURATION=release cargo run --bin android # Release mode

Running your project in release mode significantly enhances performance compared to debug mode. This is because the Rust compiler applies optimizations that improve runtime speed and reduce binary size, making your application more efficient.

Setup Halo2-Based rust project

Similar to the Setup Circom-based Rust project, start by running the following commands in your terminal:

mkdir mopro-example-app
cd mopro-example-app
cargo init --lib

This will create a new Rust project in the current directory. Next, add the required dependencies to the project. Edit your Cargo.toml to match the following:

[package]
name = "mopro-example-app"
version = "0.1.0"
edition = "2021"

# We're going to build a static library named mopro_bindings
# This library name should not be changed
[lib]
crate-type = ["lib", "cdylib", "staticlib"]
name = "mopro_bindings"

# Adapters for different proof systems
[features]
default = ["mopro-ffi/halo2"]

[dependencies]
mopro-ffi = { git = "https://github.com/zkmopro/mopro.git", branch = "main" }
uniffi = { version = "0.28", features = ["cli"] }
num-bigint = "0.4.0"
plonk-fibonacci = { package = "plonk-fibonacci", git = "https://github.com/sifnoc/plonkish-fibonacci-sample.git" }

[build-dependencies]
mopro-ffi = { git = "https://github.com/zkmopro/mopro.git", branch = "main" }
uniffi = { version = "0.28", features = ["build"] }

Next, copy your SRS and key files into the project folder. For this tutorial, we'll assume you place them in test-vectors/halo2.

Now, add four rust files, just as in the Circom-based Rust project setup.

First, add a build.rs in the main project folder. This file should contain the following:

fn main() {
// This is writing the UDL file which defines the functions exposed
// to your app. We have pre-generated this file for you.
// Feel free to modify it to suit your needs.
let udl_path = Path::new("src/mopro.udl");
if !udl_path.exists() {
std::fs::write(udl_path, mopro_ffi::app_config::UDL).expect("Failed to write UDL");
}
// Finally initialize uniffi and build the scaffolding into the
// rust binary
uniffi::generate_scaffolding(udl_path.to_str().unwrap()).unwrap();
}

Next, update the ./src/lib.rs file to look like the following:

// Here we're calling a macro exported by uniffi. This macro will
// write some functions and bind them to the uniffi UDL file.
mopro_ffi::app!();

mopro_ffi::set_halo2_circuits! {
("plonk_fibonacci_pk.bin", plonk_fibonacci::prove, "plonk_fibonacci_vk.bin", plonk_fibonacci::verify),
}

Similar to the Circom-based Rust setup, add ios.rs and android.rs for binary execution.

Create the file src/bin/ios.rs as shown below:

fn main() {
// A simple wrapper around a build command provided by mopro.
// In the future this will likely be published in the mopro crate itself.
mopro_ffi::app_config::ios::build();
}

Create another file src/bin/android.rs as shown below:

fn main() {
// A simple wrapper around a build command provided by mopro.
// In the future this will likely be published in the mopro crate itself.
mopro_ffi::app_config::android::build();
}

Now you're ready to build your static library! You should be able to run either

cargo run --bin ios

or

cargo run --bin android