Skip to main content
Version: Next

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.

create an android app project

Your android project should be opened now.

info

Please make sure you choose the Android view like this. android directory view

Then add jna to app/build.gradle.kts

dependencies {   ...   implementation("net.java.dev.jna:jna:5.13.0@aar")   ...}

add jna dependency

Sync gradle with File -> Sync Project with Gradle Files, or press

android sync gradle

Open Finder and drag folders:

  1. Move the MoproAndroidBindings/jniLibs/ folder into app/src/main/jniLibs/.
  2. Move the MoproAndroidBindings/uniffi/mopro/mopro.kts file into app/src/main/java/uniffi/mopro/mopro.kt

android bindings

Create an asset folder: File -> New -> Folder -> Assets Folder.
Paste the zkey in the assets folder.

android paste zkey

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.