Skip to main content
Version: 0.2.0

iOS Setup

This tutorial will guide you through integrating the iOS bindings into an Xcode project. Before you begin, make sure you’ve completed the "Getting Started - 3. Mopro build" process with selecting iOS platform and have the MoproiOSBindings folder ready:

MoproiOSBindings
├── mopro.swift
└── MoproBindings.xcframework

Watch the demo video below for a step-by-step guide to integrating the bindings into Xcode, or follow the written instructions that follow.

info

In this example, we use Circom circuits and their corresponding .zkey files. The process is similar for other provers.

0. Initialize an XCode project

First let's make a new iOS app Xcode project. If you already have an app project you can skip this step. We'll do File -> New -> Project and create an iOS App. Make sure the language is Swift. We suggest putting this iOS project inside the rust project folder created above.

create an ios app project

1. Drag the MoproiOSBindings folder into the project

Your Xcode project should be open now. Open Finder and drag the MoproiOSBindings folder into the project folder structure.

add mopro ios bindings to project

2. Place proving keys into the project

Next drag in any key you plan to prove with. Go to the project Build Phases and add each key to the Copy Bundle Resources step.

copy zkeys as bundle resources

Now you're ready to write the proving code in your app!

warning

Although relative paths may work locally in Rust, the proving keys should be copied into the project to ensure they are accessible by the mobile app.

3. Proving from the app

In your project there should be a file named ContentView. At the top of this file add the following:

import moproFFI

This will make the proving functions generateCircomProof and verifyCircomProof available in this module.

In the ContentView itself add a private variable and a button like this:

struct ContentView: View {
private let zkeyPath = Bundle.main.path(forResource: "multiplier3_final", ofType: "zkey")!

var body: some View {
VStack {
Button("Prove", action: runProveAction)
}
.padding()
}
}

We use the Bundle api to retrieve the full path to our zkey. Change multiplier3_final to the name of your zkey.

At the bottom of this file we'll add an extension with a function to generate a proof. In this example we're going to prove a simple circuit that accepts two inputs named a and b and generates an output c.

extension ContentView {
func runProveAction() {
// Prepare inputs
//
// The generateCircomProof function accepts an absolute path
// to the zkey, and a map of strings to arrays of strings
//
// This is a mapping of input names to values. Note that if
// the input is not an array, it will still be specified as
// and array of length 1.
let input_str: String = "{\"b\":[\"5\"],\"a\":[\"3\"]}"

// Begin timing our proof generation
let start = CFAbsoluteTimeGetCurrent()

// Call into the compiled static library
do {
let generateProofResult = try generateCircomProof(zkeyPath: zkeyPath, circuitInputs: input_str, proofLib: ProofLib.arkworks)
} catch {
print("Error generate a proof: \(error)")
}

let end = CFAbsoluteTimeGetCurrent()
let timeTaken = end - start
print("built proof in \(String(format: "%.3f", timeTaken))s")
}
}

You should now be able to run the iOS app on the simulator or a device and build a proof. The app should log the time taken to generate the proof. For a more complete example including serialization and verification check here.

4. What's next?

  • Update your ZK circuits as needed. After making changes, be sure to run:

    mopro build
    mopro update
    warning

    mopro update only works if the Android project was created within the Rust project directory during mopro init. Otherwise, you can manually update the bindings by following Step 1.

    This ensures the bindings are regenerated and reflect your latest updates.

  • Build your mobile app frontend according to your business logic and user flow.

  • Expose additional Rust functionality: If a function is missing in Swift, Kotlin, React Native, or Flutter, you can:

    • Add the required Rust crate in Cargo.toml
    • Annotate your function with #[uniffi::export] (See the Rust setup guide for details).
      Once exported, the function will be available across all supported platforms.