# 2024 TBW Mopro Workshop - https://linktr.ee/zkmopro - ![mopro-linktree](https://hackmd.io/_uploads/HJig-HY4ye.png) [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.