# 2024 TBW Mopro Workshop
- https://linktr.ee/zkmopro
- 
[TOC]
## 0. Prerequisites
- Xcode or Android Studio
- Rust and CMake
:::info
Documentation: https://zkmopro.org/docs/prerequisites
:::
## 1. Install mopro cli
We offer a convenient command-line tool called `mopro` to streamline the development process. It functions similarly to tools like `npx create-react-app` or Foundry, enabling developers to get started quickly and efficiently.
```bash
git clone https://github.com/zkmopro/mopro
cd mopro/cli
cargo install --path .
cd ../..
```
## 2. Build Rust bindings with mopro cli
The `mopro init` command helps you create a Rust project designed to generate bindings for both iOS and Android.
```bash
mopro init
```
Name your project (default: `mopro-example-app`)
and choose adapters (We will use `circom` in this tutorial).
```bash
✔ Project name · mopro-example-app
✔ Pick the adapters you want to use (multiple selection with space) · circom
```
`mopro build` command can help developers build binaries for mobile targets (e.g. iOS and Android devices).
```bash
mopro build
```
Select `debug` for fast building
Select both `ios` and `android` for React Native
```bash
✔ Build mode · debug
? Select platform(s) to build for (multiple selection with space) ›
✔ ios
✔ android
```
:::info
**iOS**
Choose `aarch64-apple-ios` for iPhone device
Choose `aarch64-apple-ios-sim` for XCode simulators
**Android**
Choose `aarch64-linux-android` for latest device/Android studio emulators
:::
:::warning
Need to wait several minutes
:::
:::danger
**20250102 Update: Done in [#271](https://github.com/zkmopro/mopro/pull/271)**
We should provide optional building for different targets
instead of building them all (7 targets in total)
Welcome to contribute: https://github.com/zkmopro/mopro/issues/262
:::
## 3. Create template for React Native
`mopro create` command generates templates for various platforms and integrates bindings into the specified directories.
```bash
mopro create
```
Select `react-native`
## 4. Start the React Native app
Follow the README in `react-native-app` folder to build and start the React Native app.
:::info
README: https://github.com/zkmopro/react-native-app
:::
```bash
cd react-native-app
```
Install the dependencies like JS/TS apps
```bash
npm install
```
Choose your target
```bash
npm run ios
```
:::info
It will automatically start the simulator (if it is installed).
If not, please open Xcode to check if there is any device available.
:::
or
```bash
npm run android
```
:::warning
It might not automatically start the emulator
```bash!
CommandError: No Android connected device found, and no emulators could be started automatically.
```
If it happens, please open Android Studio and start the emulator
Or connect to an Android device.
Check if an emulator is connected
```bash!
adb devices
```
:::
:::warning
If there is an error
```bash!
> SDK location not found. Define a valid SDK location with an ANDROID_HOME environment variable or by setting the sdk.dir path in your project's local properties file at '.../react-native-app/android/local.properties'.
```
Please follow the https://zkmopro.org/docs/prerequisites to set `ANDROID_HOME` environment variable
:::
:::danger
Web target is still WIP.
But welcome to contribute: https://github.com/zkmopro/react-native-app
```bash!
npm run web
```
:::
:::warning
```bash!
npm run start
```
Doesn't work for native modules.
Please avoid using this command.
:::
## 5. Update circuits
This section explains how to swap circuits with alternative witness generators and corresponding zkey files.
### 5-1 Add wasm and zkey file in the `test-vectors/circom` folder
- wasm: https://ci-keys.zkmopro.org/keccak256_256_test.wasm
- zkey: https://ci-keys.zkmopro.org/keccak256_256_test_final.zkey
### 5-2 Add transpiling script for keccak256 circuits
In `src/lib.rs` file,
```diff=7
rust_witness::witness!(multiplier2);
+ rust_witness::witness!(keccak256256test);
mopro_ffi::set_circom_circuits! {
("multiplier2_final.zkey", multiplier2_witness),
+ ("keccak256_256_test_final.zkey", keccak256256test_witness)
}
```
### 5-3 Update bindings
:::warning
Clean the cache (**Needs to be fixed**)
```shell
rm -rf build
rm -rf target
```
:::
In the root folder, execute
```bash!
mopro build
```
Also select `ios` and `android`
### 5-4 Replace bindings
Option 1: Remove `react-native-app` folder, and run `mopro create` again
Option 2: Find the bindings' folder and replace them
- iOS:
- `modules/mopro/ios/MoproiOSBinding`
- Android:
- `modules/mopro/android/src/main/jniLibs`
- `modules/mopro/android/src/main/java/uniffi`
:::warning
We aim to provide the `mopro update` CLI tool to assist with updating bindings.
Contributions to this effort are also welcome.
https://github.com/zkmopro/mopro/issues/269
:::
### 5-5 Copy zkey to `react-native-app/assets`
React Native will read the zkey from assets folder
e.g.
`react-native-app/assets/keys/keccak256_256_test_final.zkey`
### 5-6 Update circuit input
- circuit inputs: https://ci-keys.zkmopro.org/keccak256.json
- Replace `circuitInputs` in `react-native-app/app/(tabs)/index.tsx`
- Replace `zkeyPath` with `keccak256_256_test_final.zkey`
### 5-7 Run the app again
You need to clean the built folder
```bash
# cd react-native-app
rm -rf ios
```
or
```bash
# cd react-native-app
rm -rf android
```
And run
```bash
npm run ios
```
or
```bash
npm run android
```
## 6. Update Rust exported functions
Currently, only `generateCircomProof` and `verifyCircomProof` are available with the bindings, but the bindings can be extended to support nearly all Rust functions.
Here is an example demonstrating how to use the Semaphore crate.
### 6-1 Update `Cargo.toml`
:::warning
The `mopro-ffi = "0.1.0"` version does not support customized UDL.
Please use the crate from the GitHub main branch instead.
:::
```diff
- mopro-ffi = "0.1.0"
+ mopro-ffi = { git = "https://github.com/zkmopro/mopro" }
```
In `Cargo.toml` file, update this line
```diff=14
[features]
- default = ["mopro-ffi/circom"]
+ default = []
```
Update `build.rs` file
```diff
use std::path::Path;
fn main() {
- rust_witness::transpile::transpile_wasm("./test-vectors/circom".to_string());
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");
}
uniffi::generate_scaffolding(udl_path.to_str().unwrap()).unwrap();
}
```
and clean up `src/lib.rs` file to disable `circom` related codes.
```diff
- rust_witness::witness!(multiplier2);
- rust_witness::witness!(keccak256256test);
- mopro_ffi::set_circom_circuits! {
- ("multiplier2_final.zkey", multiplier2_witness),
- ("keccak256_256_test_final.zkey", keccak256256test_witness)
- }
```
You can also remove the `rust-witness` crate
```diff
- rust-witness = "0.1.0"
```
<!-- and the patch
```diff
- # TODO: fix this
- [patch.crates-io]
- ark-circom = { git = "https://github.com/zkmopro/circom-compat.git", version = "0.1.0", branch = "wasm-delete" }
``` -->
to save some building time.
### 6-2 Import `semaphore` crate
Import semaphore crate from: https://github.com/worldcoin/semaphore-rs
```diff
[dependencies]
...
+ semaphore = { git = "https://github.com/worldcoin/semaphore-rs", features = ["depth_16"]}
```
### 6-3 Define a function to generate semaphore proof
In `src/lib.rs`, we create a function named `semaphore()` that executes operations using the semaphore crate.
> You can just copy from
> https://github.com/worldcoin/semaphore-rs?tab=readme-ov-file#example
> but with a function name `pub fn semaphore()`
> ```diff=6
> + pub fn semaphore() {
> ```
> ```diff=39
> + }
> ```
- full code `src/lib.rs`
```rust
use semaphore::{
get_supported_depths, hash_to_field, identity::Identity, poseidon_tree::LazyPoseidonTree,
protocol::*, Field,
};
pub fn semaphore() {
let mut secret = *b"secret";
let id = Identity::from_secret(&mut secret, None);
// Get the first available tree depth. This is controlled by the crate features.
let depth = get_supported_depths()[0];
// generate merkle tree
let leaf = Field::from(0);
let mut tree = LazyPoseidonTree::new(depth, leaf).derived();
tree = tree.update(0, &id.commitment());
let merkle_proof = tree.proof(0);
let root = tree.root();
// change signal and external_nullifier here
let signal_hash = hash_to_field(b"xxx");
let external_nullifier_hash = hash_to_field(b"appId");
let nullifier_hash = generate_nullifier_hash(&id, external_nullifier_hash);
let proof = generate_proof(&id, &merkle_proof, external_nullifier_hash, signal_hash).unwrap();
let success = verify_proof(
root,
nullifier_hash,
signal_hash,
external_nullifier_hash,
&proof,
depth,
)
.unwrap();
assert!(success);
}
```
:::danger
It doesn't work for target `i686-linux-android`.
If this hasn't been merged: https://github.com/zkmopro/mopro/issues/262
please manually comment out [this line](https://github.com/zkmopro/mopro/blob/a95b7f7ee81e8a111b321f4ef763d4d129cede65/mopro-ffi/src/app_config/android.rs#L24)
Or even only compile for target `aarch64-linux-android` for the latest mobile devices.
:::
### 6-4 Export the function through UDL
- Mopro uses [UniFFI](https://github.com/mozilla/uniffi-rs) to generate bindings for iOS and Android native platforms. The function interfaces are already defined in the UDL (a type of IDL, *Interface Definition Language*) file within `mopro-ffi`.
For more details, refer to the `src/mopro.udl` file.
- To define a new function, simply add its interface to the UDL file.
:::info
The default types are specified in the UniFFI documentation.
See [Built-in types](https://mozilla.github.io/uniffi-rs/latest/udl/builtin_types.html)
:::
:::danger
The UDL file differs between `mopro-ffi v0.1.0` and the GitHub main branch. To ensure you have the latest version, copy the UDL file from: https://github.com/zkmopro/mopro/blob/main/mopro-ffi/src/mopro.udl.
:::
- For instance, to define a function named `semaphore` with no inputs or return types:
```udl
// src/mopro.udl
namespace mopro {
// ...
void semaphore();
}
```
### 6-5 Update & Replace bindings
- Refer to [5-3 and 5-4](#5-3-Update-bindings)
:::info
You can verify if the new function's interface, such as `semaphore`, is included by checking the exported `mopro.swift` or `mopro.kt` files.
:::
### 6-6 Use the function in the app
- In swift
```swift
semaphore()
```
- In kotlin
```kotlin
import uniffi.mopro.semaphore
// ...
semaphore()
```
## 7. Conclusions
- By following the tutorial, you will learn to create a native mobile ZK app with:
1. A simple circuit
2. Custom circuits
3. Custom functions and structs
- Similar to the Semaphore case, it can be extended to any other Rust crate, provided you define the input and output types for serialization and deserialization.
- Unlike the JS/TS ecosystem, the Rust ecosystem for DApps is vastly different and remains largely underexplored. However, Rust's versatility makes it a powerful choice for building across platforms, including browsers via `wasm-bindgen`. Mopro is contributing to this space as well
- check out [mopro-wasm](https://github.com/zkmopro/mopro/tree/main/mopro-wasm).
- https://github.com/zkmopro/mopro/issues/202
- https://github.com/zkmopro/mopro/pull/268
- There are still many challenges to address, and contributions are highly encouraged. Feel free to explore the issues list.
- https://github.com/zkmopro/mopro/issues
- https://github.com/zkmopro/gpu-acceleration/issues
- ...
- We encourage developers to build mobile apps, as it helps us enhance the developer experience and gain a deeper understanding of the challenges involved.