Android Setup
After you've completed the Rust setup you should be able to run cargo run --bin android
. This will create a new folder called MoproAndroidBindings
. It should look like the structure
MoproAndroidBindings├── jniLibs│ ├── arm64-v8a│ │ └── libuniffi_mopro.so│ ├── armeabi-v7a│ │ └── libuniffi_mopro.so│ ├── x86│ │ └── libuniffi_mopro.so│ └── x86_64│ └── libuniffi_mopro.so└── uniffi └── mopro └── mopro.kt
First we will create an android app through Android Studio. If you already have an app project, you can skip this step. We'll do File -> New -> New Project and create an Empty Activity.
Your android project should be opened now.
Please make sure you choose the Android view like this.
Then add jna to app/build.gradle.kts
dependencies { ... implementation("net.java.dev.jna:jna:5.13.0@aar") ...}
Sync gradle with File -> Sync Project with Gradle Files, or press
Open Finder and drag folders:
- Move the
MoproAndroidBindings/jniLibs/
folder intoapp/src/main/jniLibs/
. - Move the
MoproAndroidBindings/uniffi/mopro/mopro.kts
file intoapp/src/main/java/uniffi/mopro/mopro.kt
Create an asset folder: File -> New -> Folder -> Assets Folder.
Paste the zkey in the assets folder.
Proving from the app
In your project, there should be a file named MainActivity.kt
It should be under
app/src/main/java/com/example/YOUR_APP/MainActivity.kt
Import the following functions:
import android.content.Contextimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.paddingimport androidx.compose.ui.unit.dpimport androidx.compose.material3.Buttonimport kotlinx.coroutines.launchimport uniffi.mopro.generateCircomProofimport java.io.Fileimport java.io.FileOutputStreamimport java.io.IOException
This will make the proving functions generateCircomProof
available in this module and also help to load zkey.
In the MainActivity.kt
, make your setContent
function look like this:
setContent { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { MainScreen(this) } }
Add a private function to load zkey. It is used to copy a file from the app's assets directory to the app's internal storage so that we can read the path of the zkey file.
private fun copyAssetToInternalStorage(context: Context, assetFileName: String): String? { val file = File(context.filesDir, assetFileName) return try { context.assets.open(assetFileName).use { inputStream -> FileOutputStream(file).use { outputStream -> val buffer = ByteArray(1024) var length: Int while (inputStream.read(buffer).also { length = it } > 0) { outputStream.write(buffer, 0, length) } outputStream.flush() } } file.absolutePath } catch (e: IOException) { e.printStackTrace() null }}
At the bottom of this file we'll create a view 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
.
@Composablefun MainScreen(context: Context) { val coroutineScope = rememberCoroutineScope() Column( modifier = Modifier .fillMaxSize() .padding(16.dp) ) { Button(onClick = { coroutineScope.launch { val assetFilePath = copyAssetToInternalStorage(context, "multiplier2_final.zkey") assetFilePath?.let { path -> val inputs = mutableMapOf<String, List<String>>() inputs["a"] = listOf("3") inputs["b"] = listOf("5") val res = generateCircomProof(path, inputs) println(res) } } }) { Text(text = "Generate Proof") } }}
Full MainActivity.kt
(simplified)
package com.example.moproandroidapp // Your application IDimport android.content.Contextimport android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.Buttonimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.runtime.rememberCoroutineScopeimport androidx.compose.ui.Modifierimport androidx.compose.ui.unit.dpimport java.io.Fileimport java.io.FileOutputStreamimport java.io.IOExceptionimport kotlinx.coroutines.launchimport uniffi.mopro.generateCircomProofclass MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { MainScreen(this) } } }}@Composablefun MainScreen(context: Context) { val coroutineScope = rememberCoroutineScope() Column(modifier = Modifier.fillMaxSize().padding(16.dp)) { Button( onClick = { coroutineScope.launch { val assetFilePath = copyAssetToInternalStorage(context, "multiplier2_final.zkey") assetFilePath?.let { path -> val inputs = mutableMapOf<String, List<String>>() inputs["a"] = listOf("3") inputs["b"] = listOf("5") val res = generateCircomProof(path, inputs) println(res) } } } ) { Text(text = "Generate Proof") } }}private fun copyAssetToInternalStorage(context: Context, assetFileName: String): String? { val file = File(context.filesDir, assetFileName) return try { context.assets.open(assetFileName).use { inputStream -> FileOutputStream(file).use { outputStream -> val buffer = ByteArray(1024) var length: Int while (inputStream.read(buffer).also { length = it } > 0) { outputStream.write(buffer, 0, length) } outputStream.flush() } } file.absolutePath } catch (e: IOException) { e.printStackTrace() null }}
You should now be able to run the Android app (^
+R
or ctrl
+R
) on the simulator or a device and build a proof. The app should log the proof.