diff --git a/app/App.tsx b/app/App.tsx index 812d1ed92..82b6d9da4 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -1,148 +1,36 @@ -import React, { useEffect, useState } from 'react'; -import { - DEFAULT_PNUMBER, - DEFAULT_DOB, - DEFAULT_DOE, - AMPLITUDE_KEY -} from '@env'; -import { PassportData } from '../common/src/utils/types'; -import { mockPassportData_sha256WithRSAEncryption_65537 } from '../common/src/utils/mockPassportData'; +import React, { useEffect } from 'react'; +import "react-native-get-random-values" import "@ethersproject/shims" -import { ethers } from "ethers"; import MainScreen from './src/screens/MainScreen'; -import { Steps } from './src/utils/utils'; -import { startCameraScan } from './src/utils/cameraScanner'; -import { scan } from './src/utils/nfcScanner'; -import { mint } from './src/utils/minter'; import { Buffer } from 'buffer'; import { YStack } from 'tamagui'; -import { prove } from './src/utils/prover'; import { useToastController } from '@tamagui/toast'; +import useNavigationStore from './src/stores/navigationStore'; +import { AMPLITUDE_KEY } from '@env'; import * as amplitude from '@amplitude/analytics-react-native'; -import { checkForZkey } from './src/utils/zkeyDownload'; +import useUserStore from './src/stores/userStore'; global.Buffer = Buffer; function App(): JSX.Element { - const [passportNumber, setPassportNumber] = useState(DEFAULT_PNUMBER ?? ""); - const [dateOfBirth, setDateOfBirth] = useState(DEFAULT_DOB ?? ''); - const [dateOfExpiry, setDateOfExpiry] = useState(DEFAULT_DOE ?? ''); - const [address, setAddress] = useState(ethers.ZeroAddress); - const [passportData, setPassportData] = useState(mockPassportData_sha256WithRSAEncryption_65537 as PassportData); - const [step, setStep] = useState(Steps.MRZ_SCAN); - const [generatingProof, setGeneratingProof] = useState(false); - const [proofTime, setProofTime] = useState(0); - const [proof, setProof] = useState<{ proof: string, inputs: string } | null>(null); - const [mintText, setMintText] = useState(""); - const [majority, setMajority] = useState(18); - const [zkeydownloadStatus, setDownloadStatus] = useState<"not_started" | "downloading" | "completed" | "error">("not_started"); - const [showWarning, setShowWarning] = useState(false); - - const [disclosure, setDisclosure] = useState({ - issuing_state: false, - name: false, - passport_number: false, - nationality: false, - date_of_birth: false, - gender: false, - expiry_date: false, - older_than: false, - }); - const toast = useToastController(); - - const handleDisclosureChange = (field: string) => { - setDisclosure( - { - ...disclosure, - [field]: !disclosure[field as keyof typeof disclosure] - }); - }; + const setToast = useNavigationStore((state) => state.setToast); + const initUserStore = useUserStore((state) => state.initUserStore); + + useEffect(() => { + setToast(toast); + }, [toast, setToast]); useEffect(() => { - checkForZkey({ - setDownloadStatus, - setShowWarning, - toast - }) amplitude.init(AMPLITUDE_KEY); + initUserStore(); }, []); - const handleStartCameraScan = async () => { - startCameraScan({ - setPassportNumber, - setDateOfBirth, - setDateOfExpiry, - setStep, - toast - }); - }; - - const handleNFCScan = () => { - scan({ - passportNumber, - dateOfBirth, - dateOfExpiry, - setPassportData, - setStep, - toast - }); - }; - - const handleProve = () => { - prove({ - passportData, - majority, - disclosure, - address, - setStep, - setGeneratingProof, - setProofTime, - setProof, - toast - }); - }; - - const handleMint = () => { - mint({ - proof, - setStep, - setMintText, - toast - }); - }; + // TODO: when passportData already stored, retrieve and jump to main screen return ( - + ); diff --git a/app/README.md b/app/README.md index a15bb5be3..e9531bccb 100644 --- a/app/README.md +++ b/app/README.md @@ -21,8 +21,6 @@ yarn ## Run the app -Let's run the app with the currently deployed zkey to mint the Proof of Passport SBT. - First, connect your phone to your computer and allow access. ### Android @@ -56,28 +54,24 @@ If you want to modify the circuits, you'll have to adapt a few things. First, go to the `circuit` folder of the monorepo, modify the circuits and build them. -Then, upload the zkey and the arkzkey built at a publicly available url and replace the two urls in `common/src/constants/constants.ts`. Be sure they have the name you put as ZKEY_NAME before you zip them, because they will need to unzip to theses name. +Then, upload the zipped zkeys built at publicly available urls and replace the urls in `app/src/utils/zkeyDownload.ts`. Be sure the zkey is named `.zkey` before you zip it, and the zip is then named `.zkey.zip`. -Adapt the inputs you pass in `app/src/utils/prover.ts` and if you want to mint SBTs, adapt and redeploy the contracts. +Adapt the inputs you pass in `app/src/utils/prover.ts`, and adapt and redeploy the contracts. + +Run the common init script: +``` +./scripts/common.sh +``` ### Android +Find your android ndk path. It should be something like `/Users//Library/Android/sdk/ndk/23.1.7779620` Build the android native module: ``` +export ANDROID_NDK="" ./scripts/build_android_module.sh ``` -You might need to set the rust-toolchain rust version as global default. Example: -``` -rustup default 1.67.0 -``` - -For macOS users you might also need to set-up the path to sdk: -in `/app/android` create `local.properties` - -Add the following line: -`sdk.dir=/Users//Library/Android/sdk` or any relevant path to your sdk - ### iOS Find your [development team id](https://chat.openai.com/share/9d52c37f-d9da-4a62-acb9-9e4ee8179f95) and run: diff --git a/app/android/android-passport-reader/app/src/main/AndroidManifest.xml b/app/android/android-passport-reader/app/src/main/AndroidManifest.xml index 33cc79aee..98cf281e9 100644 --- a/app/android/android-passport-reader/app/src/main/AndroidManifest.xml +++ b/app/android/android-passport-reader/app/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ + + @@ -9,6 +13,7 @@ +#include + +using namespace std; + +extern "C" +JNIEXPORT jint JNICALL +Java_com_proofofpassport_prover_ZKPTools_groth16_1prover(JNIEnv *env, jobject thiz, + jbyteArray zkey_buffer, jlong zkey_size, + jbyteArray wtns_buffer, jlong wtns_size, + jbyteArray proof_buffer, jlongArray proof_size, + jbyteArray public_buffer, + jlongArray public_size, jbyteArray error_msg, + jlong error_msg_max_size) { + const void *zkeyBuffer = env->GetByteArrayElements(zkey_buffer, nullptr); + const void *wtnsBuffer = env->GetByteArrayElements(wtns_buffer, nullptr); + char *proofBuffer = reinterpret_cast(env->GetByteArrayElements(proof_buffer, + nullptr)); + char *publicBuffer = reinterpret_cast(env->GetByteArrayElements(public_buffer, + nullptr)); + char *errorMsg = reinterpret_cast(env->GetByteArrayElements(error_msg, nullptr)); + + unsigned long proofSize = env->GetLongArrayElements(proof_size, nullptr)[0]; + unsigned long publicSize = env->GetLongArrayElements(public_size, nullptr)[0]; + + int result = groth16_prover(zkeyBuffer, static_cast(zkey_size), + wtnsBuffer, static_cast(wtns_size), + proofBuffer, &proofSize, + publicBuffer, &publicSize, + errorMsg, static_cast(error_msg_max_size)); + + env->SetLongArrayRegion(proof_size, 0, 1, reinterpret_cast(&proofSize)); + env->SetLongArrayRegion(public_size, 0, 1, reinterpret_cast(&publicSize)); + + env->ReleaseByteArrayElements(zkey_buffer, + reinterpret_cast(const_cast(zkeyBuffer)), 0); + env->ReleaseByteArrayElements(wtns_buffer, + reinterpret_cast(const_cast(wtnsBuffer)), 0); + env->ReleaseByteArrayElements(proof_buffer, reinterpret_cast(proofBuffer), 0); + env->ReleaseByteArrayElements(public_buffer, reinterpret_cast(publicBuffer), 0); + env->ReleaseByteArrayElements(error_msg, reinterpret_cast(errorMsg), 0); + + return result; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_proofofpassport_prover_ZKPTools_witnesscalc_1proof_1of_1passport(JNIEnv *env, jobject thiz, + jbyteArray circuit_buffer, + jlong circuit_size, jbyteArray json_buffer, + jlong json_size, jbyteArray wtns_buffer, + jlongArray wtns_size, jbyteArray error_msg, + jlong error_msg_max_size) { + const char *circuitBuffer = reinterpret_cast(env->GetByteArrayElements( + circuit_buffer, nullptr)); + const char *jsonBuffer = reinterpret_cast(env->GetByteArrayElements(json_buffer, + nullptr)); + char *wtnsBuffer = reinterpret_cast(env->GetByteArrayElements(wtns_buffer, nullptr)); + char *errorMsg = reinterpret_cast(env->GetByteArrayElements(error_msg, nullptr)); + + unsigned long wtnsSize = env->GetLongArrayElements(wtns_size, nullptr)[0]; + + + int result = witnesscalc_proof_of_passport( + circuitBuffer, static_cast(circuit_size), + jsonBuffer, static_cast(json_size), + wtnsBuffer, &wtnsSize, + errorMsg, static_cast(error_msg_max_size)); + + // Set the result and release the resources + env->SetLongArrayRegion(wtns_size, 0, 1, reinterpret_cast(&wtnsSize)); + + env->ReleaseByteArrayElements(circuit_buffer, + reinterpret_cast(const_cast(circuitBuffer)), 0); + env->ReleaseByteArrayElements(json_buffer, + reinterpret_cast(const_cast(jsonBuffer)), 0); + env->ReleaseByteArrayElements(wtns_buffer, reinterpret_cast(wtnsBuffer), 0); + env->ReleaseByteArrayElements(error_msg, reinterpret_cast(errorMsg), 0); + + return result; +} + +extern "C" +JNIEXPORT jint JNICALL Java_com_proofofpassport_prover_ZKPTools_groth16_1prover_1zkey_1file( + JNIEnv *env, jobject obj, + jstring zkeyPath, + jbyteArray wtnsBuffer, jlong wtnsSize, + jbyteArray proofBuffer, jlongArray proofSize, + jbyteArray publicBuffer, jlongArray publicSize, + jbyteArray errorMsg, jlong errorMsgMaxSize +) { + // Convert jbyteArray to native types + const char *nativeZkeyPath = env->GetStringUTFChars(zkeyPath, nullptr); + + void *nativeWtnsBuffer = env->GetByteArrayElements(wtnsBuffer, nullptr); + + char *nativeProofBuffer = (char *) env->GetByteArrayElements(proofBuffer, nullptr); + char *nativePublicBuffer = (char *) env->GetByteArrayElements(publicBuffer, nullptr); + char *nativeErrorMsg = (char *) env->GetByteArrayElements(errorMsg, nullptr); + + jlong *nativeProofSizeArr = env->GetLongArrayElements(proofSize, 0); + jlong *nativePublicSizeArr = env->GetLongArrayElements(publicSize, 0); + + unsigned long nativeProofSize = nativeProofSizeArr[0]; + unsigned long nativePublicSize = nativePublicSizeArr[0]; + + // Call the groth16_prover function` + int status_code = groth16_prover_zkey_file( + nativeZkeyPath, + nativeWtnsBuffer, wtnsSize, + nativeProofBuffer, &nativeProofSize, + nativePublicBuffer, &nativePublicSize, + nativeErrorMsg, errorMsgMaxSize + ); + + // Convert the results back to JNI types + nativeProofSizeArr[0] = nativeProofSize; + nativePublicSizeArr[0] = nativePublicSize; + + env->SetLongArrayRegion(proofSize, 0, 1, (jlong *) nativeProofSizeArr); + env->SetLongArrayRegion(publicSize, 0, 1, (jlong *) nativePublicSizeArr); + + // Release the native buffers + env->ReleaseByteArrayElements(wtnsBuffer, (jbyte *) nativeWtnsBuffer, 0); + env->ReleaseByteArrayElements(proofBuffer, (jbyte *) nativeProofBuffer, 0); + env->ReleaseByteArrayElements(publicBuffer, (jbyte *) nativePublicBuffer, 0); + env->ReleaseByteArrayElements(errorMsg, (jbyte *) nativeErrorMsg, 0); + + env->ReleaseLongArrayElements(proofSize, (jlong *) nativeProofSizeArr, 0); + env->ReleaseLongArrayElements(publicSize, (jlong *) nativePublicSizeArr, 0); + + return status_code; +} \ No newline at end of file diff --git a/app/android/app/src/main/java/com/awesomeproject/ProverModule.kt b/app/android/app/src/main/java/com/awesomeproject/ProverModule.kt index 2cd4c67ed..190acf1a9 100644 --- a/app/android/app/src/main/java/com/awesomeproject/ProverModule.kt +++ b/app/android/app/src/main/java/com/awesomeproject/ProverModule.kt @@ -6,51 +6,55 @@ import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.Promise import android.util.Log +import android.content.Context +import java.io.ByteArrayOutputStream import com.facebook.react.bridge.ReadableMap -import uniffi.mopro.GenerateProofResult import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import com.google.gson.Gson +import com.google.gson.GsonBuilder + +import com.proofofpassport.R + class ProverModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private val TAG = "ProverModule" - lateinit var res: GenerateProofResult - override fun getName(): String { return "Prover" } @ReactMethod - fun runInitAction(promise: Promise) { - // Launch a coroutine in the IO dispatcher for background tasks - CoroutineScope(Dispatchers.IO).launch { - try { - val startTime = System.currentTimeMillis() - uniffi.mopro.initializeMopro() - val endTime = System.currentTimeMillis() - val initTime = "init time: " + (endTime - startTime).toString() + " ms" - - // Since the promise needs to be resolved in the main thread - withContext(Dispatchers.Main) { - promise.resolve(initTime) - } - } catch (e: Exception) { - withContext(Dispatchers.Main) { - promise.reject(e) - } - } - } - } - - @ReactMethod - // fun runProveAction(inputs: ReadableMap, zkeypath: String, promise: Promise) { - fun runProveAction(inputs: ReadableMap, promise: Promise) { + fun runProveAction(zkey_path: String, witness_calculator: String, dat_file_name: String, inputs: ReadableMap, promise: Promise) { + Log.e(TAG, "zkey_path in provePassport kotlin: " + zkey_path) + Log.e(TAG, "witness_calculator in provePassport kotlin: " + witness_calculator) + Log.e(TAG, "dat_file_name in provePassport kotlin: " + dat_file_name) Log.e(TAG, "inputs in provePassport kotlin: " + inputs.toString()) - // working example + val formattedInputs = mutableMapOf( + "mrz" to inputs.getArray("mrz")?.toArrayList()?.map { it.toString() }, + "reveal_bitmap" to inputs.getArray("reveal_bitmap")?.toArrayList()?.map { it.toString() }, + "dataHashes" to inputs.getArray("dataHashes")?.toArrayList()?.map { it.toString() }, + "datahashes_padded_length" to inputs.getArray("datahashes_padded_length")?.toArrayList()?.map { it.toString() }?.firstOrNull(), + "eContentBytes" to inputs.getArray("eContentBytes")?.toArrayList()?.map { it.toString() }, + "signature" to inputs.getArray("signature")?.toArrayList()?.map { it.toString() }, + "signatureAlgorithm" to inputs.getArray("signatureAlgorithm")?.toArrayList()?.map { it.toString() }?.firstOrNull(), + "pubkey" to inputs.getArray("pubkey")?.toArrayList()?.map { it.toString() }, + "pathIndices" to inputs.getArray("pathIndices")?.toArrayList()?.map { it.toString() }, + "siblings" to inputs.getArray("siblings")?.toArrayList()?.map { it.toString() }, + "root" to inputs.getArray("root")?.toArrayList()?.map { it.toString() }?.firstOrNull(), + "address" to inputs.getArray("address")?.toArrayList()?.map { it.toString() }?.firstOrNull(), + "current_date" to inputs.getArray("current_date")?.toArrayList()?.map { it.toString() }, + "majority" to inputs.getArray("majority")?.toArrayList()?.map { it.toString() }, + ) + + val gson = GsonBuilder().setPrettyPrinting().create() + Log.e(TAG, gson.toJson(formattedInputs)) + + // (used to be) working example // val inputs = mutableMapOf>( // "mrz" to listOf("97","91","95","31","88","80","60","70","82","65","84","65","86","69","82","78","73","69","82","60","60","70","76","79","82","69","78","84","60","72","85","71","85","69","83","60","74","69","65","78","60","60","60","60","60","60","60","60","60","49","57","72","65","51","52","56","50","56","52","70","82","65","48","48","48","55","49","57","49","77","50","57","49","50","48","57","53","60","60","60","60","60","60","60","60","60","60","60","60","60","60","48","50"), // "reveal_bitmap" to listOf("0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"), @@ -66,35 +70,239 @@ class ProverModule(reactContext: ReactApplicationContext) : ReactContextBaseJava // "address" to listOf("642829559307850963015472508762062935916233390536") // ) - val convertedInputs = mutableMapOf>() - for ((key, value) in inputs.toHashMap()) { - val parsedArray = inputs.getArray(key)?.toArrayList()?.map { item -> - item.toString() - } ?: emptyList() - convertedInputs[key] = parsedArray + val jsonInputs = gson.toJson(formattedInputs).toByteArray() + val zkpTools = ZKPTools(reactApplicationContext) + + val witnessCalcFunction = when (witness_calculator) { + "proof_of_passport" -> zkpTools::witnesscalc_proof_of_passport + // "another_calculator" -> zkpTools::witnesscalc_another_calculator + else -> throw IllegalArgumentException("Invalid witness calculator name") + } + + // Get the resource ID dynamically + val resId = reactApplicationContext.resources.getIdentifier(dat_file_name, "raw", reactApplicationContext.packageName) + if (resId == 0) { + throw IllegalArgumentException("Invalid dat file name") } - Log.e(TAG, "convertedInputs: $convertedInputs") + val zkp: ZkProof = ZKPUseCase(reactApplicationContext).generateZKP( + zkey_path, + resId, + jsonInputs, + witnessCalcFunction + ) - val startTime = System.currentTimeMillis() - res = uniffi.mopro.generateProof2(convertedInputs) - val endTime = System.currentTimeMillis() - val provingTime = "proving time: " + (endTime - startTime).toString() + " ms" - Log.e(TAG, provingTime) - - Log.e(TAG, "res: " + res.toString()) + Log.e("ZKP", gson.toJson(zkp)) - promise.resolve(res.toString()) - } - - @ReactMethod - fun runVerifyAction(promise: Promise) { - val startTime = System.currentTimeMillis() - val valid = "valid: " + uniffi.mopro.verifyProof2(res.proof, res.inputs).toString() - val endTime = System.currentTimeMillis() - val verifyingTime = "verifying time: " + (endTime - startTime).toString() + " ms" - Log.e(TAG, verifyingTime) - promise.resolve(valid) + promise.resolve(zkp.toString()) } } + + +data class Proof( + val pi_a: List, + val pi_b: List>, + val pi_c: List, + val protocol: String, + var curve: String = "bn128" +) { + companion object { + fun fromJson(jsonString: String): Proof { + Log.d("Proof", jsonString) + val json = Gson().fromJson(jsonString, Proof::class.java) + json.curve = getDefaultCurve() + return json + } + + private fun getDefaultCurve(): String { + return "bn128" + } + } +} + +data class ZkProof( + val proof: Proof, + val pub_signals: List +) + +class ZKPTools(val context: Context) { + external fun witnesscalc_proof_of_passport(circuitBuffer: ByteArray, + circuitSize: Long, + jsonBuffer: ByteArray, + jsonSize: Long, + wtnsBuffer: ByteArray, + wtnsSize: LongArray, + errorMsg: ByteArray, + errorMsgMaxSize: Long): Int + external fun groth16_prover( + zkeyBuffer: ByteArray, zkeySize: Long, + wtnsBuffer: ByteArray, wtnsSize: Long, + proofBuffer: ByteArray, proofSize: LongArray, + publicBuffer: ByteArray, publicSize: LongArray, + errorMsg: ByteArray, errorMsgMaxSize: Long + ): Int + external fun groth16_prover_zkey_file( + zkeyPath: String, + wtnsBuffer: ByteArray, wtnsSize: Long, + proofBuffer: ByteArray, proofSize: LongArray, + publicBuffer: ByteArray, publicSize: LongArray, + errorMsg: ByteArray, errorMsgMaxSize: Long + ): Int + + init { + System.loadLibrary("rapidsnark"); + System.loadLibrary("proofofpassport") + } + + fun openRawResourceAsByteArray(resourceName: Int): ByteArray { + val inputStream = context.resources.openRawResource(resourceName) + val byteArrayOutputStream = ByteArrayOutputStream() + + try { + val buffer = ByteArray(1024) + var length: Int + + while (inputStream.read(buffer).also { length = it } != -1) { + byteArrayOutputStream.write(buffer, 0, length) + } + + return byteArrayOutputStream.toByteArray() + } finally { + byteArrayOutputStream.close() + inputStream.close() + } + } +} + +class ZKPUseCase(val context: Context) { + + fun generateZKP( + zkey_path: String, + datId: Int, + inputs: ByteArray, + proofFunction: ( + circuitBuffer: ByteArray, + circuitSize: Long, + jsonBuffer: ByteArray, + jsonSize: Long, + wtnsBuffer: ByteArray, + wtnsSize: LongArray, + errorMsg: ByteArray, + errorMsgMaxSize: Long + ) -> Int + ): ZkProof { + val zkpTool = ZKPTools(context) + val datFile = zkpTool.openRawResourceAsByteArray(datId) + + val msg = ByteArray(256) + + val witnessLen = LongArray(1) + witnessLen[0] = 100 * 1024 * 1024 + + val byteArr = ByteArray(100 * 1024 * 1024) + + val res = proofFunction( + datFile, + datFile.size.toLong(), + inputs, + inputs.size.toLong(), + byteArr, + witnessLen, + msg, + 256 + ) + + Log.e("ZKPUseCase", "Witness gen res: $res") + Log.e("ZKPUseCase", "Witness gen return length: ${byteArr.size}") + + if (res == 3) { + throw Exception("Error 3") + } + + if (res == 2) { + throw Exception("Not enough memory for zkp") + } + + if (res == 1) { + throw Exception("Error during zkp ${msg.decodeToString()}") + } + + val pubData = ByteArray(4 *1024 *1024) + val pubLen = LongArray(1) + pubLen[0] = pubData.size.toLong() + + val proofData = ByteArray(4*1024*1024) + val proofLen = LongArray(1) + proofLen[0] = proofData.size.toLong() + + val witnessData = byteArr.copyOfRange(0, witnessLen[0].toInt()) + + Log.e("ZKPUseCase", "zkey_path: $zkey_path") + + val verification = zkpTool.groth16_prover_zkey_file( + zkey_path, + witnessData, + witnessLen[0], + proofData, + proofLen, + pubData, + pubLen, + msg, + 256 + ) + + Log.e("ZKPUseCase", "Verification res: $verification") + + if (verification == 2) { + throw Exception("Not enough memory for verification ${msg.decodeToString()}") + } + + if (verification == 1) { + throw Exception("Error during verification ${msg.decodeToString()}") + } + + val proofDataZip = proofData.copyOfRange(0, proofLen[0].toInt()) + + val index = findLastIndexOfSubstring( + proofDataZip.toString(Charsets.UTF_8), + "\"protocol\":\"groth16\"}" + ) + val indexPubData = findLastIndexOfSubstring( + pubData.decodeToString(), + "]" + ) + + val formattedPubData = pubData.decodeToString().slice(0..indexPubData) + + val formattedProof = proofDataZip.toString(Charsets.UTF_8).slice(0..index) + + Log.e("ZKPUseCase", "formattedProof: $formattedProof") + + val proof = Proof.fromJson(formattedProof) + + Log.e("ZKPUseCase", "Proof: $proof") + + return ZkProof( + proof = proof, + pub_signals = getPubSignals(formattedPubData).toList() + ) + } + + private fun findLastIndexOfSubstring(mainString: String, searchString: String): Int { + val index = mainString.lastIndexOf(searchString) + + if (index != -1) { + // If substring is found, calculate the last index of the substring + return index + searchString.length - 1 + } + + return -1 + } + + private fun getPubSignals(jsonString: String): List { + val gson = Gson() + val stringArray = gson.fromJson(jsonString, Array::class.java) + return stringArray.toList() + } +} diff --git a/app/android/app/src/main/java/uniffi/mopro/mopro.kt b/app/android/app/src/main/java/uniffi/mopro/mopro.kt deleted file mode 100644 index 655f6dfcd..000000000 --- a/app/android/app/src/main/java/uniffi/mopro/mopro.kt +++ /dev/null @@ -1,1172 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -@file:Suppress("NAME_SHADOWING") - -package uniffi.mopro; - -// Common helper code. -// -// Ideally this would live in a separate .kt file where it can be unittested etc -// in isolation, and perhaps even published as a re-useable package. -// -// However, it's important that the details of how this helper code works (e.g. the -// way that different builtin types are passed across the FFI) exactly match what's -// expected by the Rust code on the other side of the interface. In practice right -// now that means coming from the exact some version of `uniffi` that was used to -// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin -// helpers directly inline like we're doing here. - -import com.sun.jna.Library -import com.sun.jna.IntegerType -import com.sun.jna.Native -import com.sun.jna.Pointer -import com.sun.jna.Structure -import com.sun.jna.Callback -import com.sun.jna.ptr.* -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.nio.CharBuffer -import java.nio.charset.CodingErrorAction -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicLong - -// This is a helper for safely working with byte buffers returned from the Rust code. -// A rust-owned buffer is represented by its capacity, its current length, and a -// pointer to the underlying data. - -@Structure.FieldOrder("capacity", "len", "data") -open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue: RustBuffer(), Structure.ByValue - class ByReference: RustBuffer(), Structure.ByReference - - companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_mopro_ffi_rustbuffer_alloc(size, status) - }.also { - if(it.data == null) { - throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") - } - } - - internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue { - var buf = RustBuffer.ByValue() - buf.capacity = capacity - buf.len = len - buf.data = data - return buf - } - - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_mopro_ffi_rustbuffer_free(buf, status) - } - } - - @Suppress("TooGenericExceptionThrown") - fun asByteBuffer() = - this.data?.getByteBuffer(0, this.len.toLong())?.also { - it.order(ByteOrder.BIG_ENDIAN) - } -} - -/** - * The equivalent of the `*mut RustBuffer` type. - * Required for callbacks taking in an out pointer. - * - * Size is the sum of all values in the struct. - */ -class RustBufferByReference : ByReference(16) { - /** - * Set the pointed-to `RustBuffer` to the given value. - */ - fun setValue(value: RustBuffer.ByValue) { - // NOTE: The offsets are as they are in the C-like struct. - val pointer = getPointer() - pointer.setInt(0, value.capacity) - pointer.setInt(4, value.len) - pointer.setPointer(8, value.data) - } - - /** - * Get a `RustBuffer.ByValue` from this reference. - */ - fun getValue(): RustBuffer.ByValue { - val pointer = getPointer() - val value = RustBuffer.ByValue() - value.writeField("capacity", pointer.getInt(0)) - value.writeField("len", pointer.getInt(4)) - value.writeField("data", pointer.getPointer(8)) - - return value - } -} - -// This is a helper for safely passing byte references into the rust code. -// It's not actually used at the moment, because there aren't many things that you -// can take a direct pointer to in the JVM, and if we're going to copy something -// then we might as well copy it into a `RustBuffer`. But it's here for API -// completeness. - -@Structure.FieldOrder("len", "data") -open class ForeignBytes : Structure() { - @JvmField var len: Int = 0 - @JvmField var data: Pointer? = null - - class ByValue : ForeignBytes(), Structure.ByValue -} -// The FfiConverter interface handles converter types to and from the FFI -// -// All implementing objects should be public to support external types. When a -// type is external we need to import it's FfiConverter. -public interface FfiConverter { - // Convert an FFI type to a Kotlin type - fun lift(value: FfiType): KotlinType - - // Convert an Kotlin type to an FFI type - fun lower(value: KotlinType): FfiType - - // Read a Kotlin type from a `ByteBuffer` - fun read(buf: ByteBuffer): KotlinType - - // Calculate bytes to allocate when creating a `RustBuffer` - // - // This must return at least as many bytes as the write() function will - // write. It can return more bytes than needed, for example when writing - // Strings we can't know the exact bytes needed until we the UTF-8 - // encoding, so we pessimistically allocate the largest size possible (3 - // bytes per codepoint). Allocating extra bytes is not really a big deal - // because the `RustBuffer` is short-lived. - fun allocationSize(value: KotlinType): Int - - // Write a Kotlin type to a `ByteBuffer` - fun write(value: KotlinType, buf: ByteBuffer) - - // Lower a value into a `RustBuffer` - // - // This method lowers a value into a `RustBuffer` rather than the normal - // FfiType. It's used by the callback interface code. Callback interface - // returns are always serialized into a `RustBuffer` regardless of their - // normal FFI type. - fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { - val rbuf = RustBuffer.alloc(allocationSize(value)) - try { - val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also { - it.order(ByteOrder.BIG_ENDIAN) - } - write(value, bbuf) - rbuf.writeField("len", bbuf.position()) - return rbuf - } catch (e: Throwable) { - RustBuffer.free(rbuf) - throw e - } - } - - // Lift a value from a `RustBuffer`. - // - // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. - // It's currently only used by the `FfiConverterRustBuffer` class below. - fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { - val byteBuf = rbuf.asByteBuffer()!! - try { - val item = read(byteBuf) - if (byteBuf.hasRemaining()) { - throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") - } - return item - } finally { - RustBuffer.free(rbuf) - } - } -} - -// FfiConverter that uses `RustBuffer` as the FfiType -public interface FfiConverterRustBuffer: FfiConverter { - override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) - override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) -} -// A handful of classes and functions to support the generated data structures. -// This would be a good candidate for isolating in its own ffi-support lib. -// Error runtime. -@Structure.FieldOrder("code", "error_buf") -internal open class RustCallStatus : Structure() { - @JvmField var code: Byte = 0 - @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - - class ByValue: RustCallStatus(), Structure.ByValue - - fun isSuccess(): Boolean { - return code == 0.toByte() - } - - fun isError(): Boolean { - return code == 1.toByte() - } - - fun isPanic(): Boolean { - return code == 2.toByte() - } -} - -class InternalException(message: String) : Exception(message) - -// Each top-level error class has a companion object that can lift the error from the call status's rust buffer -interface CallStatusErrorHandler { - fun lift(error_buf: RustBuffer.ByValue): E; -} - -// Helpers for calling Rust -// In practice we usually need to be synchronized to call this safely, so it doesn't -// synchronize itself - -// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err -private inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); - val return_value = callback(status) - checkCallStatus(errorHandler, status) - return return_value -} - -// Check RustCallStatus and throw an error if the call wasn't successful -private fun checkCallStatus(errorHandler: CallStatusErrorHandler, status: RustCallStatus) { - if (status.isSuccess()) { - return - } else if (status.isError()) { - throw errorHandler.lift(status.error_buf) - } else if (status.isPanic()) { - // when the rust code sees a panic, it tries to construct a rustbuffer - // with the message. but if that code panics, then it just sends back - // an empty buffer. - if (status.error_buf.len > 0) { - throw InternalException(FfiConverterString.lift(status.error_buf)) - } else { - throw InternalException("Rust panic") - } - } else { - throw InternalException("Unknown rust call status: $status.code") - } -} - -// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR -object NullCallStatusErrorHandler: CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): InternalException { - RustBuffer.free(error_buf) - return InternalException("Unexpected CALL_ERROR") - } -} - -// Call a rust function that returns a plain value -private inline fun rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); -} - -// IntegerType that matches Rust's `usize` / C's `size_t` -public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) { - // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin. - override fun toByte() = toInt().toByte() - // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed. - @Deprecated("`toInt().toChar()` is deprecated") - override fun toChar() = toInt().toChar() - override fun toShort() = toInt().toShort() - - fun writeToBuffer(buf: ByteBuffer) { - // Make sure we always write usize integers using native byte-order, since they may be - // casted to pointer values - buf.order(ByteOrder.nativeOrder()) - try { - when (Native.SIZE_T_SIZE) { - 4 -> buf.putInt(toInt()) - 8 -> buf.putLong(toLong()) - else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") - } - } finally { - buf.order(ByteOrder.BIG_ENDIAN) - } - } - - companion object { - val size: Int - get() = Native.SIZE_T_SIZE - - fun readFromBuffer(buf: ByteBuffer) : USize { - // Make sure we always read usize integers using native byte-order, since they may be - // casted from pointer values - buf.order(ByteOrder.nativeOrder()) - try { - return when (Native.SIZE_T_SIZE) { - 4 -> USize(buf.getInt().toLong()) - 8 -> USize(buf.getLong()) - else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") - } - } finally { - buf.order(ByteOrder.BIG_ENDIAN) - } - } - } -} - - -// Map handles to objects -// -// This is used when the Rust code expects an opaque pointer to represent some foreign object. -// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an -// object reference , nor does it support leaking a reference to Rust. -// -// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to -// Rust when it needs an opaque pointer. -// -// TODO: refactor callbacks to use this class -internal class UniFfiHandleMap { - private val map = ConcurrentHashMap() - // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible - // values seems like enough. If somehow we generate 4 billion handles, then this will wrap - // around back to zero and we can assume the first handle generated will have been dropped by - // then. - private val counter = java.util.concurrent.atomic.AtomicInteger(0) - - val size: Int - get() = map.size - - fun insert(obj: T): USize { - val handle = USize(counter.getAndAdd(1).toLong()) - map.put(handle, obj) - return handle - } - - fun get(handle: USize): T? { - return map.get(handle) - } - - fun remove(handle: USize): T? { - return map.remove(handle) - } -} - -// FFI type for Rust future continuations -internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback { - fun callback(continuationHandle: USize, pollResult: Short); -} - -// Contains loading, initialization code, -// and the FFI Function declarations in a com.sun.jna.Library. -@Synchronized -private fun findLibraryName(componentName: String): String { - val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") - if (libOverride != null) { - return libOverride - } - return "uniffi_mopro" -} - -private inline fun loadIndirect( - componentName: String -): Lib { - return Native.load(findLibraryName(componentName), Lib::class.java) -} - -// A JNA Library to expose the extern-C FFI definitions. -// This is an implementation detail which will be called internally by the public API. - -internal interface _UniFFILib : Library { - companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "mopro") - .also { lib: _UniFFILib -> - uniffiCheckContractApiVersion(lib) - uniffiCheckApiChecksums(lib) - } - } - } - - fun uniffi_mopro_ffi_fn_free_moprocircom(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, - ): Unit - fun uniffi_mopro_ffi_fn_constructor_moprocircom_new(_uniffi_out_err: RustCallStatus, - ): Pointer - fun uniffi_mopro_ffi_fn_method_moprocircom_generate_proof(`ptr`: Pointer,`circuitInputs`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, - ): RustBuffer.ByValue - fun uniffi_mopro_ffi_fn_method_moprocircom_setup(`ptr`: Pointer,`wasmPath`: RustBuffer.ByValue,`r1csPath`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, - ): RustBuffer.ByValue - fun uniffi_mopro_ffi_fn_method_moprocircom_verify_proof(`ptr`: Pointer,`proof`: RustBuffer.ByValue,`publicInput`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, - ): Byte - fun uniffi_mopro_ffi_fn_func_add(`a`: Int,`b`: Int,_uniffi_out_err: RustCallStatus, - ): Int - fun uniffi_mopro_ffi_fn_func_generate_proof2(`circuitInputs`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, - ): RustBuffer.ByValue - fun uniffi_mopro_ffi_fn_func_hello(_uniffi_out_err: RustCallStatus, - ): RustBuffer.ByValue - fun uniffi_mopro_ffi_fn_func_initialize_mopro(_uniffi_out_err: RustCallStatus, - ): Unit - fun uniffi_mopro_ffi_fn_func_initialize_mopro_dylib(`dylibPath`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, - ): Unit - fun uniffi_mopro_ffi_fn_func_verify_proof2(`proof`: RustBuffer.ByValue,`publicInput`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, - ): Byte - fun ffi_mopro_ffi_rustbuffer_alloc(`size`: Int,_uniffi_out_err: RustCallStatus, - ): RustBuffer.ByValue - fun ffi_mopro_ffi_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue,_uniffi_out_err: RustCallStatus, - ): RustBuffer.ByValue - fun ffi_mopro_ffi_rustbuffer_free(`buf`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, - ): Unit - fun ffi_mopro_ffi_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Int,_uniffi_out_err: RustCallStatus, - ): RustBuffer.ByValue - fun ffi_mopro_ffi_rust_future_continuation_callback_set(`callback`: UniFffiRustFutureContinuationCallbackType, - ): Unit - fun ffi_mopro_ffi_rust_future_poll_u8(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_u8(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_u8(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_u8(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Byte - fun ffi_mopro_ffi_rust_future_poll_i8(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_i8(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_i8(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_i8(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Byte - fun ffi_mopro_ffi_rust_future_poll_u16(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_u16(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_u16(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_u16(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Short - fun ffi_mopro_ffi_rust_future_poll_i16(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_i16(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_i16(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_i16(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Short - fun ffi_mopro_ffi_rust_future_poll_u32(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_u32(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_u32(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_u32(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Int - fun ffi_mopro_ffi_rust_future_poll_i32(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_i32(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_i32(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_i32(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Int - fun ffi_mopro_ffi_rust_future_poll_u64(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_u64(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_u64(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_u64(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Long - fun ffi_mopro_ffi_rust_future_poll_i64(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_i64(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_i64(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_i64(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Long - fun ffi_mopro_ffi_rust_future_poll_f32(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_f32(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_f32(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_f32(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Float - fun ffi_mopro_ffi_rust_future_poll_f64(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_f64(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_f64(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_f64(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Double - fun ffi_mopro_ffi_rust_future_poll_pointer(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_pointer(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_pointer(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_pointer(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Pointer - fun ffi_mopro_ffi_rust_future_poll_rust_buffer(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_rust_buffer(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_rust_buffer(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_rust_buffer(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): RustBuffer.ByValue - fun ffi_mopro_ffi_rust_future_poll_void(`handle`: Pointer,`uniffiCallback`: USize, - ): Unit - fun ffi_mopro_ffi_rust_future_cancel_void(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_free_void(`handle`: Pointer, - ): Unit - fun ffi_mopro_ffi_rust_future_complete_void(`handle`: Pointer,_uniffi_out_err: RustCallStatus, - ): Unit - fun uniffi_mopro_ffi_checksum_func_add( - ): Short - fun uniffi_mopro_ffi_checksum_func_generate_proof2( - ): Short - fun uniffi_mopro_ffi_checksum_func_hello( - ): Short - fun uniffi_mopro_ffi_checksum_func_initialize_mopro( - ): Short - fun uniffi_mopro_ffi_checksum_func_initialize_mopro_dylib( - ): Short - fun uniffi_mopro_ffi_checksum_func_verify_proof2( - ): Short - fun uniffi_mopro_ffi_checksum_method_moprocircom_generate_proof( - ): Short - fun uniffi_mopro_ffi_checksum_method_moprocircom_setup( - ): Short - fun uniffi_mopro_ffi_checksum_method_moprocircom_verify_proof( - ): Short - fun uniffi_mopro_ffi_checksum_constructor_moprocircom_new( - ): Short - fun ffi_mopro_ffi_uniffi_contract_version( - ): Int - -} - -private fun uniffiCheckContractApiVersion(lib: _UniFFILib) { - // Get the bindings contract version from our ComponentInterface - val bindings_contract_version = 24 - // Get the scaffolding contract version by calling the into the dylib - val scaffolding_contract_version = lib.ffi_mopro_ffi_uniffi_contract_version() - if (bindings_contract_version != scaffolding_contract_version) { - throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") - } -} - -@Suppress("UNUSED_PARAMETER") -private fun uniffiCheckApiChecksums(lib: _UniFFILib) { - if (lib.uniffi_mopro_ffi_checksum_func_add() != 8411.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_func_generate_proof2() != 40187.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_func_hello() != 46136.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_func_initialize_mopro() != 17540.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_func_initialize_mopro_dylib() != 64476.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_func_verify_proof2() != 37192.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_method_moprocircom_generate_proof() != 64602.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_method_moprocircom_setup() != 57700.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_method_moprocircom_verify_proof() != 61522.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } - if (lib.uniffi_mopro_ffi_checksum_constructor_moprocircom_new() != 42205.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } -} - -// Async support - -// Public interface members begin here. - - -public object FfiConverterUInt: FfiConverter { - override fun lift(value: Int): UInt { - return value.toUInt() - } - - override fun read(buf: ByteBuffer): UInt { - return lift(buf.getInt()) - } - - override fun lower(value: UInt): Int { - return value.toInt() - } - - override fun allocationSize(value: UInt) = 4 - - override fun write(value: UInt, buf: ByteBuffer) { - buf.putInt(value.toInt()) - } -} - -public object FfiConverterBoolean: FfiConverter { - override fun lift(value: Byte): Boolean { - return value.toInt() != 0 - } - - override fun read(buf: ByteBuffer): Boolean { - return lift(buf.get()) - } - - override fun lower(value: Boolean): Byte { - return if (value) 1.toByte() else 0.toByte() - } - - override fun allocationSize(value: Boolean) = 1 - - override fun write(value: Boolean, buf: ByteBuffer) { - buf.put(lower(value)) - } -} - -public object FfiConverterString: FfiConverter { - // Note: we don't inherit from FfiConverterRustBuffer, because we use a - // special encoding when lowering/lifting. We can use `RustBuffer.len` to - // store our length and avoid writing it out to the buffer. - override fun lift(value: RustBuffer.ByValue): String { - try { - val byteArr = ByteArray(value.len) - value.asByteBuffer()!!.get(byteArr) - return byteArr.toString(Charsets.UTF_8) - } finally { - RustBuffer.free(value) - } - } - - override fun read(buf: ByteBuffer): String { - val len = buf.getInt() - val byteArr = ByteArray(len) - buf.get(byteArr) - return byteArr.toString(Charsets.UTF_8) - } - - fun toUtf8(value: String): ByteBuffer { - // Make sure we don't have invalid UTF-16, check for lone surrogates. - return Charsets.UTF_8.newEncoder().run { - onMalformedInput(CodingErrorAction.REPORT) - encode(CharBuffer.wrap(value)) - } - } - - override fun lower(value: String): RustBuffer.ByValue { - val byteBuf = toUtf8(value) - // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us - // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. - val rbuf = RustBuffer.alloc(byteBuf.limit()) - rbuf.asByteBuffer()!!.put(byteBuf) - return rbuf - } - - // We aren't sure exactly how many bytes our string will be once it's UTF-8 - // encoded. Allocate 3 bytes per UTF-16 code unit which will always be - // enough. - override fun allocationSize(value: String): Int { - val sizeForLength = 4 - val sizeForString = value.length * 3 - return sizeForLength + sizeForString - } - - override fun write(value: String, buf: ByteBuffer) { - val byteBuf = toUtf8(value) - buf.putInt(byteBuf.limit()) - buf.put(byteBuf) - } -} - -public object FfiConverterByteArray: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): ByteArray { - val len = buf.getInt() - val byteArr = ByteArray(len) - buf.get(byteArr) - return byteArr - } - override fun allocationSize(value: ByteArray): Int { - return 4 + value.size - } - override fun write(value: ByteArray, buf: ByteBuffer) { - buf.putInt(value.size) - buf.put(value) - } -} - - -// Interface implemented by anything that can contain an object reference. -// -// Such types expose a `destroy()` method that must be called to cleanly -// dispose of the contained objects. Failure to call this method may result -// in memory leaks. -// -// The easiest way to ensure this method is called is to use the `.use` -// helper method to execute a block and destroy the object at the end. -interface Disposable { - fun destroy() - companion object { - fun destroy(vararg args: Any?) { - args.filterIsInstance() - .forEach(Disposable::destroy) - } - } -} - -inline fun T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - -// The base class for all UniFFI Object types. -// -// This class provides core operations for working with the Rust `Arc` pointer to -// the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an `FFIObject` is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an `FFIObject` instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so will -// leak the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// In the future we may be able to replace some of this with automatic finalization logic, such as using -// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is -// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also -// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], -// so there would still be some complexity here). -// -// Sigh...all of this for want of a robust finalization mechanism. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// -abstract class FFIObject( - protected val pointer: Pointer -): Disposable, AutoCloseable { - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - open protected fun freeRustArcPtr() { - // To be overridden in subclasses. - } - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.pointer) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } -} - -public interface MoproCircomInterface { - @Throws(MoproException::class) - fun `generateProof`(`circuitInputs`: Map>): GenerateProofResult@Throws(MoproException::class) - fun `setup`(`wasmPath`: String, `r1csPath`: String): SetupResult@Throws(MoproException::class) - fun `verifyProof`(`proof`: ByteArray, `publicInput`: ByteArray): Boolean - companion object -} - -class MoproCircom( - pointer: Pointer -) : FFIObject(pointer), MoproCircomInterface { - constructor() : - this( - rustCall() { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_constructor_moprocircom_new(_status) -}) - - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_free_moprocircom(this.pointer, status) - } - } - - - @Throws(MoproException::class)override fun `generateProof`(`circuitInputs`: Map>): GenerateProofResult = - callWithPointer { - rustCallWithError(MoproException) { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_method_moprocircom_generate_proof(it, - FfiConverterMapStringSequenceString.lower(`circuitInputs`), - _status) -} - }.let { - FfiConverterTypeGenerateProofResult.lift(it) - } - - - @Throws(MoproException::class)override fun `setup`(`wasmPath`: String, `r1csPath`: String): SetupResult = - callWithPointer { - rustCallWithError(MoproException) { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_method_moprocircom_setup(it, - FfiConverterString.lower(`wasmPath`),FfiConverterString.lower(`r1csPath`), - _status) -} - }.let { - FfiConverterTypeSetupResult.lift(it) - } - - - @Throws(MoproException::class)override fun `verifyProof`(`proof`: ByteArray, `publicInput`: ByteArray): Boolean = - callWithPointer { - rustCallWithError(MoproException) { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_method_moprocircom_verify_proof(it, - FfiConverterByteArray.lower(`proof`),FfiConverterByteArray.lower(`publicInput`), - _status) -} - }.let { - FfiConverterBoolean.lift(it) - } - - - - - companion object - -} - -public object FfiConverterTypeMoproCircom: FfiConverter { - override fun lower(value: MoproCircom): Pointer = value.callWithPointer { it } - - override fun lift(value: Pointer): MoproCircom { - return MoproCircom(value) - } - - override fun read(buf: ByteBuffer): MoproCircom { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) - } - - override fun allocationSize(value: MoproCircom) = 8 - - override fun write(value: MoproCircom, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) - } -} - - - - -data class GenerateProofResult ( - var `proof`: ByteArray, - var `inputs`: ByteArray -) { - - companion object -} - -public object FfiConverterTypeGenerateProofResult: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): GenerateProofResult { - return GenerateProofResult( - FfiConverterByteArray.read(buf), - FfiConverterByteArray.read(buf), - ) - } - - override fun allocationSize(value: GenerateProofResult) = ( - FfiConverterByteArray.allocationSize(value.`proof`) + - FfiConverterByteArray.allocationSize(value.`inputs`) - ) - - override fun write(value: GenerateProofResult, buf: ByteBuffer) { - FfiConverterByteArray.write(value.`proof`, buf) - FfiConverterByteArray.write(value.`inputs`, buf) - } -} - - - - -data class SetupResult ( - var `provingKey`: ByteArray -) { - - companion object -} - -public object FfiConverterTypeSetupResult: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): SetupResult { - return SetupResult( - FfiConverterByteArray.read(buf), - ) - } - - override fun allocationSize(value: SetupResult) = ( - FfiConverterByteArray.allocationSize(value.`provingKey`) - ) - - override fun write(value: SetupResult, buf: ByteBuffer) { - FfiConverterByteArray.write(value.`provingKey`, buf) - } -} - - - - - -sealed class MoproException(message: String): Exception(message) { - // Each variant is a nested class - // Flat enums carries a string error message, so no special implementation is necessary. - class CircomException(message: String) : MoproException(message) - - - companion object ErrorHandler : CallStatusErrorHandler { - override fun lift(error_buf: RustBuffer.ByValue): MoproException = FfiConverterTypeMoproError.lift(error_buf) - } -} - -public object FfiConverterTypeMoproError : FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): MoproException { - - return when(buf.getInt()) { - 1 -> MoproException.CircomException(FfiConverterString.read(buf)) - else -> throw RuntimeException("invalid error enum value, something is very wrong!!") - } - - } - - override fun allocationSize(value: MoproException): Int { - return 4 - } - - override fun write(value: MoproException, buf: ByteBuffer) { - when(value) { - is MoproException.CircomException -> { - buf.putInt(1) - Unit - } - }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } - } - -} - - - - -public object FfiConverterSequenceString: FfiConverterRustBuffer> { - override fun read(buf: ByteBuffer): List { - val len = buf.getInt() - return List(len) { - FfiConverterString.read(buf) - } - } - - override fun allocationSize(value: List): Int { - val sizeForLength = 4 - val sizeForItems = value.map { FfiConverterString.allocationSize(it) }.sum() - return sizeForLength + sizeForItems - } - - override fun write(value: List, buf: ByteBuffer) { - buf.putInt(value.size) - value.forEach { - FfiConverterString.write(it, buf) - } - } -} - - - -public object FfiConverterMapStringSequenceString: FfiConverterRustBuffer>> { - override fun read(buf: ByteBuffer): Map> { - val len = buf.getInt() - return buildMap>(len) { - repeat(len) { - val k = FfiConverterString.read(buf) - val v = FfiConverterSequenceString.read(buf) - this[k] = v - } - } - } - - override fun allocationSize(value: Map>): Int { - val spaceForMapSize = 4 - val spaceForChildren = value.map { (k, v) -> - FfiConverterString.allocationSize(k) + - FfiConverterSequenceString.allocationSize(v) - }.sum() - return spaceForMapSize + spaceForChildren - } - - override fun write(value: Map>, buf: ByteBuffer) { - buf.putInt(value.size) - // The parens on `(k, v)` here ensure we're calling the right method, - // which is important for compatibility with older android devices. - // Ref https://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/ - value.forEach { (k, v) -> - FfiConverterString.write(k, buf) - FfiConverterSequenceString.write(v, buf) - } - } -} - -fun `add`(`a`: UInt, `b`: UInt): UInt { - return FfiConverterUInt.lift( - rustCall() { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_func_add(FfiConverterUInt.lower(`a`),FfiConverterUInt.lower(`b`),_status) -}) -} - -@Throws(MoproException::class) - -fun `generateProof2`(`circuitInputs`: Map>): GenerateProofResult { - return FfiConverterTypeGenerateProofResult.lift( - rustCallWithError(MoproException) { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_func_generate_proof2(FfiConverterMapStringSequenceString.lower(`circuitInputs`),_status) -}) -} - - -fun `hello`(): String { - return FfiConverterString.lift( - rustCall() { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_func_hello(_status) -}) -} - -@Throws(MoproException::class) - -fun `initializeMopro`() = - - rustCallWithError(MoproException) { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_func_initialize_mopro(_status) -} - - -@Throws(MoproException::class) - -fun `initializeMoproDylib`(`dylibPath`: String) = - - rustCallWithError(MoproException) { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_func_initialize_mopro_dylib(FfiConverterString.lower(`dylibPath`),_status) -} - - -@Throws(MoproException::class) - -fun `verifyProof2`(`proof`: ByteArray, `publicInput`: ByteArray): Boolean { - return FfiConverterBoolean.lift( - rustCallWithError(MoproException) { _status -> - _UniFFILib.INSTANCE.uniffi_mopro_ffi_fn_func_verify_proof2(FfiConverterByteArray.lower(`proof`),FfiConverterByteArray.lower(`publicInput`),_status) -}) -} - - diff --git a/app/android/app/src/main/jniLibs/arm64-v8a/libuniffi_mopro.so b/app/android/app/src/main/jniLibs/arm64-v8a/libuniffi_mopro.so deleted file mode 100755 index 56bb5b674..000000000 Binary files a/app/android/app/src/main/jniLibs/arm64-v8a/libuniffi_mopro.so and /dev/null differ diff --git a/app/android/app/src/main/res/raw/proof_of_passport.dat b/app/android/app/src/main/res/raw/proof_of_passport.dat new file mode 100644 index 000000000..da3688916 Binary files /dev/null and b/app/android/app/src/main/res/raw/proof_of_passport.dat differ diff --git a/app/android/build.gradle b/app/android/build.gradle index ab536876c..1673cebdb 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -20,7 +20,6 @@ buildscript { } } dependencies { - // classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:7.4.2' diff --git a/app/ark-zkey/.gitignore b/app/ark-zkey/.gitignore deleted file mode 100644 index 6985cf1bd..000000000 --- a/app/ark-zkey/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb diff --git a/app/ark-zkey/Cargo.toml b/app/ark-zkey/Cargo.toml deleted file mode 100644 index 97c4985d0..000000000 --- a/app/ark-zkey/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "ark-zkey" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[[bin]] -name = "arkzkey-util" -path = "src/bin/arkzkey_util.rs" - -# XXX: Shouldn't be necessary, but this way we stay consistent with wasmer version and fix -# error[E0432]: unresolved import `wasmer` error -# (likely due to other packages) -[patch.crates-io] -# NOTE: Forked wasmer to work around memory limits -# See https://github.com/wasmerio/wasmer/commit/09c7070 -wasmer = { git = "https://github.com/oskarth/wasmer.git", rev = "09c7070" } - -[dependencies] -color-eyre = "0.6" -memmap2 = "0.9" -flame = "0.2" -flamer = "0.5" - -ark-serialize = { version = "=0.4.1", features = ["derive"] } -ark-bn254 = { version = "=0.4.0" } -ark-groth16 = { version = "=0.4.0" } -ark-circom = { git = "https://github.com/arkworks-rs/circom-compat.git" } -ark-relations = { version = "=0.4.0" } -ark-ff = { version = "=0.4.1" } -ark-ec = { version = "=0.4.1" } diff --git a/app/ark-zkey/README.md b/app/ark-zkey/README.md deleted file mode 100644 index 4fda435a9..000000000 --- a/app/ark-zkey/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# ark-zkey - -Library to read `zkey` faster by serializing to `arkworks` friendly format. - -See https://github.com/oskarth/mopro/issues/25 for context. - -## How to use - -Run the following to convert a `zkey` to an `arkzkey`. This should be done as a pre-processing step. - -`cargo run --bin arkzkey-util --release -- ../mopro-core/examples/circom/keccak256/target/keccak256_256_test_final.zkey` - -This will generate and place an `arkzkey` file in the same directory as the original zkey. - -You can also install it locally: - -`cargo install --bin arkzkey-util --path . --release` - -## Tests - -``` -cargo test multiplier2 --release -- --nocapture -cargo test keccak256 --release -- --nocapture -cargo test rsa --release -- --nocapture -``` - -## Benchmark (Keccak) - -`cargo test keccak256 --release -- --nocapture` - -``` -[build] Processing zkey data... -test tests::test_keccak256_serialization_deserialization has been running for over 60 seconds -[build]Time to process zkey data: 158.753181958s -[build] Serializing proving key and constraint matrices -[build] Time to serialize proving key and constraint matrices: 42ns -[build] Writing arkzkey to: ../mopro-core/examples/circom/keccak256/target/keccak256_256_test_final.arkzkey -[build] Time to write arkzkey: 16.204274125s -Reading arkzkey from: ../mopro-core/examples/circom/keccak256/target/keccak256_256_test_final.arkzkey -Time to open arkzkey file: 51.75µs -Time to mmap arkzkey: 17.25µs -Time to deserialize proving key: 18.323550083s -Time to deserialize matrices: 46.935792ms -Time to read arkzkey: 18.3730695s -test tests::test_keccak256_serialization_deserialization ... ok -``` - -Vs naive: - -`[build] Time to process zkey data: 158.753181958s` - -**Result: 18s vs 158s** \ No newline at end of file diff --git a/app/ark-zkey/src/bin/arkzkey_util.rs b/app/ark-zkey/src/bin/arkzkey_util.rs deleted file mode 100644 index 130a93a2b..000000000 --- a/app/ark-zkey/src/bin/arkzkey_util.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::env; -use std::path::{Path, PathBuf}; - -extern crate ark_zkey; -use ark_zkey::{convert_zkey, read_proving_key_and_matrices_from_zkey}; - -fn main() -> color_eyre::eyre::Result<()> { - color_eyre::install()?; - - let args: Vec = env::args().collect(); - if args.len() != 2 { - eprintln!("Usage: zkey_to_arkzkey "); - std::process::exit(1); - } - - let zkey_path = &args[1]; - let (proving_key, constraint_matrices) = read_proving_key_and_matrices_from_zkey(zkey_path)?; - - let arkzkey_path = get_arkzkey_path(zkey_path); - let arkzkey_path_str = arkzkey_path - .to_str() - .ok_or_else(|| color_eyre::eyre::eyre!("Failed to convert arkzkey path to string"))?; - - convert_zkey(proving_key, constraint_matrices, &arkzkey_path_str)?; - - println!("Converted zkey file saved to: {}", arkzkey_path.display()); - - Ok(()) -} - -fn get_arkzkey_path(zkey_path: &str) -> PathBuf { - let path = Path::new(zkey_path); - let mut arkzkey_path = path.to_path_buf(); - arkzkey_path.set_extension("arkzkey"); - arkzkey_path -} diff --git a/app/ark-zkey/src/lib.rs b/app/ark-zkey/src/lib.rs deleted file mode 100644 index a41c84a92..000000000 --- a/app/ark-zkey/src/lib.rs +++ /dev/null @@ -1,247 +0,0 @@ -use ark_bn254::{Bn254, Fr}; -use ark_circom::read_zkey; -//use ark_ec::pairing::Pairing; -use ark_ff::Field; -use ark_groth16::ProvingKey; -//use ark_groth16::VerifyingKey; -use ark_relations::r1cs::ConstraintMatrices; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use color_eyre::eyre::{Result, WrapErr}; -use memmap2::Mmap; -use std::fs::File; -//use std::io::Cursor; -//use std::io::{Read,self}; -use std::io::BufReader; -use std::path::PathBuf; -use std::time::Instant; - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)] -pub struct SerializableProvingKey(pub ProvingKey); - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)] -pub struct SerializableMatrix { - pub data: Vec>, -} - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)] -pub struct SerializableConstraintMatrices { - pub num_instance_variables: usize, - pub num_witness_variables: usize, - pub num_constraints: usize, - pub a_num_non_zero: usize, - pub b_num_non_zero: usize, - pub c_num_non_zero: usize, - pub a: SerializableMatrix, - pub b: SerializableMatrix, - pub c: SerializableMatrix, -} - -impl From>> for SerializableMatrix { - fn from(matrix: Vec>) -> Self { - SerializableMatrix { data: matrix } - } -} - -impl From> for Vec> { - fn from(serializable_matrix: SerializableMatrix) -> Self { - serializable_matrix.data - } -} - -pub fn serialize_proving_key(pk: &SerializableProvingKey) -> Vec { - let mut serialized_data = Vec::new(); - pk.serialize_compressed(&mut serialized_data) - .expect("Serialization failed"); - serialized_data -} - -pub fn deserialize_proving_key(data: Vec) -> SerializableProvingKey { - SerializableProvingKey::deserialize_compressed_unchecked(&mut &data[..]) - .expect("Deserialization failed") -} - -pub fn read_arkzkey( - arkzkey_path: &str, -) -> Result<(SerializableProvingKey, SerializableConstraintMatrices)> { - let now = std::time::Instant::now(); - let arkzkey_file_path = PathBuf::from(arkzkey_path); - let arkzkey_file = File::open(arkzkey_file_path).wrap_err("Failed to open arkzkey file")?; - println!("Time to open arkzkey file: {:?}", now.elapsed()); - - //let mut buf_reader = BufReader::new(arkzkey_file); - - // Using mmap - let now = std::time::Instant::now(); - let mmap = unsafe { Mmap::map(&arkzkey_file)? }; - let mut cursor = std::io::Cursor::new(mmap); - println!("Time to mmap arkzkey: {:?}", now.elapsed()); - - // Was &mut buf_reader - let now = std::time::Instant::now(); - let proving_key = SerializableProvingKey::deserialize_compressed_unchecked(&mut cursor) - .wrap_err("Failed to deserialize proving key")?; - println!("Time to deserialize proving key: {:?}", now.elapsed()); - - let now = std::time::Instant::now(); - let constraint_matrices = - SerializableConstraintMatrices::deserialize_compressed_unchecked(&mut cursor) - .wrap_err("Failed to deserialize constraint matrices")?; - println!("Time to deserialize matrices: {:?}", now.elapsed()); - - Ok((proving_key, constraint_matrices)) -} - -// TODO: Return ProvingKey, ConstraintMatrices? -pub fn read_arkzkey_from_bytes( - arkzkey_bytes: &[u8], -) -> Result<(ProvingKey, ConstraintMatrices)> { - let mut cursor = std::io::Cursor::new(arkzkey_bytes); - - let now = std::time::Instant::now(); - let serialized_proving_key = - SerializableProvingKey::deserialize_compressed_unchecked(&mut cursor) - .wrap_err("Failed to deserialize proving key")?; - println!("Time to deserialize proving key: {:?}", now.elapsed()); - - let now = std::time::Instant::now(); - let serialized_constraint_matrices = - SerializableConstraintMatrices::deserialize_compressed_unchecked(&mut cursor) - .wrap_err("Failed to deserialize constraint matrices")?; - println!("Time to deserialize matrices: {:?}", now.elapsed()); - - // Get on right form for API - let proving_key: ProvingKey = serialized_proving_key.0; - let constraint_matrices: ConstraintMatrices = ConstraintMatrices { - num_instance_variables: serialized_constraint_matrices.num_instance_variables, - num_witness_variables: serialized_constraint_matrices.num_witness_variables, - num_constraints: serialized_constraint_matrices.num_constraints, - a_num_non_zero: serialized_constraint_matrices.a_num_non_zero, - b_num_non_zero: serialized_constraint_matrices.b_num_non_zero, - c_num_non_zero: serialized_constraint_matrices.c_num_non_zero, - a: serialized_constraint_matrices.a.data, - b: serialized_constraint_matrices.b.data, - c: serialized_constraint_matrices.c.data, - }; - - Ok((proving_key, constraint_matrices)) -} - -pub fn read_proving_key_and_matrices_from_zkey( - zkey_path: &str, -) -> Result<(SerializableProvingKey, SerializableConstraintMatrices)> { - println!("Reading zkey from: {}", zkey_path); - let now = Instant::now(); - let zkey_file_path = PathBuf::from(zkey_path); - let zkey_file = File::open(zkey_file_path).wrap_err("Failed to open zkey file")?; - - let mut buf_reader = BufReader::new(zkey_file); - - let (proving_key, matrices) = - read_zkey(&mut buf_reader).wrap_err("Failed to read zkey file")?; - println!("Time to read zkey: {:?}", now.elapsed()); - - println!("Serializing proving key and constraint matrices"); - let now = Instant::now(); - let serializable_proving_key = SerializableProvingKey(proving_key); - let serializable_constrain_matrices = SerializableConstraintMatrices { - num_instance_variables: matrices.num_instance_variables, - num_witness_variables: matrices.num_witness_variables, - num_constraints: matrices.num_constraints, - a_num_non_zero: matrices.a_num_non_zero, - b_num_non_zero: matrices.b_num_non_zero, - c_num_non_zero: matrices.c_num_non_zero, - a: SerializableMatrix { data: matrices.a }, - b: SerializableMatrix { data: matrices.b }, - c: SerializableMatrix { data: matrices.c }, - }; - println!( - "Time to serialize proving key and constraint matrices: {:?}", - now.elapsed() - ); - - Ok((serializable_proving_key, serializable_constrain_matrices)) -} - -pub fn convert_zkey( - proving_key: SerializableProvingKey, - constraint_matrices: SerializableConstraintMatrices, - arkzkey_path: &str, -) -> Result<()> { - let arkzkey_file_path = PathBuf::from(arkzkey_path); - - let serialized_path = PathBuf::from(arkzkey_file_path); - - let mut file = - File::create(&serialized_path).wrap_err("Failed to create serialized proving key file")?; - - proving_key - .serialize_compressed(&mut file) - .wrap_err("Failed to serialize proving key")?; - - constraint_matrices - .serialize_compressed(&mut file) - .wrap_err("Failed to serialize constraint matrices")?; - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::time::Instant; - - fn test_circuit_serialization_deserialization(zkey_path: &str) -> Result<()> { - let arkzkey_path = zkey_path.replace(".zkey", ".arkzkey"); - - let (original_proving_key, original_constraint_matrices) = - read_proving_key_and_matrices_from_zkey(zkey_path)?; - - println!("[build] Writing arkzkey to: {}", arkzkey_path); - let now = Instant::now(); - convert_zkey( - original_proving_key.clone(), - original_constraint_matrices.clone(), - &arkzkey_path, - )?; - println!("[build] Time to write arkzkey: {:?}", now.elapsed()); - - println!("Reading arkzkey from: {}", arkzkey_path); - let now = Instant::now(); - let (deserialized_proving_key, deserialized_constraint_matrices) = - read_arkzkey(&arkzkey_path)?; - println!("Time to read arkzkey: {:?}", now.elapsed()); - - assert_eq!( - original_proving_key, deserialized_proving_key, - "Original and deserialized proving keys do not match" - ); - - assert_eq!( - original_constraint_matrices, deserialized_constraint_matrices, - "Original and deserialized constraint matrices do not match" - ); - - Ok(()) - } - - #[test] - fn test_multiplier2_serialization_deserialization() -> Result<()> { - test_circuit_serialization_deserialization( - "../mopro-core/examples/circom/multiplier2/target/multiplier2_final.zkey", - ) - } - - #[test] - fn test_keccak256_serialization_deserialization() -> Result<()> { - test_circuit_serialization_deserialization( - "../mopro-core/examples/circom/keccak256/target/keccak256_256_test_final.zkey", - ) - } - - #[test] - fn test_rsa_serialization_deserialization() -> Result<()> { - test_circuit_serialization_deserialization( - "../mopro-core/examples/circom/rsa/target/main_final.zkey", - ) - } -} diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 6366b6ce9..8aa553af6 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -291,6 +291,8 @@ PODS: - React-jsinspector (0.72.3) - React-logger (0.72.3): - glog + - react-native-get-random-values (1.11.0): + - React-Core - react-native-netinfo (11.3.1): - React-Core - React-NativeModulesApple (0.72.3): @@ -398,12 +400,12 @@ PODS: - React-jsi (= 0.72.3) - React-logger (= 0.72.3) - React-perflogger (= 0.72.3) - - RNCAsyncStorage (1.23.1): - - React-Core - RNCClipboard (1.5.1): - React-Core - RNFS (2.20.0): - React-Core + - RNKeychain (8.2.0): + - React-Core - RNSVG (13.4.0): - React-Core - RNZipArchive (6.1.0): @@ -443,6 +445,7 @@ DEPENDENCIES: - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - react-native-get-random-values (from `../node_modules/react-native-get-random-values`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) @@ -461,9 +464,9 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)" - RNFS (from `../node_modules/react-native-fs`) + - RNKeychain (from `../node_modules/react-native-keychain`) - RNSVG (from `../node_modules/react-native-svg`) - RNZipArchive (from `../node_modules/react-native-zip-archive`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -524,6 +527,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsinspector" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" + react-native-get-random-values: + :path: "../node_modules/react-native-get-random-values" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" React-NativeModulesApple: @@ -560,12 +565,12 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/utils" ReactCommon: :path: "../node_modules/react-native/ReactCommon" - RNCAsyncStorage: - :path: "../node_modules/@react-native-async-storage/async-storage" RNCClipboard: :path: "../node_modules/@react-native-community/clipboard" RNFS: :path: "../node_modules/react-native-fs" + RNKeychain: + :path: "../node_modules/react-native-keychain" RNSVG: :path: "../node_modules/react-native-svg" RNZipArchive: @@ -605,6 +610,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 2c15ba1bace70177492368d5180b564f165870fd React-jsinspector: b511447170f561157547bc0bef3f169663860be7 React-logger: c5b527272d5f22eaa09bb3c3a690fee8f237ae95 + react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 react-native-netinfo: bdb108d340cdb41875c9ced535977cac6d2ff321 React-NativeModulesApple: 0438665fc7473be6edc496e823e6ea0b0537b46c React-perflogger: 6bd153e776e6beed54c56b0847e1220a3ff92ba5 @@ -623,9 +629,9 @@ SPEC CHECKSUMS: React-runtimescheduler: ec1066a4f2d1152eb1bc3fb61d69376b3bc0dde0 React-utils: d55ba834beb39f01b0b470ae43478c0a3a024abe ReactCommon: 68e3a815fbb69af3bb4196e04c6ae7abb306e7a8 - RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 + RNKeychain: bfe3d12bf4620fe488771c414530bf16e88f3678 RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2 RNZipArchive: ef9451b849c45a29509bf44e65b788829ab07801 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 diff --git a/app/ios/ProofOfPassport/Info.plist b/app/ios/ProofOfPassport/Info.plist index d402a29c2..2bef855b6 100644 --- a/app/ios/ProofOfPassport/Info.plist +++ b/app/ios/ProofOfPassport/Info.plist @@ -33,9 +33,11 @@ NFCReaderUsageDescription Need NFC to read Passport NSAppTransportSecurity - + + NSFaceIDUsageDescription + Needed to secure the secret NSCameraUsageDescription - Needed to scan your passport MRZ, you can however enter it manually. + Needed to scan the passport MRZ. NSHumanReadableCopyright NSLocationWhenInUseUsageDescription diff --git a/app/ios/Prover.m b/app/ios/Prover.m index 7def067eb..87bae0919 100644 --- a/app/ios/Prover.m +++ b/app/ios/Prover.m @@ -10,10 +10,12 @@ @interface RCT_EXTERN_MODULE(Prover, NSObject) -RCT_EXTERN_METHOD(runProveAction:(NSDictionary *)inputs +RCT_EXTERN_METHOD(runProveAction:(NSString *)zkey_path + witness_calculator:(NSString *)witness_calculator + dat_file_name:(NSString *)dat_file_name + inputs:(NSDictionary *)inputs resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) - + (BOOL) requiresMainQueueSetup { return YES; } diff --git a/app/ios/Prover.swift b/app/ios/Prover.swift index 03a3c0817..85c761a16 100644 --- a/app/ios/Prover.swift +++ b/app/ios/Prover.swift @@ -34,17 +34,21 @@ struct Proof: Codable { @available(iOS 15, *) @objc(Prover) class Prover: NSObject { - @objc(runProveAction:resolve:reject:) - func runProveAction(_ inputs: [String: [String]], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + @objc(runProveAction:witness_calculator:dat_file_name:inputs:resolve:reject:) + func runProveAction(_ zkey_path: String, witness_calculator: String, dat_file_name: String, inputs: [String: [String]], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { do { let inputsJson = try! JSONEncoder().encode(inputs) print("inputs size: \(inputsJson.count) bytes") print("inputs data: \(String(data: inputsJson, encoding: .utf8) ?? "")") - let wtns = try! calcWtns(inputsJson: inputsJson) + let wtns = try! calcWtns( + witness_calculator: witness_calculator, + dat_file_name: dat_file_name, + inputsJson: inputsJson + ) print("wtns size: \(wtns.count) bytes") - let (proofRaw, pubSignalsRaw) = try groth16prove(wtns: wtns) + let (proofRaw, pubSignalsRaw) = try groth16prove(zkey_path: zkey_path, wtns: wtns) let proof = try JSONDecoder().decode(Proof.self, from: proofRaw) let pubSignals = try JSONDecoder().decode([String].self, from: pubSignalsRaw) @@ -68,12 +72,12 @@ class Prover: NSObject { } } -public func calcWtns(inputsJson: Data) throws -> Data { - let dat = NSDataAsset(name: "proof_of_passport.dat")!.data - return try _calcWtns(dat: dat, jsonData: inputsJson) +public func calcWtns(witness_calculator: String, dat_file_name: String, inputsJson: Data) throws -> Data { + let dat = NSDataAsset(name: dat_file_name + ".dat")!.data + return try _calcWtns(witness_calculator: witness_calculator, dat: dat, jsonData: inputsJson) } -private func _calcWtns(dat: Data, jsonData: Data) throws -> Data { +private func _calcWtns(witness_calculator: String, dat: Data, jsonData: Data) throws -> Data { let datSize = UInt(dat.count) let jsonDataSize = UInt(jsonData.count) @@ -85,12 +89,18 @@ private func _calcWtns(dat: Data, jsonData: Data) throws -> Data { let wtnsBuffer = UnsafeMutablePointer.allocate(capacity: (100 * 1024 * 1024)) let errorBuffer = UnsafeMutablePointer.allocate(capacity: Int(errorSize)) - let result = witnesscalc_proof_of_passport( - (dat as NSData).bytes, datSize, - (jsonData as NSData).bytes, jsonDataSize, - wtnsBuffer, wtnsSize, - errorBuffer, errorSize - ) + let result: Int32 + + if witness_calculator == "proof_of_passport" { + result = witnesscalc_proof_of_passport( + (dat as NSData).bytes, datSize, + (jsonData as NSData).bytes, jsonDataSize, + wtnsBuffer, wtnsSize, + errorBuffer, errorSize + ) + } else { + fatalError("Invalid witness calculator name") + } if result == WITNESSCALC_ERROR { let errorMessage = String(bytes: Data(bytes: errorBuffer, count: Int(errorSize)), encoding: .utf8)! @@ -105,10 +115,12 @@ private func _calcWtns(dat: Data, jsonData: Data) throws -> Data { return Data(bytes: wtnsBuffer, count: Int(wtnsSize.pointee)) } -public func groth16prove(wtns: Data) throws -> (proof: Data, publicInputs: Data) { - let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! - let zkeyURL = documentsPath.appendingPathComponent("proof_of_passport.zkey") - +public func groth16prove(zkey_path: String, wtns: Data) throws -> (proof: Data, publicInputs: Data) { + guard let zkeyURL = URL(string: "file://" + zkey_path) else { + throw NSError(domain: "YourErrorDomain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid zkey file path."]) + } + print("zkeyURL: \(zkeyURL)") + guard let zkeyData = try? Data(contentsOf: zkeyURL) else { throw NSError(domain: "YourErrorDomain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to load zkey file."]) } @@ -156,4 +168,4 @@ public func _groth16Prover(zkey: Data, wtns: Data) throws -> (proof: Data, publi publicInputs = publicInputs[0.. Result<()> { - let project_dir = env::var("CARGO_MANIFEST_DIR")?; - let zkey_file = PathBuf::from(&project_dir).join(zkey_path); - let wasm_file = PathBuf::from(&project_dir).join(wasm_path); - let arkzkey_file = PathBuf::from(&project_dir).join(arkzkey_path); - - // TODO: Right now emitting as warnings for visibility, figure out better way to do this? - println!("cargo:warning=zkey_file: {}", zkey_file.display()); - println!("cargo:warning=wasm_file: {}", wasm_file.display()); - println!("cargo:warning=arkzkey_file: {}", arkzkey_file.display()); - - // Set BUILD_RS_ZKEY_FILE and BUILD_RS_WASM_FILE env var - println!("cargo:rustc-env=BUILD_RS_ZKEY_FILE={}", zkey_file.display()); - println!("cargo:rustc-env=BUILD_RS_WASM_FILE={}", wasm_file.display()); - println!( - "cargo:rustc-env=BUILD_RS_ARKZKEY_FILE={}", - arkzkey_file.display() - ); - - Ok(()) -} - -#[cfg(feature = "dylib")] -fn build_dylib(wasm_path: String, dylib_name: String) -> Result<()> { - use std::path::Path; - use std::{fs, str::FromStr}; - - use color_eyre::eyre::eyre; - use enumset::enum_set; - use enumset::EnumSet; - - use wasmer::Cranelift; - use wasmer::Dylib; - use wasmer::Target; - use wasmer::{Module, Store, Triple}; - - let out_dir = env::var("OUT_DIR")?; - let project_dir = env::var("CARGO_MANIFEST_DIR")?; - let build_mode = env::var("PROFILE")?; - let target_arch = env::var("TARGET")?; - - let out_dir = Path::new(&out_dir).to_path_buf(); - let wasm_file = Path::new(&wasm_path).to_path_buf(); - let dylib_file = out_dir.join(&dylib_name); - let final_dir = PathBuf::from(&project_dir) - .join("target") - .join(&target_arch) - .join(build_mode); - - // if dylib_file.exists() { - // return Ok(()); - // } - - // Create a WASM engine for the target that can compile - let triple = Triple::from_str(&target_arch).map_err(|e| eyre!(e))?; - let cpu_features = enum_set!(); - let target = Target::new(triple, cpu_features); - let engine = Dylib::new(Cranelift::default()).target(target).engine(); - println!("cargo:warning=Building dylib for {}", target_arch); - - // Compile the WASM module - let store = Store::new(&engine); - let module = Module::from_file(&store, &wasm_file).unwrap(); - module.serialize_to_file(&dylib_file).unwrap(); - assert!(dylib_file.exists()); - - // Copy dylib to a more predictable path - fs::create_dir_all(&final_dir)?; - let final_path = final_dir.join(dylib_name); - fs::copy(&dylib_file, &final_path)?; - println!("cargo:warning=Dylib location: {}", final_path.display()); - - Ok(()) -} - -fn main() -> Result<()> { - // TODO: build_circuit function to builds all related artifacts, instead of doing this externally - let dir = "../../circuits"; - let circuit = "proof_of_passport"; - - let zkey_path = format!("{}/build/{}_final.zkey", dir, circuit); - let wasm_path = format!("{}/build/{}_js/{}.wasm", dir, circuit, circuit); - // TODO: Need to modify script for this - let arkzkey_path = format!("{}/build/{}_final.arkzkey", dir, circuit); - - println!("cargo:warning=arkzkey_path: {}", arkzkey_path); - - prepare_env(zkey_path, wasm_path, arkzkey_path)?; - - Ok(()) -} diff --git a/app/mopro-core/src/lib.rs b/app/mopro-core/src/lib.rs deleted file mode 100644 index 2ac7b546f..000000000 --- a/app/mopro-core/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod middleware; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum MoproError { - #[error("CircomError: {0}")] - CircomError(String), -} diff --git a/app/mopro-core/src/middleware/circom/mod.rs b/app/mopro-core/src/middleware/circom/mod.rs deleted file mode 100644 index cc4133c01..000000000 --- a/app/mopro-core/src/middleware/circom/mod.rs +++ /dev/null @@ -1,794 +0,0 @@ -use self::{ - serialization::{SerializableInputs, SerializableProof, SerializableProvingKey}, - utils::{assert_paths_exists, bytes_to_bits}, -}; -use crate::MoproError; - -use std::collections::HashMap; -use std::sync::Mutex; -use std::time::Instant; -use std::fs; - -use ark_bn254::{Bn254, Fr}; -use ark_circom::{ - CircomBuilder, - CircomCircuit, - CircomConfig, - CircomReduction, - WitnessCalculator, -}; -use ark_crypto_primitives::snark::SNARK; -use ark_groth16::{prepare_verifying_key, Groth16, ProvingKey}; -use ark_std::UniformRand; - -use ark_relations::r1cs::ConstraintMatrices; -use ark_std::rand::thread_rng; -use color_eyre::Result; -use core::include_bytes; -use num_bigint::BigInt; -use once_cell::sync::{Lazy, OnceCell}; - -use wasmer::{Module, Store}; - -use ark_zkey::{read_arkzkey_from_bytes}; - -pub mod serialization; -pub mod utils; - -type GrothBn = Groth16; - -type CircuitInputs = HashMap>; - -// TODO: Split up this namespace a bit, right now quite a lot of things going on - -pub struct CircomState { - builder: Option>, - circuit: Option>, - params: Option>, -} - -impl Default for CircomState { - fn default() -> Self { - Self::new() - } -} - -// NOTE: A lot of the contents of this file is inspired by github.com/worldcoin/semaphore-rs - - -// const fileName = "passport.arkzkey" -// const path = "/data/user/0/com.proofofpassport/files/" + fileName -// const ZKEY_PATH_STR: &str = "proof_of_passport.arkzkey"; -const ZKEY_PATH_STR: &str = "/data/user/0/com.proofofpassport/files/proof_of_passport.zkey"; - -const WASM: &[u8] = include_bytes!(env!("BUILD_RS_WASM_FILE")); - -/// `WITNESS_CALCULATOR` is a lazily initialized, thread-safe singleton of type `WitnessCalculator`. -/// `OnceCell` ensures that the initialization occurs exactly once, and `Mutex` allows safe shared -/// access from multiple threads. -static WITNESS_CALCULATOR: OnceCell> = OnceCell::new(); - -static ARKZKEY: Lazy<(ProvingKey, ConstraintMatrices)> = Lazy::new(|| { - let bytes = fs::read(ZKEY_PATH_STR).map_err(|e| MoproError::CircomError(e.to_string())).unwrap(); - read_arkzkey_from_bytes(&bytes).map_err(|e| MoproError::CircomError(e.to_string())).unwrap() -}); - -pub fn initialize() { - println!("Initializing library with arkzkey"); - - let now = std::time::Instant::now(); - Lazy::force(&ARKZKEY); - println!("Initializing arkzkey took: {:.2?}", now.elapsed()); -} - -// fn load_arkzkey_from_file( -// zkey_path: &str, -// ) -> Result<(ProvingKey, ConstraintMatrices), MoproError> { -// let bytes = fs::read(zkey_path).map_err(|e| MoproError::CircomError(e.to_string()))?; -// read_arkzkey_from_bytes(&bytes).map_err(|e| MoproError::CircomError(e.to_string())) -// } - -pub fn arkzkey() -> (ProvingKey, ConstraintMatrices) { - // load_arkzkey_from_file(zkey_path).unwrap() - ARKZKEY.clone() -} - -pub fn witness_calculator() -> &'static Mutex { - WITNESS_CALCULATOR.get_or_init(|| { - let store = Store::default(); - let module = Module::from_binary(&store, WASM).expect("WASM should be valid"); - let result = - WitnessCalculator::from_module(module).expect("Failed to create WitnessCalculator"); - Mutex::new(result) - }) -} - -pub fn generate_proof2( - inputs: CircuitInputs, -) -> Result<(SerializableProof, SerializableInputs), MoproError> { - let mut rng = thread_rng(); - let rng = &mut rng; - - let r = ark_bn254::Fr::rand(rng); - let s = ark_bn254::Fr::rand(rng); - - println!("Generating proof 2"); - - let now = std::time::Instant::now(); - let full_assignment = witness_calculator() - .lock() - .expect("Failed to lock witness calculator") - .calculate_witness_element::(inputs, false) - .map_err(|e| MoproError::CircomError(e.to_string()))?; - - println!("Witness generation took: {:.2?}", now.elapsed()); - - let now = std::time::Instant::now(); - - let zkey = arkzkey(); - println!("Loading arkzkey took: {:.2?}", now.elapsed()); - - let public_inputs = full_assignment.as_slice()[1..zkey.1.num_instance_variables].to_vec(); - - let now = std::time::Instant::now(); - let ark_proof = Groth16::<_, CircomReduction>::create_proof_with_reduction_and_matrices( - &zkey.0, - r, - s, - &zkey.1, - zkey.1.num_instance_variables, - zkey.1.num_constraints, - full_assignment.as_slice(), - ); - - let proof = ark_proof.map_err(|e| MoproError::CircomError(e.to_string()))?; - - println!("proof generation took: {:.2?}", now.elapsed()); - - Ok((SerializableProof(proof), SerializableInputs(public_inputs))) -} - -pub fn verify_proof2( - serialized_proof: SerializableProof, - serialized_inputs: SerializableInputs, -) -> Result { - let start = Instant::now(); - let zkey = arkzkey(); - let pvk = prepare_verifying_key(&zkey.0.vk); - - let proof_verified = - GrothBn::verify_with_processed_vk(&pvk, &serialized_inputs.0, &serialized_proof.0) - .map_err(|e| MoproError::CircomError(e.to_string()))?; - - let verification_duration = start.elapsed(); - println!("Verification time 2: {:?}", verification_duration); - Ok(proof_verified) -} - -impl CircomState { - pub fn new() -> Self { - Self { - builder: None, - circuit: None, - params: None, - } - } - - pub fn setup( - &mut self, - wasm_path: &str, - r1cs_path: &str, - ) -> Result { - assert_paths_exists(wasm_path, r1cs_path)?; - println!("Setup"); - let start = Instant::now(); - - // Load the WASM and R1CS for witness and proof generation - let cfg = self.load_config(wasm_path, r1cs_path)?; - - // Create an empty instance for setup - self.builder = Some(CircomBuilder::new(cfg)); - - // Run a trusted setup using the rng in the state - let params = self.run_trusted_setup()?; - - self.params = Some(params.clone()); - - let setup_duration = start.elapsed(); - println!("Setup time: {:?}", setup_duration); - - Ok(SerializableProvingKey(params)) - } - - // NOTE: Consider generate_proof> API - // XXX: BigInt might present problems for UniFFI - pub fn generate_proof( - &mut self, - inputs: CircuitInputs, - ) -> Result<(SerializableProof, SerializableInputs), MoproError> { - let start = Instant::now(); - println!("Generating proof"); - - let mut rng = thread_rng(); - - let builder = self.builder.as_mut().ok_or(MoproError::CircomError( - "Builder has not been set up".to_string(), - ))?; - - // Insert our inputs as key value pairs - for (key, values) in &inputs { - for value in values { - builder.push_input(&key, value.clone()); - } - } - - // Clone the builder, then build the circuit - let circom = builder - .clone() - .build() - .map_err(|e| MoproError::CircomError(e.to_string()))?; - - // Update the circuit in self - self.circuit = Some(circom.clone()); - - let params = self.params.as_ref().ok_or(MoproError::CircomError( - "Parameters have not been set up".to_string(), - ))?; - - let inputs = circom.get_public_inputs().ok_or(MoproError::CircomError( - "Failed to get public inputs".to_string(), - ))?; - - let proof = GrothBn::prove(params, circom.clone(), &mut rng) - .map_err(|e| MoproError::CircomError(e.to_string()))?; - - let proof_duration = start.elapsed(); - println!("Proof generation time: {:?}", proof_duration); - - Ok((SerializableProof(proof), SerializableInputs(inputs))) - } - - pub fn verify_proof( - &self, - serialized_proof: SerializableProof, - serialized_inputs: SerializableInputs, - ) -> Result { - let start = Instant::now(); - - println!("Verifying proof"); - - let params = self.params.as_ref().ok_or(MoproError::CircomError( - "Parameters have not been set up".to_string(), - ))?; - - let pvk = - GrothBn::process_vk(¶ms.vk).map_err(|e| MoproError::CircomError(e.to_string()))?; - - let proof_verified = - GrothBn::verify_with_processed_vk(&pvk, &serialized_inputs.0, &serialized_proof.0) - .map_err(|e| MoproError::CircomError(e.to_string()))?; - - let verification_duration = start.elapsed(); - println!("Verification time: {:?}", verification_duration); - Ok(proof_verified) - } - - fn load_config( - &self, - wasm_path: &str, - r1cs_path: &str, - ) -> Result, MoproError> { - CircomConfig::::new(wasm_path, r1cs_path) - .map_err(|e| MoproError::CircomError(e.to_string())) - } - - fn run_trusted_setup(&mut self) -> Result, MoproError> { - let circom_setup = self - .builder - .as_mut() - .ok_or(MoproError::CircomError( - "Builder has not been set up".to_string(), - ))? - .setup(); - - let mut rng = thread_rng(); - - GrothBn::generate_random_parameters_with_reduction(circom_setup, &mut rng) - .map_err(|e| MoproError::CircomError(e.to_string())) - } -} - -// Helper function for Keccak256 example -pub fn bytes_to_circuit_inputs(bytes: &[u8]) -> CircuitInputs { - let bits = bytes_to_bits(bytes); - let big_int_bits = bits - .into_iter() - .map(|bit| BigInt::from(bit as u8)) - .collect(); - let mut inputs = HashMap::new(); - inputs.insert("in".to_string(), big_int_bits); - inputs -} - -pub fn strings_to_circuit_inputs(strings: &[&str]) -> Vec { - strings - .iter() - .map(|&value| BigInt::parse_bytes(value.as_bytes(), 10).unwrap()) - .collect() -} - -pub fn bytes_to_circuit_outputs(bytes: &[u8]) -> SerializableInputs { - let bits = bytes_to_bits(bytes); - let field_bits = bits.into_iter().map(|bit| Fr::from(bit as u8)).collect(); - SerializableInputs(field_bits) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_setup_prove_verify_simple() { - let wasm_path = "./examples/circom/multiplier2/target/multiplier2_js/multiplier2.wasm"; - let r1cs_path = "./examples/circom/multiplier2/target/multiplier2.r1cs"; - - // Instantiate CircomState - let mut circom_state = CircomState::new(); - - // Setup - let setup_res = circom_state.setup(wasm_path, r1cs_path); - assert!(setup_res.is_ok()); - - let _serialized_pk = setup_res.unwrap(); - - // Deserialize the proving key and inputs if necessary - - // Prepare inputs - let mut inputs = HashMap::new(); - let a = 3; - let b = 5; - let c = a * b; - inputs.insert("a".to_string(), vec![BigInt::from(a)]); - inputs.insert("b".to_string(), vec![BigInt::from(b)]); - // output = [public output c, public input a] - let expected_output = vec![Fr::from(c), Fr::from(a)]; - let serialized_outputs = SerializableInputs(expected_output); - - // Proof generation - let generate_proof_res = circom_state.generate_proof(inputs); - - // Check and print the error if there is one - if let Err(e) = &generate_proof_res { - println!("Error: {:?}", e); - } - - assert!(generate_proof_res.is_ok()); - - let (serialized_proof, serialized_inputs) = generate_proof_res.unwrap(); - - // Check output - assert_eq!(serialized_inputs, serialized_outputs); - - // Proof verification - let verify_res = circom_state.verify_proof(serialized_proof, serialized_inputs); - assert!(verify_res.is_ok()); - assert!(verify_res.unwrap()); // Verifying that the proof was indeed verified - } - - #[test] - fn test_setup_prove_verify_keccak() { - let wasm_path = - "./examples/circom/keccak256/target/keccak256_256_test_js/keccak256_256_test.wasm"; - let r1cs_path = "./examples/circom/keccak256/target/keccak256_256_test.r1cs"; - - // Instantiate CircomState - let mut circom_state = CircomState::new(); - - // Setup - let setup_res = circom_state.setup(wasm_path, r1cs_path); - assert!(setup_res.is_ok()); - - let _serialized_pk = setup_res.unwrap(); - - // Deserialize the proving key and inputs if necessary - - // Prepare inputs - let input_vec = vec![ - 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ]; - - // Expected output - let expected_output_vec = vec![ - 37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, 228, 211, 170, 133, 153, 9, 88, - 212, 4, 212, 175, 238, 249, 210, 214, 116, 170, 85, 45, 21, - ]; - - let inputs = bytes_to_circuit_inputs(&input_vec); - let serialized_outputs = bytes_to_circuit_outputs(&expected_output_vec); - - // Proof generation - let generate_proof_res = circom_state.generate_proof(inputs); - - // Check and print the error if there is one - if let Err(e) = &generate_proof_res { - println!("Error: {:?}", e); - } - - assert!(generate_proof_res.is_ok()); - - let (serialized_proof, serialized_inputs) = generate_proof_res.unwrap(); - - // Check output - assert_eq!(serialized_inputs, serialized_outputs); - - // Proof verification - let verify_res = circom_state.verify_proof(serialized_proof, serialized_inputs); - assert!(verify_res.is_ok()); - - assert!(verify_res.unwrap()); // Verifying that the proof was indeed verified - } - - #[test] - fn test_setup_error() { - // Arrange: Create a new CircomState instance - let mut circom_state = CircomState::new(); - - let wasm_path = "badpath/multiplier2.wasm"; - let r1cs_path = "badpath/multiplier2.r1cs"; - - // Act: Call the setup method - let result = circom_state.setup(wasm_path, r1cs_path); - - // Assert: Check that the method returns an error - assert!(result.is_err()); - } - - #[cfg(feature = "dylib")] - #[test] - fn test_dylib_init_and_generate_witness() { - // Assumes that the dylib file has been built and is in the following location - let dylib_path = "target/debug/aarch64-apple-darwin/keccak256.dylib"; - - // Initialize libray - initialize(Path::new(&dylib_path)); - - let input_vec = vec![ - 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ]; - - let inputs = bytes_to_circuit_inputs(&input_vec); - let now = std::time::Instant::now(); - let full_assignment = witness_calculator() - .lock() - .expect("Failed to lock witness calculator") - .calculate_witness_element::(inputs, false) - .map_err(|e| MoproError::CircomError(e.to_string())); - - println!("Witness generation took: {:.2?}", now.elapsed()); - - assert!(full_assignment.is_ok()); - } - - #[test] - fn test_generate_proof2() { - // XXX: This can be done better - #[cfg(feature = "dylib")] - { - // Assumes that the dylib file has been built and is in the following location - let dylib_path = "target/debug/aarch64-apple-darwin/keccak256.dylib"; - - // Initialize libray - initialize(Path::new(&dylib_path)); - } - - let input_vec = vec![ - 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ]; - let expected_output_vec = vec![ - 37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, 228, 211, 170, 133, 153, 9, 88, - 212, 4, 212, 175, 238, 249, 210, 214, 116, 170, 85, 45, 21, - ]; - let inputs = bytes_to_circuit_inputs(&input_vec); - let serialized_outputs = bytes_to_circuit_outputs(&expected_output_vec); - - let generate_proof_res = generate_proof2(inputs); - let (serialized_proof, serialized_inputs) = generate_proof_res.unwrap(); - assert_eq!(serialized_inputs, serialized_outputs); - - // Proof verification - let verify_res = verify_proof2(serialized_proof, serialized_inputs); - assert!(verify_res.is_ok()); - assert!(verify_res.unwrap()); // Verifying that the proof was indeed verified - } - - #[ignore = "ignore for ci"] - #[test] - fn test_setup_prove_rsa() { - let wasm_path = "./examples/circom/rsa/target/main_js/main.wasm"; - let r1cs_path = "./examples/circom/rsa/target/main.r1cs"; - - // Instantiate CircomState - let mut circom_state = CircomState::new(); - - // Setup - let setup_res = circom_state.setup(wasm_path, r1cs_path); - assert!(setup_res.is_ok()); - - let _serialized_pk = setup_res.unwrap(); - - // Deserialize the proving key and inputs if necessary - - // Prepare inputs - let signature = [ - "3582320600048169363", - "7163546589759624213", - "18262551396327275695", - "4479772254206047016", - "1970274621151677644", - "6547632513799968987", - "921117808165172908", - "7155116889028933260", - "16769940396381196125", - "17141182191056257954", - "4376997046052607007", - "17471823348423771450", - "16282311012391954891", - "70286524413490741", - "1588836847166444745", - "15693430141227594668", - "13832254169115286697", - "15936550641925323613", - "323842208142565220", - "6558662646882345749", - "15268061661646212265", - "14962976685717212593", - "15773505053543368901", - "9586594741348111792", - "1455720481014374292", - "13945813312010515080", - "6352059456732816887", - "17556873002865047035", - "2412591065060484384", - "11512123092407778330", - "8499281165724578877", - "12768005853882726493", - ]; - let modulus = [ - "13792647154200341559", - "12773492180790982043", - "13046321649363433702", - "10174370803876824128", - "7282572246071034406", - "1524365412687682781", - "4900829043004737418", - "6195884386932410966", - "13554217876979843574", - "17902692039595931737", - "12433028734895890975", - "15971442058448435996", - "4591894758077129763", - "11258250015882429548", - "16399550288873254981", - "8246389845141771315", - "14040203746442788850", - "7283856864330834987", - "12297563098718697441", - "13560928146585163504", - "7380926829734048483", - "14591299561622291080", - "8439722381984777599", - "17375431987296514829", - "16727607878674407272", - "3233954801381564296", - "17255435698225160983", - "15093748890170255670", - "15810389980847260072", - "11120056430439037392", - "5866130971823719482", - "13327552690270163501", - ]; - let base_message = [ - "18114495772705111902", - "2254271930739856077", - "2068851770", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - ]; - - let mut inputs: HashMap> = HashMap::new(); - inputs.insert( - "signature".to_string(), - strings_to_circuit_inputs(&signature), - ); - inputs.insert("modulus".to_string(), strings_to_circuit_inputs(&modulus)); - inputs.insert( - "base_message".to_string(), - strings_to_circuit_inputs(&base_message), - ); - - // Proof generation - let generate_proof_res = circom_state.generate_proof(inputs); - - // Check and print the error if there is one - if let Err(e) = &generate_proof_res { - println!("Error: {:?}", e); - } - - assert!(generate_proof_res.is_ok()); - - let (serialized_proof, serialized_inputs) = generate_proof_res.unwrap(); - - // Proof verification - let verify_res = circom_state.verify_proof(serialized_proof, serialized_inputs); - assert!(verify_res.is_ok()); - - assert!(verify_res.unwrap()); // Verifying that the proof was indeed verified - } - - #[ignore = "ignore for ci"] - #[test] - fn test_setup_prove_rsa2() { - // Prepare inputs - let signature = [ - "3582320600048169363", - "7163546589759624213", - "18262551396327275695", - "4479772254206047016", - "1970274621151677644", - "6547632513799968987", - "921117808165172908", - "7155116889028933260", - "16769940396381196125", - "17141182191056257954", - "4376997046052607007", - "17471823348423771450", - "16282311012391954891", - "70286524413490741", - "1588836847166444745", - "15693430141227594668", - "13832254169115286697", - "15936550641925323613", - "323842208142565220", - "6558662646882345749", - "15268061661646212265", - "14962976685717212593", - "15773505053543368901", - "9586594741348111792", - "1455720481014374292", - "13945813312010515080", - "6352059456732816887", - "17556873002865047035", - "2412591065060484384", - "11512123092407778330", - "8499281165724578877", - "12768005853882726493", - ]; - let modulus = [ - "13792647154200341559", - "12773492180790982043", - "13046321649363433702", - "10174370803876824128", - "7282572246071034406", - "1524365412687682781", - "4900829043004737418", - "6195884386932410966", - "13554217876979843574", - "17902692039595931737", - "12433028734895890975", - "15971442058448435996", - "4591894758077129763", - "11258250015882429548", - "16399550288873254981", - "8246389845141771315", - "14040203746442788850", - "7283856864330834987", - "12297563098718697441", - "13560928146585163504", - "7380926829734048483", - "14591299561622291080", - "8439722381984777599", - "17375431987296514829", - "16727607878674407272", - "3233954801381564296", - "17255435698225160983", - "15093748890170255670", - "15810389980847260072", - "11120056430439037392", - "5866130971823719482", - "13327552690270163501", - ]; - let base_message = [ - "18114495772705111902", - "2254271930739856077", - "2068851770", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - ]; - - let mut inputs: HashMap> = HashMap::new(); - inputs.insert( - "signature".to_string(), - strings_to_circuit_inputs(&signature), - ); - inputs.insert("modulus".to_string(), strings_to_circuit_inputs(&modulus)); - inputs.insert( - "base_message".to_string(), - strings_to_circuit_inputs(&base_message), - ); - - // Proof generation - let generate_proof_res = generate_proof2(inputs); - - // Check and print the error if there is one - if let Err(e) = &generate_proof_res { - println!("Error: {:?}", e); - } - - assert!(generate_proof_res.is_ok()); - - let (serialized_proof, serialized_inputs) = generate_proof_res.unwrap(); - - // Proof verification - let verify_res = verify_proof2(serialized_proof, serialized_inputs); - assert!(verify_res.is_ok()); - - assert!(verify_res.unwrap()); // Verifying that the proof was indeed verified - } -} diff --git a/app/mopro-core/src/middleware/circom/serialization.rs b/app/mopro-core/src/middleware/circom/serialization.rs deleted file mode 100644 index 47fa40088..000000000 --- a/app/mopro-core/src/middleware/circom/serialization.rs +++ /dev/null @@ -1,106 +0,0 @@ -use ark_bn254::Bn254; -use ark_ec::pairing::Pairing; -use ark_groth16::{Proof, ProvingKey}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use color_eyre::Result; - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -pub struct SerializableProvingKey(pub ProvingKey); - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)] -pub struct SerializableProof(pub Proof); - -#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)] -pub struct SerializableInputs(pub Vec<::ScalarField>); - -pub fn serialize_proof(proof: &SerializableProof) -> Vec { - let mut serialized_data = Vec::new(); - proof - .serialize_uncompressed(&mut serialized_data) - .expect("Serialization failed"); - serialized_data -} - -pub fn deserialize_proof(data: Vec) -> SerializableProof { - SerializableProof::deserialize_uncompressed(&mut &data[..]).expect("Deserialization failed") -} - -pub fn serialize_proving_key(pk: &SerializableProvingKey) -> Vec { - let mut serialized_data = Vec::new(); - pk.serialize_uncompressed(&mut serialized_data) - .expect("Serialization failed"); - serialized_data -} - -pub fn deserialize_proving_key(data: Vec) -> SerializableProvingKey { - SerializableProvingKey::deserialize_uncompressed(&mut &data[..]) - .expect("Deserialization failed") -} - -pub fn serialize_inputs(inputs: &SerializableInputs) -> Vec { - let mut serialized_data = Vec::new(); - inputs - .serialize_uncompressed(&mut serialized_data) - .expect("Serialization failed"); - serialized_data -} - -pub fn deserialize_inputs(data: Vec) -> SerializableInputs { - SerializableInputs::deserialize_uncompressed(&mut &data[..]).expect("Deserialization failed") -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::middleware::circom::serialization::SerializableProvingKey; - use crate::middleware::circom::utils::assert_paths_exists; - use crate::MoproError; - use ark_bn254::Bn254; - use ark_circom::{CircomBuilder, CircomConfig}; - use ark_groth16::Groth16; - use ark_std::rand::thread_rng; - use color_eyre::Result; - - type GrothBn = Groth16; - - fn generate_serializable_proving_key( - wasm_path: &str, - r1cs_path: &str, - ) -> Result { - assert_paths_exists(wasm_path, r1cs_path)?; - - let cfg = CircomConfig::::new(wasm_path, r1cs_path) - .map_err(|e| MoproError::CircomError(e.to_string()))?; - - let builder = CircomBuilder::new(cfg); - let circom = builder.setup(); - - let mut rng = thread_rng(); - let raw_params = GrothBn::generate_random_parameters_with_reduction(circom, &mut rng) - .map_err(|e| MoproError::CircomError(e.to_string()))?; - - Ok(SerializableProvingKey(raw_params)) - } - - #[test] - fn test_serialization_deserialization() { - let wasm_path = "./examples/circom/multiplier2/target/multiplier2_js/multiplier2.wasm"; - let r1cs_path = "./examples/circom/multiplier2/target/multiplier2.r1cs"; - - // Generate a serializable proving key for testing - let serializable_pk = generate_serializable_proving_key(wasm_path, r1cs_path) - .expect("Failed to generate serializable proving key"); - - // Serialize - let serialized_data = serialize_proving_key(&serializable_pk); - - // Deserialize - let deserialized_pk = deserialize_proving_key(serialized_data); - - // Assert that the original and deserialized ProvingKeys are the same - assert_eq!( - serializable_pk.0, deserialized_pk.0, - "Original and deserialized proving keys do not match" - ); - } -} diff --git a/app/mopro-core/src/middleware/circom/utils.rs b/app/mopro-core/src/middleware/circom/utils.rs deleted file mode 100644 index 31d6122c0..000000000 --- a/app/mopro-core/src/middleware/circom/utils.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::MoproError; - -use std::path::Path; - -pub fn assert_paths_exists(wasm_path: &str, r1cs_path: &str) -> Result<(), MoproError> { - // Check that the files exist - ark-circom should probably do this instead and not panic - if !Path::new(wasm_path).exists() { - return Err(MoproError::CircomError(format!( - "Path does not exist: {}", - wasm_path - ))); - } - - if !Path::new(r1cs_path).exists() { - return Err(MoproError::CircomError(format!( - "Path does not exist: {}", - r1cs_path - ))); - }; - - Ok(()) -} - -pub fn bytes_to_bits(bytes: &[u8]) -> Vec { - let mut bits = Vec::new(); - for &byte in bytes { - for j in 0..8 { - let bit = (byte >> j) & 1; - bits.push(bit == 1); - } - } - bits -} diff --git a/app/mopro-core/src/middleware/mod.rs b/app/mopro-core/src/middleware/mod.rs deleted file mode 100644 index d18079a10..000000000 --- a/app/mopro-core/src/middleware/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod circom; diff --git a/app/mopro-ffi/.gitignore b/app/mopro-ffi/.gitignore deleted file mode 100644 index 8648243db..000000000 --- a/app/mopro-ffi/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -# kotlin generated file -jniLibs/ -src/uniffi/mopro/ \ No newline at end of file diff --git a/app/mopro-ffi/Cargo.toml b/app/mopro-ffi/Cargo.toml deleted file mode 100644 index e8fbd1111..000000000 --- a/app/mopro-ffi/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "mopro-ffi" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["lib", "cdylib", "staticlib"] -name = "mopro_ffi" - -[[bin]] -name = "uniffi-bindgen" -path = "uniffi-bindgen.rs" - -[features] -default = [] - -# If we enable dylib here it should be enabled in mopro-core as well -dylib = ["mopro-core/dylib"] - -[patch.crates-io] -# NOTE: Forked wasmer to work around memory limits -# See https://github.com/wasmerio/wasmer/commit/09c7070 -wasmer = { git = "https://github.com/oskarth/wasmer.git", rev = "09c7070" } - -[dependencies] -mopro-core = { path = "../mopro-core" } -uniffi = { version = "0.25", features = ["cli"] } -serde = { version = "1", features = ["derive"] } -bincode = "1" -ark-serialize = { version = "=0.4.1", features = ["derive"] } -num-bigint = { version = "=0.4.3", default-features = false, features = [ - "rand", -] } - -# Error handling -thiserror = "=1.0.39" -color-eyre = "=0.6.2" -criterion = "=0.3.6" - -[build-dependencies] -uniffi = { version = "0.25", features = ["build"] } - -[dev-dependencies] -uniffi = { version = "0.25", features = ["bindgen-tests"] } -ark-bn254 = { version = "=0.4.0" } diff --git a/app/mopro-ffi/Makefile b/app/mopro-ffi/Makefile deleted file mode 100644 index 072c25bb8..000000000 --- a/app/mopro-ffi/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -TARGETS = x86_64-apple-ios aarch64-apple-ios aarch64-apple-ios-sim -BUILD_TYPES = debug release - -all: $(BUILD_TYPES) - -debug: $(TARGETS) - for target in $(TARGETS); do \ - cargo build --target $$target; \ - done - -release: - for target in $(TARGETS); do \ - cargo build --release --target $$target; \ - done - -.PHONY: all $(BUILD_TYPES) $(TARGETS) \ No newline at end of file diff --git a/app/mopro-ffi/README.md b/app/mopro-ffi/README.md deleted file mode 100644 index ec8eb6e62..000000000 --- a/app/mopro-ffi/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# mopro-ffi - -Thin wrapper around `mopro-core`, exposes UniFFI bindings to be used by `rust-ios`, etc. - -## Overview - -TBD. - -## Development - -### Prerequisites - -1. Ensure you have Rust installed -2. Add platform targets `rustup target add x86_64-apple-ios aarch64-apple-ios aarch64-apple-ios-sim` -3. Install `uniffi-bindgen` locally with `cargo install --bin uniffi-bindgen --path .` -4. In order to locally run the bindings tests, you will need - * Kotlin: - * `kotlinc`, the [Kotlin command-line compiler](https://kotlinlang.org/docs/command-line.html). - * `ktlint`, the [Kotlin linter used to format the generated bindings](https://ktlint.github.io/). - * The [Java Native Access](https://github.com/java-native-access/jna#download) JAR downloaded and its path - added to your `$CLASSPATH` environment variable. - * Swift: - * `swift` and `swiftc`, the [Swift command-line tools](https://swift.org/download/). - * The Swift `Foundation` package. - -### Platforms supported - -Currently iOS is the main target, but Android will soon follow. PRs welcome. - -### Building - -Run `make` to build debug and release static libraries for supported platforms. - -### Generate UniFFI bindings - -The following command generates Swift bindings: - -`uniffi-bindgen generate src/mopro.udl --language swift --out-dir target/SwiftBindings` - -## Test bindings - -To test bindings: - -`cargo test --test test_generated_bindings` - -To test bindings in release mode without warning: - -`cargo test --test test_generated_bindings --release 2>/dev/null` diff --git a/app/mopro-ffi/build.rs b/app/mopro-ffi/build.rs deleted file mode 100644 index ca4b249f7..000000000 --- a/app/mopro-ffi/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - uniffi::generate_scaffolding("src/mopro.udl").expect("Building the UDL file failed"); -} diff --git a/app/mopro-ffi/src/lib.rs b/app/mopro-ffi/src/lib.rs deleted file mode 100644 index 94c5c9b1b..000000000 --- a/app/mopro-ffi/src/lib.rs +++ /dev/null @@ -1,295 +0,0 @@ -use mopro_core::middleware::circom; -use mopro_core::MoproError; - -use num_bigint::BigInt; -use std::collections::HashMap; -use std::path::Path; -use std::str::FromStr; -use std::sync::RwLock; - -#[derive(Debug)] -pub enum FFIError { - MoproError(mopro_core::MoproError), - SerializationError(String), -} - -#[derive(Debug, Clone)] -pub struct GenerateProofResult { - pub proof: Vec, - pub inputs: Vec, -} - -// NOTE: Make UniFFI and Rust happy, can maybe do some renaming here -#[allow(non_snake_case)] -#[derive(Debug, Clone)] -pub struct SetupResult { - pub provingKey: Vec, -} - -// pub inputs: Vec, - -impl From for FFIError { - fn from(error: mopro_core::MoproError) -> Self { - FFIError::MoproError(error) - } -} - -pub struct MoproCircom { - state: RwLock, -} - -impl Default for MoproCircom { - fn default() -> Self { - Self::new() - } -} - -#[cfg(not(feature = "dylib"))] -pub fn initialize_mopro() -> Result<(), MoproError> { - // TODO: Error handle / panic? - circom::initialize(); - Ok(()) -} - -#[cfg(feature = "dylib")] -pub fn initialize_mopro() -> Result<(), MoproError> { - println!("need to use dylib to init!"); - panic!("need to use dylib to init!"); -} - -#[cfg(feature = "dylib")] -pub fn initialize_mopro_dylib(dylib_path: String) -> Result<(), MoproError> { - // TODO: Error handle / panic? - let dylib_path = Path::new(dylib_path.as_str()); - circom::initialize(dylib_path); - Ok(()) -} - -#[cfg(not(feature = "dylib"))] -pub fn initialize_mopro_dylib(dylib_path: String) -> Result<(), MoproError> { - println!("dylib feature not enabled!"); - panic!("dylib feature not enabled!"); -} - -pub fn generate_proof2( - inputs: HashMap>, -) -> Result { - // Convert inputs to BigInt - let bigint_inputs = inputs - .into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(|i| BigInt::from_str(&i).unwrap()) - .collect(), - ) - }) - .collect(); - - let (proof, inputs) = circom::generate_proof2(bigint_inputs)?; - - let serialized_proof = circom::serialization::serialize_proof(&proof); - let serialized_inputs = circom::serialization::serialize_inputs(&inputs); - Ok(GenerateProofResult { - proof: serialized_proof, - inputs: serialized_inputs, - }) -} - -pub fn verify_proof2(proof: Vec, public_input: Vec) -> Result { - let deserialized_proof = circom::serialization::deserialize_proof(proof); - let deserialized_public_input = circom::serialization::deserialize_inputs(public_input); - let is_valid = circom::verify_proof2(deserialized_proof, deserialized_public_input)?; - Ok(is_valid) -} - -// TODO: Use FFIError::SerializationError instead -impl MoproCircom { - pub fn new() -> Self { - Self { - state: RwLock::new(circom::CircomState::new()), - } - } - - pub fn setup(&self, wasm_path: String, r1cs_path: String) -> Result { - let mut state_guard = self.state.write().unwrap(); - let pk = state_guard.setup(wasm_path.as_str(), r1cs_path.as_str())?; - Ok(SetupResult { - provingKey: circom::serialization::serialize_proving_key(&pk), - }) - } - - // inputs: circom::serialization::serialize_inputs(&inputs), - - pub fn generate_proof( - &self, - inputs: HashMap>, - ) -> Result { - let mut state_guard = self.state.write().unwrap(); - - // Convert inputs to BigInt - let bigint_inputs = inputs - .into_iter() - .map(|(k, v)| { - ( - k, - v.into_iter() - .map(|i| BigInt::from_str(&i).unwrap()) - .collect(), - ) - }) - .collect(); - - let (proof, inputs) = state_guard.generate_proof(bigint_inputs)?; - - Ok(GenerateProofResult { - proof: circom::serialization::serialize_proof(&proof), - inputs: circom::serialization::serialize_inputs(&inputs), - }) - } - - pub fn verify_proof(&self, proof: Vec, public_input: Vec) -> Result { - let state_guard = self.state.read().unwrap(); - let deserialized_proof = circom::serialization::deserialize_proof(proof); - let deserialized_public_input = circom::serialization::deserialize_inputs(public_input); - let is_valid = state_guard.verify_proof(deserialized_proof, deserialized_public_input)?; - Ok(is_valid) - } -} - -fn add(a: u32, b: u32) -> u32 { - a + b -} - -fn hello() -> String { - "Hello World from Rust".to_string() -} - -// TODO: Remove me -// UniFFI expects String type -// See https://mozilla.github.io/uniffi-rs/udl/builtin_types.html -// fn run_example(wasm_path: String, r1cs_path: String) -> Result<(), MoproError> { -// circom::run_example(wasm_path.as_str(), r1cs_path.as_str()) -// } - -uniffi::include_scaffolding!("mopro"); - -#[cfg(test)] -mod tests { - use super::*; - use ark_bn254::Fr; - use num_bigint::BigUint; - - fn bytes_to_circuit_inputs(input_vec: &Vec) -> HashMap> { - let bits = circom::utils::bytes_to_bits(&input_vec); - let converted_vec: Vec = bits - .into_iter() - .map(|bit| (bit as i32).to_string()) - .collect(); - let mut inputs = HashMap::new(); - inputs.insert("in".to_string(), converted_vec); - inputs - } - - fn bytes_to_circuit_outputs(bytes: &[u8]) -> Vec { - let bits = circom::utils::bytes_to_bits(bytes); - let field_bits = bits.into_iter().map(|bit| Fr::from(bit as u8)).collect(); - let circom_outputs = circom::serialization::SerializableInputs(field_bits); - circom::serialization::serialize_inputs(&circom_outputs) - } - - #[test] - fn add_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } - - #[test] - fn test_end_to_end() -> Result<(), MoproError> { - // Paths to your wasm and r1cs files - let wasm_path = - "./../mopro-core/examples/circom/multiplier2/target/multiplier2_js/multiplier2.wasm"; - let r1cs_path = "./../mopro-core/examples/circom/multiplier2/target/multiplier2.r1cs"; - - // Create a new MoproCircom instance - let mopro_circom = MoproCircom::new(); - - // Step 1: Setup - let setup_result = mopro_circom.setup(wasm_path.to_string(), r1cs_path.to_string())?; - assert!(setup_result.provingKey.len() > 0); - - let mut inputs = HashMap::new(); - let a = BigUint::from_str( - "21888242871839275222246405745257275088548364400416034343698204186575808495616", - ) - .unwrap(); - let b = BigUint::from(1u8); - let c = a.clone() * b.clone(); - inputs.insert("a".to_string(), vec![a.to_string()]); - inputs.insert("b".to_string(), vec![b.to_string()]); - // output = [public output c, public input a] - let expected_output = vec![Fr::from(c), Fr::from(a)]; - let circom_outputs = circom::serialization::SerializableInputs(expected_output); - let serialized_outputs = circom::serialization::serialize_inputs(&circom_outputs); - - // Step 2: Generate Proof - let generate_proof_result = mopro_circom.generate_proof(inputs)?; - let serialized_proof = generate_proof_result.proof; - let serialized_inputs = generate_proof_result.inputs; - - assert!(serialized_proof.len() > 0); - assert_eq!(serialized_inputs, serialized_outputs); - - // Step 3: Verify Proof - let is_valid = mopro_circom.verify_proof(serialized_proof, serialized_inputs)?; - assert!(is_valid); - - Ok(()) - } - - #[test] - fn test_end_to_end_keccak() -> Result<(), MoproError> { - // Paths to your wasm and r1cs files - let wasm_path = - "./../mopro-core/examples/circom/keccak256/target/keccak256_256_test_js/keccak256_256_test.wasm"; - let r1cs_path = "./../mopro-core/examples/circom/keccak256/target/keccak256_256_test.r1cs"; - - // Create a new MoproCircom instance - let mopro_circom = MoproCircom::new(); - - // Step 1: Setup - let setup_result = mopro_circom.setup(wasm_path.to_string(), r1cs_path.to_string())?; - assert!(setup_result.provingKey.len() > 0); - - // Prepare inputs - let input_vec = vec![ - 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ]; - - // Expected output - let expected_output_vec = vec![ - 37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, 228, 211, 170, 133, 153, 9, 88, - 212, 4, 212, 175, 238, 249, 210, 214, 116, 170, 85, 45, 21, - ]; - - let inputs = bytes_to_circuit_inputs(&input_vec); - let serialized_outputs = bytes_to_circuit_outputs(&expected_output_vec); - - // Step 2: Generate Proof - let generate_proof_result = mopro_circom.generate_proof(inputs)?; - let serialized_proof = generate_proof_result.proof; - let serialized_inputs = generate_proof_result.inputs; - - assert!(serialized_proof.len() > 0); - assert_eq!(serialized_inputs, serialized_outputs); - - // Step 3: Verify Proof - - let is_valid = mopro_circom.verify_proof(serialized_proof, serialized_inputs)?; - assert!(is_valid); - - Ok(()) - } -} diff --git a/app/mopro-ffi/src/mopro.udl b/app/mopro-ffi/src/mopro.udl deleted file mode 100644 index 875a5b1b8..000000000 --- a/app/mopro-ffi/src/mopro.udl +++ /dev/null @@ -1,43 +0,0 @@ -namespace mopro { - u32 add(u32 a, u32 b); - string hello(); - - [Throws=MoproError] - void initialize_mopro(); - - [Throws=MoproError] - void initialize_mopro_dylib(string dylib_path); - - [Throws=MoproError] - GenerateProofResult generate_proof2(record> circuit_inputs); - - [Throws=MoproError] - boolean verify_proof2(bytes proof, bytes public_input); -}; - -dictionary SetupResult { - bytes provingKey; -}; - -dictionary GenerateProofResult { - bytes proof; - bytes inputs; -}; - -[Error] -enum MoproError { - "CircomError", -}; - -interface MoproCircom { - constructor(); - - [Throws=MoproError] - SetupResult setup(string wasm_path, string r1cs_path); - - [Throws=MoproError] - GenerateProofResult generate_proof(record> circuit_inputs); - - [Throws=MoproError] - boolean verify_proof(bytes proof, bytes public_input); -}; diff --git a/app/mopro-ffi/tests/bindings/test_mopro.kts b/app/mopro-ffi/tests/bindings/test_mopro.kts deleted file mode 100644 index 838e72fe2..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro.kts +++ /dev/null @@ -1,21 +0,0 @@ -import uniffi.mopro.* - -var wasmPath = "../mopro-core/examples/circom/multiplier2/target/multiplier2_js/multiplier2.wasm" -var r1csPath = "../mopro-core/examples/circom/multiplier2/target/multiplier2.r1cs" - -try { - var moproCircom = MoproCircom() - var setupResult = moproCircom.setup(wasmPath, r1csPath) - assert(setupResult.provingKey.size > 0) { "Proving key should not be empty" } - - val inputs = mutableMapOf>() - inputs["a"] = listOf("3") - inputs["b"] = listOf("5") - - var generateProofResult = moproCircom.generateProof(inputs) - assert(generateProofResult.proof.size > 0) { "Proof is empty" } - var isValid = moproCircom.verifyProof(generateProofResult.proof, generateProofResult.inputs) - assert(isValid) { "Proof is invalid" } -} catch (e: Exception) { - println(e) -} diff --git a/app/mopro-ffi/tests/bindings/test_mopro.swift b/app/mopro-ffi/tests/bindings/test_mopro.swift deleted file mode 100644 index 7193618a7..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro.swift +++ /dev/null @@ -1,65 +0,0 @@ -import mopro -import Foundation - -let moproCircom = MoproCircom() - -let wasmPath = "./../../../../mopro-core/examples/circom/multiplier2/target/multiplier2_js/multiplier2.wasm" -let r1csPath = "./../../../../mopro-core/examples/circom/multiplier2/target/multiplier2.r1cs" - -func serializeOutputs(_ stringArray: [String]) -> [UInt8] { - var bytesArray: [UInt8] = [] - let length = stringArray.count - var littleEndianLength = length.littleEndian - let targetLength = 32 - withUnsafeBytes(of: &littleEndianLength) { - bytesArray.append(contentsOf: $0) - } - for value in stringArray { - // TODO: should handle 254-bit input - var littleEndian = Int32(value)!.littleEndian - var byteLength = 0 - withUnsafeBytes(of: &littleEndian) { - bytesArray.append(contentsOf: $0) - byteLength = byteLength + $0.count - } - if byteLength < targetLength { - let paddingCount = targetLength - byteLength - let paddingArray = [UInt8](repeating: 0, count: paddingCount) - bytesArray.append(contentsOf: paddingArray) - } - } - return bytesArray -} - -do { - // Setup - let setupResult = try moproCircom.setup(wasmPath: wasmPath, r1csPath: r1csPath) - assert(!setupResult.provingKey.isEmpty, "Proving key should not be empty") - - // Prepare inputs - var inputs = [String: [String]]() - let a = 3 - let b = 5 - let c = a*b - inputs["a"] = [String(a)] - inputs["b"] = [String(b)] - - // Expected outputs - let outputs: [String] = [String(c), String(a)] - let expectedOutput: [UInt8] = serializeOutputs(outputs) - - // Generate Proof - let generateProofResult = try moproCircom.generateProof(circuitInputs: inputs) - assert(!generateProofResult.proof.isEmpty, "Proof should not be empty") - - // Verify Proof - assert(Data(expectedOutput) == generateProofResult.inputs, "Circuit outputs mismatch the expected outputs") - - let isValid = try moproCircom.verifyProof(proof: generateProofResult.proof, publicInput: generateProofResult.inputs) - assert(isValid, "Proof verification should succeed") - -} catch let error as MoproError { - print("MoproError: \(error)") -} catch { - print("Unexpected error: \(error)") -} diff --git a/app/mopro-ffi/tests/bindings/test_mopro_keccak.kts b/app/mopro-ffi/tests/bindings/test_mopro_keccak.kts deleted file mode 100644 index 5fb5b30a8..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro_keccak.kts +++ /dev/null @@ -1,279 +0,0 @@ -import uniffi.mopro.* - -var wasmPath = - "../mopro-core/examples/circom/keccak256/target/keccak256_256_test_js/keccak256_256_test.wasm" -var r1csPath = "../mopro-core/examples/circom/keccak256/target/keccak256_256_test.r1cs" - -try { - var moproCircom = MoproCircom() - var setupResult = moproCircom.setup(wasmPath, r1csPath) - assert(setupResult.provingKey.size > 0) { "Proving key should not be empty" } - - val inputs = mutableMapOf>() - inputs["in"] = - listOf( - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "1", - "0", - "1", - "0", - "0", - "1", - "1", - "0", - "1", - "1", - "0", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0" - ) - - var generateProofResult = moproCircom.generateProof(inputs) - assert(generateProofResult.proof.size > 0) { "Proof is empty" } - var isValid = moproCircom.verifyProof(generateProofResult.proof, generateProofResult.inputs) - assert(isValid) { "Proof is invalid" } -} catch (e: Exception) { - println(e) -} diff --git a/app/mopro-ffi/tests/bindings/test_mopro_keccak.swift b/app/mopro-ffi/tests/bindings/test_mopro_keccak.swift deleted file mode 100644 index 2ca7dc476..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro_keccak.swift +++ /dev/null @@ -1,82 +0,0 @@ -import mopro -import Foundation - -let moproCircom = MoproCircom() - -let wasmPath = "./../../../../mopro-core/examples/circom/keccak256/target/keccak256_256_test_js/keccak256_256_test.wasm" -let r1csPath = "./../../../../mopro-core/examples/circom/keccak256/target/keccak256_256_test.r1cs" - -// Helper function to convert bytes to bits -func bytesToBits(bytes: [UInt8]) -> [String] { - var bits = [String]() - for byte in bytes { - for j in 0..<8 { - let bit = (byte >> j) & 1 - bits.append(String(bit)) - } - } - return bits -} - -func serializeOutputs(_ stringArray: [String]) -> [UInt8] { - var bytesArray: [UInt8] = [] - let length = stringArray.count - var littleEndianLength = length.littleEndian - let targetLength = 32 - withUnsafeBytes(of: &littleEndianLength) { - bytesArray.append(contentsOf: $0) - } - for value in stringArray { - // TODO: should handle 254-bit input - var littleEndian = Int32(value)!.littleEndian - var byteLength = 0 - withUnsafeBytes(of: &littleEndian) { - bytesArray.append(contentsOf: $0) - byteLength = byteLength + $0.count - } - if byteLength < targetLength { - let paddingCount = targetLength - byteLength - let paddingArray = [UInt8](repeating: 0, count: paddingCount) - bytesArray.append(contentsOf: paddingArray) - } - } - return bytesArray -} - -do { - // Setup - let setupResult = try moproCircom.setup(wasmPath: wasmPath, r1csPath: r1csPath) - assert(!setupResult.provingKey.isEmpty, "Proving key should not be empty") - - // Prepare inputs - let inputVec: [UInt8] = [ - 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ] - let bits = bytesToBits(bytes: inputVec) - var inputs = [String: [String]]() - inputs["in"] = bits - - // Expected outputs - let outputVec: [UInt8] = [ - 37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, 228, 211, 170, 133, 153, 9, 88, - 212, 4, 212, 175, 238, 249, 210, 214, 116, 170, 85, 45, 21, - ] - let outputBits: [String] = bytesToBits(bytes: outputVec) - let expectedOutput: [UInt8] = serializeOutputs(outputBits) - - // Generate Proof - let generateProofResult = try moproCircom.generateProof(circuitInputs: inputs) - assert(!generateProofResult.proof.isEmpty, "Proof should not be empty") - - // Verify Proof - assert(Data(expectedOutput) == generateProofResult.inputs, "Circuit outputs mismatch the expected outputs") - - let isValid = try moproCircom.verifyProof(proof: generateProofResult.proof, publicInput: generateProofResult.inputs) - assert(isValid, "Proof verification should succeed") - -} catch let error as MoproError { - print("MoproError: \(error)") -} catch { - print("Unexpected error: \(error)") -} diff --git a/app/mopro-ffi/tests/bindings/test_mopro_keccak2.kts b/app/mopro-ffi/tests/bindings/test_mopro_keccak2.kts deleted file mode 100644 index 832628df9..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro_keccak2.kts +++ /dev/null @@ -1,273 +0,0 @@ -import uniffi.mopro.* - -try { - initializeMopro() - - val inputs = mutableMapOf>() - inputs["in"] = - listOf( - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "1", - "0", - "1", - "0", - "0", - "1", - "1", - "0", - "1", - "1", - "0", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0" - ) - - var generateProofResult = generateProof2(inputs) - assert(generateProofResult.proof.size > 0) { "Proof is empty" } - var isValid = verifyProof2(generateProofResult.proof, generateProofResult.inputs) - assert(isValid) { "Proof is invalid" } -} catch (e: Exception) { - println(e) -} diff --git a/app/mopro-ffi/tests/bindings/test_mopro_keccak2.swift b/app/mopro-ffi/tests/bindings/test_mopro_keccak2.swift deleted file mode 100644 index a489bc98c..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro_keccak2.swift +++ /dev/null @@ -1,86 +0,0 @@ -import Foundation -import mopro - -//let moproCircom = MoproCircom() - -// Using zkey and generate_proof2 - -// let wasmPath = "./../../../../mopro-core/examples/circom/keccak256/target/keccak256_256_test_js/keccak256_256_test.wasm" -// let r1csPath = "./../../../../mopro-core/examples/circom/keccak256/target/keccak256_256_test.r1cs" - -// Helper function to convert bytes to bits -func bytesToBits(bytes: [UInt8]) -> [String] { - var bits = [String]() - for byte in bytes { - for j in 0..<8 { - let bit = (byte >> j) & 1 - bits.append(String(bit)) - } - } - return bits -} - -func serializeOutputs(_ stringArray: [String]) -> [UInt8] { - var bytesArray: [UInt8] = [] - let length = stringArray.count - var littleEndianLength = length.littleEndian - let targetLength = 32 - withUnsafeBytes(of: &littleEndianLength) { - bytesArray.append(contentsOf: $0) - } - for value in stringArray { - // TODO: should handle 254-bit input - var littleEndian = Int32(value)!.littleEndian - var byteLength = 0 - withUnsafeBytes(of: &littleEndian) { - bytesArray.append(contentsOf: $0) - byteLength = byteLength + $0.count - } - if byteLength < targetLength { - let paddingCount = targetLength - byteLength - let paddingArray = [UInt8](repeating: 0, count: paddingCount) - bytesArray.append(contentsOf: paddingArray) - } - } - return bytesArray -} - -do { - // // Setup - // let setupResult = try moproCircom.setup(wasmPath: wasmPath, r1csPath: r1csPath) - // assert(!setupResult.provingKey.isEmpty, "Proving key should not be empty") - - // Prepare inputs - let inputVec: [UInt8] = [ - 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - ] - let bits = bytesToBits(bytes: inputVec) - var inputs = [String: [String]]() - inputs["in"] = bits - - // Expected outputs - let outputVec: [UInt8] = [ - 37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, 228, 211, 170, 133, 153, 9, 88, - 212, 4, 212, 175, 238, 249, 210, 214, 116, 170, 85, 45, 21, - ] - let outputBits: [String] = bytesToBits(bytes: outputVec) - let expectedOutput: [UInt8] = serializeOutputs(outputBits) - - // // Generate Proof - let generateProofResult = try generateProof2(circuitInputs: inputs) - // let generateProofResult = try moproCircom.generateProof(circuitInputs: inputs) - assert(!generateProofResult.proof.isEmpty, "Proof should not be empty") - - // // Verify Proof - assert(Data(expectedOutput) == generateProofResult.inputs, "Circuit outputs mismatch the expected outputs") - - let isValid = try verifyProof2( - proof: generateProofResult.proof, publicInput: generateProofResult.inputs) - assert(isValid, "Proof verification should succeed") - -} catch let error as MoproError { - print("MoproError: \(error)") -} catch { - print("Unexpected error: \(error)") -} diff --git a/app/mopro-ffi/tests/bindings/test_mopro_rsa.kts b/app/mopro-ffi/tests/bindings/test_mopro_rsa.kts deleted file mode 100644 index 15e770f08..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro_rsa.kts +++ /dev/null @@ -1,115 +0,0 @@ -import uniffi.mopro.*; - -var wasmPath = "../mopro-core/examples/circom/rsa/target/main_js/main.wasm" -var r1csPath = "../mopro-core/examples/circom/rsa/target/main.r1cs" - -try { - var moproCircom = MoproCircom() - var setupResult = moproCircom.setup(wasmPath, r1csPath) - assert(setupResult.provingKey.size > 0) { "Proving key should not be empty"} - - val inputs = mutableMapOf>() - inputs["signature"] = listOf("3582320600048169363", - "7163546589759624213", - "18262551396327275695", - "4479772254206047016", - "1970274621151677644", - "6547632513799968987", - "921117808165172908", - "7155116889028933260", - "16769940396381196125", - "17141182191056257954", - "4376997046052607007", - "17471823348423771450", - "16282311012391954891", - "70286524413490741", - "1588836847166444745", - "15693430141227594668", - "13832254169115286697", - "15936550641925323613", - "323842208142565220", - "6558662646882345749", - "15268061661646212265", - "14962976685717212593", - "15773505053543368901", - "9586594741348111792", - "1455720481014374292", - "13945813312010515080", - "6352059456732816887", - "17556873002865047035", - "2412591065060484384", - "11512123092407778330", - "8499281165724578877", - "12768005853882726493") - inputs["modulus"] = listOf("13792647154200341559", - "12773492180790982043", - "13046321649363433702", - "10174370803876824128", - "7282572246071034406", - "1524365412687682781", - "4900829043004737418", - "6195884386932410966", - "13554217876979843574", - "17902692039595931737", - "12433028734895890975", - "15971442058448435996", - "4591894758077129763", - "11258250015882429548", - "16399550288873254981", - "8246389845141771315", - "14040203746442788850", - "7283856864330834987", - "12297563098718697441", - "13560928146585163504", - "7380926829734048483", - "14591299561622291080", - "8439722381984777599", - "17375431987296514829", - "16727607878674407272", - "3233954801381564296", - "17255435698225160983", - "15093748890170255670", - "15810389980847260072", - "11120056430439037392", - "5866130971823719482", - "13327552690270163501",) - inputs["base_message"] = listOf("18114495772705111902", - "2254271930739856077", - "2068851770", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0",) - - var generateProofResult = moproCircom.generateProof(inputs) - assert(generateProofResult.proof.size > 0) { "Proof is empty"} - var isValid = moproCircom.verifyProof(generateProofResult.proof, generateProofResult.inputs) - assert(isValid) { "Proof is invalid"} -} catch (e: Exception) { - println(e); -} diff --git a/app/mopro-ffi/tests/bindings/test_mopro_rsa.swift b/app/mopro-ffi/tests/bindings/test_mopro_rsa.swift deleted file mode 100644 index 917fbbe0f..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro_rsa.swift +++ /dev/null @@ -1,174 +0,0 @@ -import mopro -import Foundation - -let moproCircom = MoproCircom() - -let wasmPath = "./../../../../mopro-core/examples/circom/rsa/target/main_js/main.wasm" -let r1csPath = "./../../../../mopro-core/examples/circom/rsa/target/main.r1cs" - -// Helper function to convert bytes to bits -func bytesToBits(bytes: [UInt8]) -> [String] { - var bits = [String]() - for byte in bytes { - for j in 0..<8 { - let bit = (byte >> j) & 1 - bits.append(String(bit)) - } - } - return bits -} - -func serializeOutputs(_ stringArray: [String]) -> [UInt8] { - var bytesArray: [UInt8] = [] - let length = stringArray.count - var littleEndianLength = length.littleEndian - let targetLength = 32 - withUnsafeBytes(of: &littleEndianLength) { - bytesArray.append(contentsOf: $0) - } - for value in stringArray { - // TODO: should handle 254-bit input - var littleEndian = Int32(value)!.littleEndian - var byteLength = 0 - withUnsafeBytes(of: &littleEndian) { - bytesArray.append(contentsOf: $0) - byteLength = byteLength + $0.count - } - if byteLength < targetLength { - let paddingCount = targetLength - byteLength - let paddingArray = [UInt8](repeating: 0, count: paddingCount) - bytesArray.append(contentsOf: paddingArray) - } - } - return bytesArray -} - -do { - // Setup - let setupResult = try moproCircom.setup(wasmPath: wasmPath, r1csPath: r1csPath) - assert(!setupResult.provingKey.isEmpty, "Proving key should not be empty") - - // Prepare inputs - let signature: [String] = [ - "3582320600048169363", - "7163546589759624213", - "18262551396327275695", - "4479772254206047016", - "1970274621151677644", - "6547632513799968987", - "921117808165172908", - "7155116889028933260", - "16769940396381196125", - "17141182191056257954", - "4376997046052607007", - "17471823348423771450", - "16282311012391954891", - "70286524413490741", - "1588836847166444745", - "15693430141227594668", - "13832254169115286697", - "15936550641925323613", - "323842208142565220", - "6558662646882345749", - "15268061661646212265", - "14962976685717212593", - "15773505053543368901", - "9586594741348111792", - "1455720481014374292", - "13945813312010515080", - "6352059456732816887", - "17556873002865047035", - "2412591065060484384", - "11512123092407778330", - "8499281165724578877", - "12768005853882726493", - ] - - let modulus: [String] = [ - "13792647154200341559", - "12773492180790982043", - "13046321649363433702", - "10174370803876824128", - "7282572246071034406", - "1524365412687682781", - "4900829043004737418", - "6195884386932410966", - "13554217876979843574", - "17902692039595931737", - "12433028734895890975", - "15971442058448435996", - "4591894758077129763", - "11258250015882429548", - "16399550288873254981", - "8246389845141771315", - "14040203746442788850", - "7283856864330834987", - "12297563098718697441", - "13560928146585163504", - "7380926829734048483", - "14591299561622291080", - "8439722381984777599", - "17375431987296514829", - "16727607878674407272", - "3233954801381564296", - "17255435698225160983", - "15093748890170255670", - "15810389980847260072", - "11120056430439037392", - "5866130971823719482", - "13327552690270163501", - ] - let base_message: [String] = [ - "18114495772705111902", - "2254271930739856077", - "2068851770", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - ] - - var inputs = [String: [String]]() - inputs["signature"] = signature; - inputs["modulus"] = modulus; - inputs["base_message"] = base_message; - - - // Generate Proof - let generateProofResult = try moproCircom.generateProof(circuitInputs: inputs) - assert(!generateProofResult.proof.isEmpty, "Proof should not be empty") - - // Verifying the Proof - let isValid = try moproCircom.verifyProof(proof: generateProofResult.proof, publicInput: generateProofResult.inputs) - assert(isValid, "Proof verification should succeed") - -} catch let error as MoproError { - print("MoproError: \(error)") -} catch { - print("Unexpected error: \(error)") -} diff --git a/app/mopro-ffi/tests/bindings/test_mopro_rsa2.swift b/app/mopro-ffi/tests/bindings/test_mopro_rsa2.swift deleted file mode 100644 index 70998bd08..000000000 --- a/app/mopro-ffi/tests/bindings/test_mopro_rsa2.swift +++ /dev/null @@ -1,167 +0,0 @@ -import mopro -import Foundation - -// Helper function to convert bytes to bits -func bytesToBits(bytes: [UInt8]) -> [String] { - var bits = [String]() - for byte in bytes { - for j in 0..<8 { - let bit = (byte >> j) & 1 - bits.append(String(bit)) - } - } - return bits -} - -func serializeOutputs(_ stringArray: [String]) -> [UInt8] { - var bytesArray: [UInt8] = [] - let length = stringArray.count - var littleEndianLength = length.littleEndian - let targetLength = 32 - withUnsafeBytes(of: &littleEndianLength) { - bytesArray.append(contentsOf: $0) - } - for value in stringArray { - // TODO: should handle 254-bit input - var littleEndian = Int32(value)!.littleEndian - var byteLength = 0 - withUnsafeBytes(of: &littleEndian) { - bytesArray.append(contentsOf: $0) - byteLength = byteLength + $0.count - } - if byteLength < targetLength { - let paddingCount = targetLength - byteLength - let paddingArray = [UInt8](repeating: 0, count: paddingCount) - bytesArray.append(contentsOf: paddingArray) - } - } - return bytesArray -} - -do { - // Initialize - try initializeMopro() - - // Prepare inputs - let signature: [String] = [ - "3582320600048169363", - "7163546589759624213", - "18262551396327275695", - "4479772254206047016", - "1970274621151677644", - "6547632513799968987", - "921117808165172908", - "7155116889028933260", - "16769940396381196125", - "17141182191056257954", - "4376997046052607007", - "17471823348423771450", - "16282311012391954891", - "70286524413490741", - "1588836847166444745", - "15693430141227594668", - "13832254169115286697", - "15936550641925323613", - "323842208142565220", - "6558662646882345749", - "15268061661646212265", - "14962976685717212593", - "15773505053543368901", - "9586594741348111792", - "1455720481014374292", - "13945813312010515080", - "6352059456732816887", - "17556873002865047035", - "2412591065060484384", - "11512123092407778330", - "8499281165724578877", - "12768005853882726493", - ] - - let modulus: [String] = [ - "13792647154200341559", - "12773492180790982043", - "13046321649363433702", - "10174370803876824128", - "7282572246071034406", - "1524365412687682781", - "4900829043004737418", - "6195884386932410966", - "13554217876979843574", - "17902692039595931737", - "12433028734895890975", - "15971442058448435996", - "4591894758077129763", - "11258250015882429548", - "16399550288873254981", - "8246389845141771315", - "14040203746442788850", - "7283856864330834987", - "12297563098718697441", - "13560928146585163504", - "7380926829734048483", - "14591299561622291080", - "8439722381984777599", - "17375431987296514829", - "16727607878674407272", - "3233954801381564296", - "17255435698225160983", - "15093748890170255670", - "15810389980847260072", - "11120056430439037392", - "5866130971823719482", - "13327552690270163501", - ] - let base_message: [String] = [ - "18114495772705111902", - "2254271930739856077", - "2068851770", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - ] - - var inputs = [String: [String]]() - inputs["signature"] = signature; - inputs["modulus"] = modulus; - inputs["base_message"] = base_message; - - // Generate Proof - let generateProofResult = try generateProof2(circuitInputs: inputs) - assert(!generateProofResult.proof.isEmpty, "Proof should not be empty") - - // Verifying the Proof - let isValid = try verifyProof2(proof: generateProofResult.proof, publicInput: generateProofResult.inputs) - assert(isValid, "Proof verification should succeed") - -} catch let error as MoproError { - print("MoproError: \(error)") -} catch { - print("Unexpected error: \(error)") -} diff --git a/app/mopro-ffi/tests/test_generated_bindings.rs b/app/mopro-ffi/tests/test_generated_bindings.rs deleted file mode 100644 index d88f82a2c..000000000 --- a/app/mopro-ffi/tests/test_generated_bindings.rs +++ /dev/null @@ -1,13 +0,0 @@ -uniffi::build_foreign_language_testcases!( - "tests/bindings/test_mopro.swift", - "tests/bindings/test_mopro.kts", - // "tests/bindings/test_mopro.rb", - // "tests/bindings/test_mopro.py", - "tests/bindings/test_mopro_keccak.swift", - // "tests/bindings/test_mopro_keccak.kts", // FIXME: java.lang.OutOfMemoryError: Java heap space - "tests/bindings/test_mopro_keccak2.swift", - "tests/bindings/test_mopro_keccak2.kts", - "tests/bindings/test_mopro_rsa.swift", - // "tests/bindings/test_mopro_rsa.kts", // FIXME: java.lang.OutOfMemoryError: Java heap space - // "tests/bindings/test_mopro_rsa2.swift", -); diff --git a/app/mopro-ffi/uniffi-bindgen.rs b/app/mopro-ffi/uniffi-bindgen.rs deleted file mode 100644 index f6cff6cf1..000000000 --- a/app/mopro-ffi/uniffi-bindgen.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - uniffi::uniffi_bindgen_main() -} diff --git a/app/mopro-ffi/uniffi.toml b/app/mopro-ffi/uniffi.toml deleted file mode 100644 index f14925e0c..000000000 --- a/app/mopro-ffi/uniffi.toml +++ /dev/null @@ -1,2 +0,0 @@ -[bindings.swift] -module_name = "mopro" diff --git a/app/package.json b/app/package.json index 33d19b2ff..7609a7cc4 100644 --- a/app/package.json +++ b/app/package.json @@ -13,7 +13,6 @@ "@amplitude/analytics-react-native": "^1.4.7", "@babel/plugin-transform-private-methods": "^7.23.3", "@ethersproject/shims": "^5.7.0", - "@react-native-async-storage/async-storage": "^1.23.1", "@react-native-community/clipboard": "^1.5.1", "@react-native-community/netinfo": "^11.3.1", "@tamagui/colors": "^1.94.3", @@ -40,10 +39,13 @@ "react-native": "0.72.3", "react-native-canvas": "^0.1.39", "react-native-fs": "^2.20.0", + "react-native-get-random-values": "^1.11.0", + "react-native-keychain": "^8.2.0", "react-native-passport-reader": "^1.0.3", "react-native-svg": "13.4.0", "react-native-zip-archive": "^6.1.0", - "tamagui": "^1.94.3" + "tamagui": "^1.94.3", + "zustand": "^4.5.2" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/app/scripts/build_android_module.sh b/app/scripts/build_android_module.sh index 0e60d3368..3b75ac9af 100755 --- a/app/scripts/build_android_module.sh +++ b/app/scripts/build_android_module.sh @@ -1,76 +1,9 @@ #!/bin/bash -# This is adapted from mopro +cd witnesscalc +./build_gmp.sh android +make android +cd .. -DEVICE_TYPE="arm64" -BUILD_MODE="release" - -# Determine the architecture and folder based on device type -case $DEVICE_TYPE in - "x86_64") - ARCHITECTURE="x86_64-linux-android" - FOLDER="x86_64" - ;; - "x86") - ARCHITECTURE="i686-linux-android" - FOLDER="x86" - ;; - "arm") - ARCHITECTURE="armv7-linux-androideabi" - FOLDER="armeabi-v7a" - ;; - "arm64") - ARCHITECTURE="aarch64-linux-android" - FOLDER="arm64-v8a" - ;; - *) - echo -e "${RED}Error: Invalid device type specified in config: $DEVICE_TYPE${DEFAULT}" - exit 1 - ;; -esac - -# Determine the library directory and build command based on build mode -case $BUILD_MODE in - "debug") - LIB_DIR="debug" - COMMAND="" - ;; - "release") - LIB_DIR="release" - COMMAND="--release" - ;; - *) - echo -e "${RED}Error: Invalid build mode specified in config: $BUILD_MODE${DEFAULT}" - exit 1 - ;; -esac - -PROJECT_DIR=$(pwd) - -cd ${PROJECT_DIR}/mopro-ffi - -echo "[android] Install cargo-ndk" -cargo install cargo-ndk - -# Print appropriate message based on device type -echo "Using $ARCHITECTURE libmopro_ffi.a ($LIB_DIR) static library..." -echo "This only works on $FOLDER devices!" - -echo "[android] Build target in $BUILD_MODE mode" -cargo ndk -t ${ARCHITECTURE} build --lib ${COMMAND} - -echo "[android] Copy files in mopro-android/Example/jniLibs/" -for binary in ${PROJECT_DIR}/mopro-ffi/target/*/*/libmopro_ffi.so; do file $binary; done - -mkdir -p jniLibs/${FOLDER}/ && \ -cp ${PROJECT_DIR}/mopro-ffi/target/${ARCHITECTURE}/${LIB_DIR}/libmopro_ffi.so jniLibs/${FOLDER}/libuniffi_mopro.so - -echo "[android] Generating Kotlin bindings in $BUILD_MODE mode..." -cargo run --features=uniffi/cli ${COMMAND} \ ---bin uniffi-bindgen \ -generate src/mopro.udl \ ---language kotlin - -echo "[android] Copy Kotlin bindings to android app" -cp -r ${PROJECT_DIR}/mopro-ffi/jniLibs/ ${PROJECT_DIR}/android/app/src/main/jniLibs/ -cp -r ${PROJECT_DIR}/mopro-ffi/src/uniffi/ ${PROJECT_DIR}/android/app/src/main/java/uniffi/ \ No newline at end of file +cp ../circuits/build/proof_of_passport_cpp/proof_of_passport.dat android/app/src/main/res/raw/proof_of_passport.dat +cp witnesscalc/build_witnesscalc_android/src/libwitnesscalc_proof_of_passport.so android/app/src/main/cpp/lib/ \ No newline at end of file diff --git a/app/scripts/build_ios_module.sh b/app/scripts/build_ios_module.sh index f38592470..85ea17bec 100755 --- a/app/scripts/build_ios_module.sh +++ b/app/scripts/build_ios_module.sh @@ -1,39 +1,20 @@ #!/bin/bash -cp ../circuits/build/proof_of_passport_cpp/proof_of_passport.cpp witnesscalc/src -cp ../circuits/build/proof_of_passport_cpp/proof_of_passport.dat witnesscalc/src - -cd witnesscalc/src -# This adds the namespace to the circuit file as described in the README -last_include=$(grep -n '#include' proof_of_passport.cpp | tail -1 | cut -d: -f1) -if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS requires an empty string with the -i flag and handles backslashes differently - sed -i "" "${last_include}a\\ -namespace CIRCUIT_NAME {" proof_of_passport.cpp -else - # Linux - sed -i "${last_include}a \\nnamespace CIRCUIT_NAME {" proof_of_passport.cpp -fi -echo "}" >> proof_of_passport.cpp - -cd ../../.. -git submodule init -git submodule update - -cd app/witnesscalc +cd witnesscalc ./build_gmp.sh ios make ios - cd build_witnesscalc_ios + xcodebuild -project witnesscalc.xcodeproj \ - -scheme proof_of_passport \ - -sdk iphoneos \ - -configuration Release \ - DEVELOPMENT_TEAM="$DEVELOPMENT_TEAM" \ - ARCHS="arm64" \ - -destination 'generic/platform=iOS' \ - PRODUCT_BUNDLE_IDENTIFIER=com.warrom.witnesscalc \ - build + -scheme proof_of_passport \ + -sdk iphoneos \ + -configuration Release \ + DEVELOPMENT_TEAM="$DEVELOPMENT_TEAM" \ + ARCHS="arm64" \ + -destination 'generic/platform=iOS' \ + PRODUCT_BUNDLE_IDENTIFIER=com.warrom.witnesscalc \ + build + cd ../.. cp witnesscalc/build_witnesscalc_ios/src/Release-iphoneos/libwitnesscalc_proof_of_passport.a ios -cp witnesscalc/src/proof_of_passport.dat ios/ProofOfPassport/Assets.xcassets/proof_of_passport.dat.dataset/proof_of_passport.dat +cp witnesscalc/src/proof_of_passport.dat ios/ProofOfPassport/Assets.xcassets/proof_of_passport.dat.dataset/proof_of_passport.dat \ No newline at end of file diff --git a/app/scripts/common.sh b/app/scripts/common.sh new file mode 100644 index 000000000..dbe153e6c --- /dev/null +++ b/app/scripts/common.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +cp ../circuits/build/proof_of_passport_cpp/proof_of_passport.cpp witnesscalc/src +cp ../circuits/build/proof_of_passport_cpp/proof_of_passport.dat witnesscalc/src + +cd witnesscalc/src + +# This adds the namespace to the circuit file as described in the README +last_include=$(grep -n '#include' proof_of_passport.cpp | tail -1 | cut -d: -f1) +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS requires an empty string with the -i flag and handles backslashes differently + sed -i "" "${last_include}a\\ +namespace CIRCUIT_NAME {" proof_of_passport.cpp +else + # Linux + sed -i "${last_include}a \\nnamespace CIRCUIT_NAME {" proof_of_passport.cpp +fi +echo "}" >> proof_of_passport.cpp + +cd ../.. +git submodule init +git submodule update \ No newline at end of file diff --git a/app/src/apps/gitcoin.tsx b/app/src/apps/gitcoin.tsx new file mode 100644 index 000000000..29d55c331 --- /dev/null +++ b/app/src/apps/gitcoin.tsx @@ -0,0 +1,30 @@ +import { AppType } from "../utils/appType"; +import { Text, YStack } from 'tamagui'; +import { Coins } from '@tamagui/lucide-icons'; +import GITCOIN from '../images/gitcoin.png'; + +const comingSoon = () => ( + + coming soon + +); + +export const gitcoinApp: AppType = { + id: 'gitcoin', + + title: 'Gitcoin passport', + description: 'Add to Gitcoin passport and donate to your favorite projects', + background: GITCOIN, + colorOfTheText: 'white', + selectable: false, + icon: Coins, + tags: [comingSoon()], + + name: 'Gitcoin', + disclosureOptions: { + date_of_expiry: "required" + }, + sendButtonText: 'Add to Gitcoin passport', +} + +export default gitcoinApp; \ No newline at end of file diff --git a/app/src/apps/sbt.tsx b/app/src/apps/sbt.tsx new file mode 100644 index 000000000..cc83debdb --- /dev/null +++ b/app/src/apps/sbt.tsx @@ -0,0 +1,262 @@ +import { AppType } from "../utils/appType"; +import { Flame } from '@tamagui/lucide-icons'; +import { Text, XStack, YStack } from 'tamagui'; +import { generateProof } from "../utils/prover"; +import useUserStore from "../stores/userStore"; +import { generateCircuitInputs } from "../../../common/src/utils/generateInputs"; +import EnterAddress from "../components/EnterAddress"; +import { revealBitmapFromMapping } from "../../../common/src/utils/revealBitmap"; +import useSbtStore from "../stores/sbtStore"; +import useNavigationStore from "../stores/navigationStore"; +import { Steps } from "../utils/utils"; +import { mintSBT } from "../utils/minter"; +import { ethers } from "ethers"; +import * as amplitude from '@amplitude/analytics-react-native'; +import Clipboard from "@react-native-community/clipboard"; +import { shortenTxHash } from "../../utils/utils"; +import { textColor1 } from "../utils/colors"; +import { Pressable } from "react-native"; + +const sepolia = () => ( + + Sepolia + +); + +export const sbtApp: AppType = { + id: 'soulbound', + + // AppScreen UI + title: 'Soulbound Token', + description: 'Mint a Soulbound Token and prove you\'re a human', + colorOfTheText: 'black', + selectable: true, + icon: Flame, + tags: [sepolia()], + + // ProveScreen UI + name: 'Soulbound token', + disclosureOptions: { + nationality: "optional", + expiry_date: "optional", + older_than: "optional" + }, + + // SendProofScreen UI before sending proof + beforeSendText1: "You can now use this proof to mint a Soulbound token.", + beforeSendText2: "Disclosed information will be displayed on SBT.", + sendButtonText: 'Mint Soulbound token', + sendingButtonText: 'Minting...', + + // SendProofScreen UI after sending proof + successTitle: 'You just have minted a Soulbound token 🎉', + successText: 'You can now share this proof with the selected app.', + + successComponent: () => { + const txHash = useSbtStore.getState().txHash; + const toast = useNavigationStore.getState().toast; + + return ( + { + Clipboard.setString(txHash); + toast?.show('🖨️', { + message: "Tx copied to clipboard", + customData: { + type: "success", + }, + }) + }} + > + + + Tx: {shortenTxHash(txHash)} + + + + ) + }, + + finalButtonAction: () => { + const txHash = useSbtStore.getState().txHash; + const toast = useNavigationStore.getState().toast; + + Clipboard.setString(txHash); + toast?.show('🖨️', { + message: "Tx copied to clipboard", + customData: { + type: "success", + }, + }) + }, + + finalButtonText: 'Copy to clipboard', + + + circuit: "proof_of_passport", // will be "disclose" soon + + // fields the user can fill + fields: [ + EnterAddress + ], + + handleProve: async () => { + const { + update, + disclosure, + address, + majority + } = useSbtStore.getState(); + + const { + toast, + setStep + } = useNavigationStore.getState(); + + setStep(Steps.GENERATING_PROOF); + + await new Promise(resolve => setTimeout(resolve, 10)); + const reveal_bitmap = revealBitmapFromMapping(disclosure); + + const passportData = useUserStore.getState().passportData; + + try { + const inputs = generateCircuitInputs( + passportData, + reveal_bitmap, + address, + majority, + { developmentMode: false } + ); + + console.log('inputs:', inputs); + + const start = Date.now(); + + const proof = await generateProof( + sbtApp.circuit, + inputs, + ); + + const end = Date.now(); + console.log('Total proof time from frontend:', end - start); + amplitude.track('Proof generation successful, took ' + ((end - start) / 1000) + ' seconds'); + update({ + proof: proof, + proofTime: end - start, + }); + setStep(Steps.PROOF_GENERATED); + } catch (error: any) { + console.error(error); + toast?.show('Error', { + message: error.message, + customData: { + type: "error", + }, + }) + setStep(Steps.NFC_SCAN_COMPLETED); + amplitude.track(error.message); + } + }, + + handleSendProof: async () => { + const { + update, + proof + } = useSbtStore.getState(); + + const { + toast, + setStep + } = useNavigationStore.getState(); + + if (!proof) { + console.error('Proof is not generated'); + return; + } + + setStep(Steps.PROOF_SENDING); + + toast?.show('🚀',{ + message: "Transaction sent...", + customData: { + type: "info", + }, + }) + + const provider = new ethers.JsonRpcProvider('https://gateway.tenderly.co/public/sepolia'); + // https://mainnet.optimism.io + + try { + const serverResponse = await mintSBT( + proof, + provider, + "sepolia" + ) + const txHash = serverResponse?.data.hash; + + setStep(Steps.PROOF_SENT); + update({ + txHash: txHash, + proofSentText: `SBT minting... Network: Sepolia. Transaction hash: ${txHash}` + }); + + const receipt = await provider.waitForTransaction(txHash); + console.log('receipt status:', receipt?.status); + + if (receipt?.status === 1) { + toast?.show('🎊', { + message: "SBT minted", + customData: { + type: "success", + }, + }) + update({ + proofSentText: `SBT minted. Network: Sepolia. Transaction hash: ${txHash}` + }); + } else { + toast?.show('Error', { + message: "SBT mint failed", + customData: { + type: "error", + }, + }) + update({ + proofSentText: `Error minting SBT. Network: Sepolia. Transaction hash: ${txHash}` + }); + setStep(Steps.PROOF_GENERATED); + } + } catch (error: any) { + setStep(Steps.PROOF_GENERATED); + update({ + proofSentText: `Error minting SBT. Network: Sepolia.` + }); + if (error.isAxiosError && error.response) { + const errorMessage = error.response.data.error; + console.log('Server error message:', errorMessage); + + // parse blockchain error and show it + const match = errorMessage.match(/execution reverted: "([^"]*)"/); + if (match && match[1]) { + console.log('Parsed blockchain error:', match[1]); + toast?.show('Error', { + message: `Error: ${match[1]}`, + customData: { + type: "error", + }, + }) + } else { + toast?.show('Error', { + message: `Error: mint failed`, + customData: { + type: "error", + }, + }) + console.log('Failed to parse blockchain error'); + } + } + amplitude.track(error.message); + } + } +} + +export default sbtApp; \ No newline at end of file diff --git a/app/src/apps/zupass.tsx b/app/src/apps/zupass.tsx new file mode 100644 index 000000000..c53fad8f7 --- /dev/null +++ b/app/src/apps/zupass.tsx @@ -0,0 +1,30 @@ +import { AppType } from "../utils/appType"; +import { Text, YStack } from 'tamagui'; +import { Ticket } from '@tamagui/lucide-icons'; +import ZUPASS from '../images/zupass.png'; + +const comingSoon = () => ( + + coming soon + +); + +export const zupassApp: AppType = { + id: 'zuzalu', + + title: 'Zupass', + description: 'Connect to prove your identity at in person events', + background: ZUPASS, + colorOfTheText: 'white', + selectable: false, + icon: Ticket, + tags: [comingSoon()], + + name: 'Zupass', + disclosureOptions: { + date_of_expiry: "required" + }, + sendButtonText: 'Add to Zupass', +} + +export default zupassApp; \ No newline at end of file diff --git a/app/src/components/EnterAddress.tsx b/app/src/components/EnterAddress.tsx new file mode 100644 index 000000000..42954688c --- /dev/null +++ b/app/src/components/EnterAddress.tsx @@ -0,0 +1,117 @@ +import React, { useState, useEffect } from 'react'; +import { YStack, XStack, Text, Checkbox, Input, Button, Spinner, Image, useWindowDimensions, ScrollView } from 'tamagui'; +import { borderColor, componentBgColor, componentBgColor2, textColor1, textColor2 } from '../utils/colors'; +import ENS from "../images/ens_mark_dao.png" +import { useToastController } from '@tamagui/toast' +import { ethers } from 'ethers'; +import useSbtStore from '../stores/sbtStore'; + +const EnterAddress: React.FC = () => { + const [inputValue, setInputValue] = useState(''); + + const { + address, + ens, + update + } = useSbtStore(); + + const provider = new ethers.JsonRpcProvider(`https://eth-mainnet.g.alchemy.com/v2/lpOn3k6Fezetn1e5QF-iEsn-J0C6oGE0`); + const toast = useToastController() + + useEffect(() => { + if (ens != '' && inputValue == '') { + setInputValue(ens); + + } + else if (address != ethers.ZeroAddress && inputValue == '') { + setInputValue(address); + } + }, []) + + useEffect(() => { + const resolveENS = async () => { + if (inputValue != ens) { + if (inputValue.endsWith('.eth')) { + try { + toast.show('🔭 Looking onchain', { + message: 'Looking for ' + inputValue, + customData: { + type: "info", + }, + }) + + const resolvedAddress = await provider.resolveName(inputValue); + if (resolvedAddress) { + console.log("new address settled:" + resolvedAddress); + update({ + address: resolvedAddress, + ens: inputValue + }); + toast.show('✨ Welcome ✨', { + message: 'Nice to meet you ' + inputValue, + customData: { + type: "success", + }, + }) + } else { + toast.show('Error', { + message: inputValue + ' not found ', + customData: { + type: "error", + }, + }) + } + } catch (error) { + toast.show('Error', { + message: 'Check input format or RPC provider or internet connection', + customData: { + type: "error", + }, + }) + } + } + else if (inputValue.length === 42 && inputValue.startsWith('0x')) { + update({ + address: inputValue, + }); + } + }; + }; + + resolveENS(); + }, [inputValue]); + + return ( + + + + + + + + Address or ENS + + + + + + + + ); +}; + +export default EnterAddress; \ No newline at end of file diff --git a/app/src/components/ProofGrid.tsx b/app/src/components/ProofGrid.tsx index b94cbdc3f..34565418f 100644 --- a/app/src/components/ProofGrid.tsx +++ b/app/src/components/ProofGrid.tsx @@ -1,9 +1,10 @@ import React, { useMemo } from 'react'; import { Svg, Rect } from 'react-native-svg'; import { YStack } from 'tamagui'; +import { Proof } from '../../../common/src/utils/types'; interface ProofGridProps { - proof: { proof: string; inputs: string } | null; + proof: Proof | null; } const ProofGrid: React.FC = ({ proof }) => { @@ -22,7 +23,7 @@ const ProofGrid: React.FC = ({ proof }) => { return { rValues: [], gValues: [], bValues: [] }; } - const parsedProof = JSON.parse(proof.proof); + const parsedProof = proof.proof; return { rValues: sumAndScaleDigits(parsedProof.a), gValues: sumAndScaleDigits(parsedProof.b.flat()), diff --git a/app/src/screens/AppScreen.tsx b/app/src/screens/AppScreen.tsx index e3622f57a..4fce61bf8 100644 --- a/app/src/screens/AppScreen.tsx +++ b/app/src/screens/AppScreen.tsx @@ -1,104 +1,54 @@ import React from 'react'; -import ZUPASS from '../images/zupass.png'; -import GITCOIN from '../images/gitcoin.png'; -import { ScrollView, Text, YStack } from 'tamagui'; +import { ScrollView, YStack } from 'tamagui'; import AppCard from '../components/AppCard'; -import { App, gitcoin, soulbound, zuzalu } from '../utils/AppClass'; import { Steps } from '../utils/utils'; -import { Coins, Flame, Ticket } from '@tamagui/lucide-icons'; +import useNavigationStore from '../stores/navigationStore'; +import { AppType } from '../utils/appType'; +import sbtApp from '../apps/sbt'; +import zupassApp from '../apps/zupass'; +import gitcoinApp from '../apps/gitcoin'; -interface AppScreenProps { - selectedApp: App | null; - setSelectedApp: (app: App | null) => void; - step: number; - setStep: (step: number) => void; - setSelectedTab: (tab: string) => void; -} +const AppScreen: React.FC = () => { + const { + selectedApp, + update + } = useNavigationStore(); -const AppScreen: React.FC = ({ selectedApp, setSelectedApp, step, setStep, setSelectedTab }) => { - - const handleCardSelect = (app: App) => { - setSelectedApp(app); - setStep(Steps.APP_SELECTED); - setSelectedTab("prove"); + const handleCardSelect = (app: AppType) => { + update({ + selectedTab: "prove", + selectedApp: app, + step: Steps.APP_SELECTED, + }) }; - const age = () => ( - - age - - ); - const comingSoon = () => ( - - coming soon - - ); - const nationality = () => ( - - nationality - - ); - const sepolia = () => ( - - Sepolia - - ); - - - + // add new apps here const cardsData = [ - { - app: soulbound, - title: 'Soulbound Token', - description: 'Mint a Soulbound Token and prove you\'re a human', - colorOfTheText: 'black', - selectable: true, - icon: Flame, - tags: [sepolia()] - }, - { - app: zuzalu, - title: 'Zupass', - description: 'Connect to prove your identity at in person events', - background: ZUPASS, - colorOfTheText: 'white', - selectable: false, - icon: Ticket, - tags: [comingSoon()] - }, - { - app: gitcoin, - title: 'Gitcoin passport', - description: 'Add to Gitcoin passport and donate to your favorite projects', - background: GITCOIN, - colorOfTheText: 'white', - selectable: false, - icon: Coins, - tags: [comingSoon()] - } + sbtApp, + zupassApp, + gitcoinApp ]; return ( - - < YStack my="$8" gap="$5" px="$5" jc="center" alignItems='center' > + + { - cardsData.map(card => ( + cardsData.map(app => ( handleCardSelect(card.app)} - selected={selectedApp && selectedApp.id === card.app.id ? true : false} - selectable={card.selectable} - icon={card.icon} - tags={card.tags} + key={app.id} + title={app.title} + description={app.description} + id={app.id} + onTouchStart={() => handleCardSelect(app)} + selected={selectedApp && selectedApp.id === app.id ? true : false} + selectable={app.selectable} + icon={app.icon} + tags={app.tags} /> )) } - - - + + ); } diff --git a/app/src/screens/MainScreen.tsx b/app/src/screens/MainScreen.tsx index 103174546..0dae8a908 100644 --- a/app/src/screens/MainScreen.tsx +++ b/app/src/screens/MainScreen.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect, Profiler } from 'react'; -import { YStack, XStack, Text, Button, Tabs, Sheet, Label, Fieldset, Input, Switch, H2, Image, useWindowDimensions, H4, H3, ScrollView } from 'tamagui' -import { HelpCircle, IterationCw, VenetianMask, Cog, CheckCircle2, ExternalLink } from '@tamagui/lucide-icons'; +import React, { useState, useEffect } from 'react'; +import { YStack, XStack, Text, Button, Tabs, Sheet, Label, Fieldset, Input, Switch, H2, Image, useWindowDimensions, H4, H3 } from 'tamagui' +import { HelpCircle, IterationCw, VenetianMask, Cog, CheckCircle2 } from '@tamagui/lucide-icons'; import X from '../images/x.png' import Telegram from '../images/telegram.png' import Github from '../images/github.png' @@ -9,116 +9,73 @@ import ScanScreen from './ScanScreen'; import ProveScreen from './ProveScreen'; import { Steps } from '../utils/utils'; import AppScreen from './AppScreen'; -import { App } from '../utils/AppClass'; import { Linking, Modal, Platform, Pressable } from 'react-native'; -import { Keyboard } from 'react-native'; import NFC_IMAGE from '../images/nfc.png' import { bgColor, blueColorLight, borderColor, componentBgColor, textColor1, textColor2 } from '../utils/colors'; -import MintScreen from './MintScreen'; -import { ToastViewport, useToastController } from '@tamagui/toast'; +import SendProofScreen from './SendProofScreen'; +import { ToastViewport } from '@tamagui/toast'; import { ToastMessage } from '../components/ToastMessage'; -import { downloadZkey } from '../utils/zkeyDownload'; +import { CircuitName, fetchZkey } from '../utils/zkeyDownload'; +import useUserStore from '../stores/userStore'; +import { scan } from '../utils/nfcScanner'; +import useNavigationStore from '../stores/navigationStore'; -interface MainScreenProps { - onStartCameraScan: () => void; - nfcScan: () => void; - passportData: any; - disclosure: { [key: string]: boolean }; - handleDisclosureChange: (field: string) => void; - address: string; - setAddress: (address: string) => void; - generatingProof: boolean; - handleProve: () => void; - step: number; - mintText: string; - proof: any; - proofTime: number; - handleMint: () => void; - setStep: (step: number) => void; - passportNumber: string; - setPassportNumber: (number: string) => void; - dateOfBirth: string; - setDateOfBirth: (date: string) => void; - dateOfExpiry: string; - setDateOfExpiry: (date: string) => void; - majority: number; - setMajority: (age: number) => void; - zkeydownloadStatus: string; - showWarning: boolean; - setShowWarning: (value: boolean) => void; - setDownloadStatus: (value: "not_started" | "downloading" | "completed" | "error") => void; -} - -const MainScreen: React.FC = ({ - onStartCameraScan, - nfcScan, - passportData, - disclosure, - handleDisclosureChange, - address, - setAddress, - generatingProof, - handleProve, - step, - mintText, - proof, - proofTime, - handleMint, - setStep, - passportNumber, - setPassportNumber, - dateOfBirth, - setDateOfBirth, - dateOfExpiry, - setDateOfExpiry, - majority, - setMajority, - zkeydownloadStatus, - showWarning, - setShowWarning, - setDownloadStatus -}) => { +const MainScreen: React.FC = () => { const [NFCScanIsOpen, setNFCScanIsOpen] = useState(false); const [SettingsIsOpen, setSettingsIsOpen] = useState(false); const [HelpIsOpen, setHelpIsOpen] = useState(false); - const [ens, setEns] = useState(''); - const [selectedTab, setSelectedTab] = useState("scan"); - const [selectedApp, setSelectedApp] = useState(null); const [brokenCamera, setBrokenCamera] = useState(false); - const [hideData, setHideData] = useState(false); - const toast = useToastController(); + const { + passportNumber, + dateOfBirth, + dateOfExpiry, + deleteMrzFields, + update, + clearPassportDataFromStorage, + clearSecretFromStorage, + } = useUserStore() + + const { + showWarningModal, + update: updateNavigationStore, + step, + setStep, + selectedTab, + hideData + } = useNavigationStore(); const handleRestart = () => { - setStep(Steps.MRZ_SCAN); - setSelectedApp(null) - setPassportNumber(""); - setDateOfBirth(""); - setDateOfExpiry(""); - setSelectedTab("scan"); - + updateNavigationStore({ + selectedTab: "scan", + selectedApp: null, + step: Steps.MRZ_SCAN, + }) + deleteMrzFields(); } + const handleSkip = () => { setStep(Steps.NFC_SCAN_COMPLETED); - setPassportNumber(""); - setDateOfBirth(""); - setDateOfExpiry(""); - + deleteMrzFields(); } + const handleHideData = () => { - setHideData(!hideData); + updateNavigationStore({ + hideData: !hideData, + }) } const handleNFCScan = () => { if ((Platform.OS === 'ios')) { console.log('ios'); - nfcScan(); + scan(); } else { console.log('android :)'); setNFCScanIsOpen(true); - nfcScan(); + scan(); } } + useEffect(() => { if (passportNumber?.length === 9 && (dateOfBirth?.length === 6 && dateOfExpiry?.length === 6)) { setStep(Steps.MRZ_SCAN_COMPLETED); @@ -139,10 +96,14 @@ const MainScreen: React.FC = ({ }, 700); } else if (step == Steps.PROOF_GENERATED) { - setSelectedTab("mint"); + updateNavigationStore({ + selectedTab: "mint", + }) } if (step == Steps.NFC_SCAN_COMPLETED) { - setSelectedTab("app"); + updateNavigationStore({ + selectedTab: "app", + }) } return () => { if (timeoutId) { @@ -151,42 +112,18 @@ const MainScreen: React.FC = ({ }; }, [step]); - // Keyboard management - const [keyboardVisible, setKeyboardVisible] = useState(false); - - useEffect(() => { - const showSubscription = Keyboard.addListener('keyboardDidShow', () => { - setKeyboardVisible(true); - }); - const hideSubscription = Keyboard.addListener('keyboardDidHide', () => { - setKeyboardVisible(false); - }); - - return () => { - showSubscription.remove(); - hideSubscription.remove(); - }; - }, []); - - const { height, width } = useWindowDimensions(); + const { height } = useWindowDimensions(); return ( <> - - - - {selectedTab === "scan" ? "Scan" : (selectedTab === "app" ? "Apps" : "Prove")} - - - @@ -238,19 +175,55 @@ const MainScreen: React.FC = ({ - setPassportNumber(text.toUpperCase())} value={passportNumber} keyboardType="default" /> + { + update({passportNumber: text.toUpperCase()}) + }} + value={passportNumber} + keyboardType="default" + />
- + { + update({dateOfBirth: text}) + }} + value={dateOfBirth} + keyboardType={Platform.OS === "ios" ? "default" : "number-pad"} + />
- + { + update({dateOfExpiry: text}) + }} + value={dateOfExpiry} + keyboardType={Platform.OS === "ios" ? "default" : "number-pad"} + />
} @@ -264,8 +237,6 @@ const MainScreen: React.FC = ({ - -
+ +
+ + +
+ +
+ + +
+ @@ -351,53 +341,6 @@ const MainScreen: React.FC = ({ - {/* - - -

How do I scan my passport ?

- 1. Find the location of the NFC chip of your passport. Most of the time, it will be in the back cover. If you have an American passport, the front and back cover are NFC-protected, so you have to open your passport and scan the back cover from the inside. - - - 2. Find the location of the NFC reader of your phone. On an iPhone, it should be on the upper part of your phone. On Android phones, it should be in the center. - - - 3. Keep your passport pressed against your phone when the NFC popup shows up and hold still. -
- -

Security and Privacy

- Proof of Passport uses zero-knowledge cryptography to allow you to prove facts about yourself like humanity, nationality or age without disclosing sensitive information. It works by generating a proof showing your passport data has been correctly signed by a government authority without revealing the signature. -
- -

What are zero-knowledge proofs ?

- - Zero-knowledge proofs rely on mathematical magic tricks to show the correctness of some computation while hiding some inputs of its inputs. In our case, the proof shows the passport has not been forged, but allows you to hide sensitive data. -
- - -

Contacts

- - Linking.openURL('https://t.me/proofofpassport')}> - - - Linking.openURL('https://x.com/proofofpassport')}> - - - Linking.openURL('https://github.com/zk-passport/proof-of-passport')}> - - - -
-
*/} @@ -407,71 +350,52 @@ const MainScreen: React.FC = ({
- + updateNavigationStore({ selectedTab: value })} + > + step={step} + /> - + - + - + - + 👋 Hi - The app needs to download a large file (300MB). Please make sure you're connected to a Wi-Fi network before continuing. + The app needs to download a large file ({(showWarningModal.size / 1024 / 1024).toFixed(2)}MB). Please make sure you're connected to a Wi-Fi network before continuing. - diff --git a/app/src/screens/MintScreen.tsx b/app/src/screens/MintScreen.tsx deleted file mode 100644 index a88011619..000000000 --- a/app/src/screens/MintScreen.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { YStack, XStack, Text, Button, Spinner } from 'tamagui'; -import { Copy } from '@tamagui/lucide-icons'; -import { formatDuration } from '../../utils/utils'; -import { Steps } from '../utils/utils'; -import ProofGrid from '../components/ProofGrid'; -import { App } from '../utils/AppClass'; -import { Platform } from 'react-native'; -import Clipboard from '@react-native-community/clipboard'; -import { blueColor, borderColor, componentBgColor, textColor1, textColor2 } from '../utils/colors'; -import { useToastController } from '@tamagui/toast'; - -const { ethers } = require('ethers'); -const fileName = "passport.arkzkey" -const path = "/data/user/0/com.proofofpassport/files/" + fileName - -interface MintScreenProps { - selectedApp: App | null; - step: number; - mintText: string; - proof: { proof: string, inputs: string } | null; - proofTime: number; - handleMint: () => void; - -} - -const MintScreen: React.FC = ({ - selectedApp, - step, - mintText, - proof, - proofTime, - handleMint, -}) => { - const toast = useToastController(); - - const getTx = (input: string | null): string => { - if (!input) return ''; - const transaction = input.split(' ').filter(word => word.startsWith('0x')).join(' '); - return transaction; - } - const shortenInput = (input: string | null): string => { - if (!input) return ''; - if (input.length > 9) { - return input.substring(0, 25) + '\u2026'; - } else { - return input; - } - } - - const copyToClipboard = (input: string) => { - Clipboard.setString(input); - toast.show('🖨️', { - message: "Tx copied to clipboard", - customData: { - type: "success", - }, - }) - - }; - - - return ( - - {step === Steps.TX_MINTED ? ( - - - - - - You just have minted a Soulbound token 🎉 - You can now share this proof with the selected app. - - Network: Sepolia - - Tx: {shortenInput(getTx(mintText))} - - - - - - - - ) : ( - - - - - ZK proof generated 🎉 - Proof generation duration: {formatDuration(proofTime)} - - You can now use this proof to mint a Soulbound token. - Disclosed information will be displayed on SBT. - - - - - - - )} - - - ); -}; -export default MintScreen; diff --git a/app/src/screens/ProveScreen.tsx b/app/src/screens/ProveScreen.tsx index 86cac0268..1c76e8553 100644 --- a/app/src/screens/ProveScreen.tsx +++ b/app/src/screens/ProveScreen.tsx @@ -4,176 +4,107 @@ import { Check, Plus, Minus, PenTool } from '@tamagui/lucide-icons'; import { getFirstName, maskString } from '../../utils/utils'; import { attributeToPosition } from '../../../common/src/constants/constants'; import USER from '../images/user.png' -import { App } from '../utils/AppClass'; -import { DEFAULT_ADDRESS } from '@env'; import { borderColor, componentBgColor, componentBgColor2, textColor1, textColor2 } from '../utils/colors'; -import ENS from "../images/ens_mark_dao.png" -import { useToastController } from '@tamagui/toast' import { ethers } from 'ethers'; import { Platform } from 'react-native'; -import { formatAttribute } from '../utils/utils'; +import { formatAttribute, Steps } from '../utils/utils'; +import { downloadZkey } from '../utils/zkeyDownload'; +import useUserStore from '../stores/userStore'; +import useNavigationStore from '../stores/navigationStore'; +import { AppType } from '../utils/appType'; +import useSbtStore from '../stores/sbtStore'; -interface ProveScreenProps { - selectedApp: App | null; - passportData: any; - disclosure: { [key: string]: boolean }; - handleDisclosureChange: (field: string) => void; - address: string; - setAddress: (address: string) => void; - generatingProof: boolean; - handleProve: () => void; - handleMint: () => void; - step: number; - mintText: string; - proof: { proof: string, inputs: string } | null; - proofTime: number; - hideData: boolean; - ens: string; - setEns: (ens: string) => void; - majority: number; - setMajority: (age: number) => void; - zkeydownloadStatus: string; -} +export const appStoreMapping = { + 'soulbound': useSbtStore, + // Add more app ID to store mappings as needed +}; -const ProveScreen: React.FC = ({ - passportData, - disclosure, - selectedApp, - handleDisclosureChange, - address, - setAddress, - generatingProof, - handleProve, - hideData, - ens, - setEns, - majority, - setMajority, - zkeydownloadStatus -}) => { +const ProveScreen: React.FC = () => { + const selectedApp = useNavigationStore(state => state.selectedApp) as AppType; + const { + hideData, + isZkeyDownloading, + step, + } = useNavigationStore() + + const { + fields, + handleProve, + circuit, + } = selectedApp + + const useAppStore = appStoreMapping[selectedApp.id as keyof typeof appStoreMapping] + + const { + address, + majority, + disclosure, + update + } = useAppStore(); + + const { + registered, + passportData, + } = useUserStore(); + + const handleDisclosureChange = (field: string) => { + const requiredOrOptional = selectedApp.disclosureOptions[field as keyof typeof selectedApp.disclosureOptions]; + + if (requiredOrOptional === 'required') { + return; + } + + update({ + disclosure: { + ...disclosure, + [field]: !disclosure[field as keyof typeof disclosure] + } + }); + }; + const { height } = useWindowDimensions(); - const [inputValue, setInputValue] = useState(DEFAULT_ADDRESS ?? ''); - const provider = new ethers.JsonRpcProvider(`https://eth-mainnet.g.alchemy.com/v2/lpOn3k6Fezetn1e5QF-iEsn-J0C6oGE0`); - const toast = useToastController() - useEffect(() => { - if (ens != '' && inputValue == '') { - setInputValue(ens); - - } - else if (address != ethers.ZeroAddress && inputValue == '') { - setInputValue(address); - } + // this already checks if downloading is required + downloadZkey(circuit); }, []) - useEffect(() => { - const resolveENS = async () => { - if (inputValue != ens) { - if (inputValue.endsWith('.eth')) { - try { - toast.show('🔭 Looking onchain', { - message: 'Looking for ' + inputValue, - customData: { - type: "info", - }, - }) - - const resolvedAddress = await provider.resolveName(inputValue); - if (resolvedAddress) { - console.log("new address settled:" + resolvedAddress); - setAddress(resolvedAddress); - setEns(inputValue); - toast.show('✨ Welcome ✨', { - message: 'Nice to meet you ' + inputValue, - customData: { - type: "success", - }, - }) - if (hideData) { - console.log(maskString(address)); - } - } else { - toast.show('Error', { - message: inputValue + ' not found ', - customData: { - type: "error", - }, - }) - } - } catch (error) { - toast.show('Error', { - message: 'Check input format or RPC provider or internet connection', - customData: { - type: "error", - }, - }) - } - } - else if (inputValue.length === 42 && inputValue.startsWith('0x')) { - setAddress(inputValue); - } - }; - }; - - resolveENS(); - }, [inputValue]); - return ( - {hideData ? - 750 ? 150 : 100} - h={height > 750 ? 190 : 80} - borderRadius={height > 800 ? "$7" : "$6"} - source={{ - uri: USER, - }} - /> : - 750 ? 150 : 110} - h={height > 750 ? 190 : 130} - borderRadius={height > 750 ? "$7" : "$6"} - source={{ - uri: passportData.photoBase64 ?? USER, - }} - /> - + {hideData + ? 750 ? 150 : 100} + h={height > 750 ? 190 : 80} + borderRadius={height > 800 ? "$7" : "$6"} + source={{ + uri: USER, + }} + /> + : 750 ? 150 : 110} + h={height > 750 ? 190 : 130} + borderRadius={height > 750 ? "$7" : "$6"} + source={{ + uri: passportData.photoBase64 ?? USER, + }} + /> } - Hi {hideData ? maskString(getFirstName(passportData.mrz)) : getFirstName(passportData.mrz)} 👋 + + Hi {" "} + { + hideData + ? maskString(getFirstName(passportData.mrz)) + : getFirstName(passportData.mrz) + } + 👋 + - - - - - - - - Address or ENS - - - - - - - + {fields.map((Field, index) => ( + + ))} @@ -186,29 +117,39 @@ const ProveScreen: React.FC = ({ Disclose - {/* */} - Select optional data + Select what to disclose - + - {selectedApp && Object.keys(selectedApp.disclosure).map((key) => { - const key_ = key as string; - const indexes = attributeToPosition[key_]; + {selectedApp && Object.keys(selectedApp.disclosureOptions).map((key) => { + const key_ = key; + const indexes = attributeToPosition[key_ as keyof typeof attributeToPosition]; const keyFormatted = key_.replace(/_/g, ' ').split(' ').map((word: string) => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); const mrzAttribute = passportData.mrz.slice(indexes[0], indexes[1] + 1); const mrzAttributeFormatted = formatAttribute(key_, mrzAttribute); return ( - + handleDisclosureChange(key_)} > handleDisclosureChange(key_)} aria-label={keyFormatted} size="$6" @@ -219,28 +160,52 @@ const ProveScreen: React.FC = ({ {keyFormatted}: + {key_ === 'older_than' ? ( {majority} yo - - + + ) : ( - {hideData ? maskString(mrzAttributeFormatted) : mrzAttributeFormatted} + + {hideData ? maskString(mrzAttributeFormatted) : mrzAttributeFormatted} + )} ); })} - - {(height > 750) && This operation can take up to 2 mn, phone may freeze during this time} + { + (height > 750) && + + This operation can take up to 1 mn, phone may freeze during this time + + } - ); }; diff --git a/app/src/screens/ScanScreen.tsx b/app/src/screens/ScanScreen.tsx index 40319e7a5..4b06ca406 100644 --- a/app/src/screens/ScanScreen.tsx +++ b/app/src/screens/ScanScreen.tsx @@ -7,16 +7,15 @@ import { useToastController } from '@tamagui/toast' import NFCHelp from '../images/nfc_help.png' import SCANHelp from '../images/scan_help.png' import { Linking } from 'react-native'; +import { startCameraScan } from '../utils/cameraScanner'; interface ScanScreenProps { - onStartCameraScan: () => void; handleNFCScan: () => void; step: number; } -const ScanScreen: React.FC = ({ onStartCameraScan, handleNFCScan, step }) => { - const toast = useToastController(); +const ScanScreen: React.FC = ({ handleNFCScan, step }) => { return ( @@ -56,7 +55,7 @@ const ScanScreen: React.FC = ({ onStartCameraScan, handleNFCSca )} - + + + ) : ( + + + + + + ZK proof generated 🎉 + + + Proof generation duration: {formatDuration(proofTime as number)} + + + + {beforeSendText1} + + + {beforeSendText2} + + + + + + + )} + + ); +}; + +export default SendProofScreen; diff --git a/app/src/stores/navigationStore.ts b/app/src/stores/navigationStore.ts new file mode 100644 index 000000000..7ba44220e --- /dev/null +++ b/app/src/stores/navigationStore.ts @@ -0,0 +1,51 @@ +import { create } from 'zustand' +import { IsZkeyDownloading, ShowWarningModalProps } from '../utils/zkeyDownload'; +import { Steps } from '../utils/utils'; +import { useToastController } from '@tamagui/toast'; +import { AppType } from '../utils/appType'; + +interface NavigationState { + step: number + isZkeyDownloading: IsZkeyDownloading + showWarningModal: ShowWarningModalProps + hideData: boolean + toast: ReturnType | null + selectedTab: string + selectedApp: AppType | null + setToast: (toast: ReturnType) => void; + setStep: (step: number) => void + update: (patch: any) => void +} + +const useNavigationStore = create((set, get) => ({ + step: Steps.MRZ_SCAN, + isZkeyDownloading: { + register_sha256WithRSAEncryption_65537: false, + disclose: false, + proof_of_passport: false, + }, + showWarningModal: { + show: false, + circuit: "", + size: 0, + }, + hideData: false, + + toast: null, + + selectedTab: "scan", + selectedApp: null, + + setToast: (toast) => set({ toast }), + + setStep: (step) => set({ step }), + + update: (patch) => { + set({ + ...get(), + ...patch, + }); + }, +})) + +export default useNavigationStore \ No newline at end of file diff --git a/app/src/stores/sbtStore.ts b/app/src/stores/sbtStore.ts new file mode 100644 index 000000000..c2d368ede --- /dev/null +++ b/app/src/stores/sbtStore.ts @@ -0,0 +1,55 @@ +import { ethers } from 'ethers' +import { create } from 'zustand' +import { Proof } from '../../../common/src/utils/types' + +interface SbtState { + address: string + majority: number + ens: string + disclosure: { + nationality: boolean + expiry_date: boolean + older_than: boolean + } + + proof: Proof | null + proofTime: number | null + + proofSentText: string + txHash: string + + appAlreadyUsed: boolean + + update: (patch: any) => void + cleanAppState: () => void +} + +const useSbtStore = create((set, get) => ({ + address: ethers.ZeroAddress, + majority: 18, + ens: "", + disclosure: { + nationality: false, + expiry_date: false, + older_than: false, + }, + + proof: null, + proofTime: null, + + proofSentText: "", + txHash: "", + + appAlreadyUsed: false, + + update: (patch) => { + set({ + ...get(), + ...patch, + }); + }, + + cleanAppState: () => set({}, true), +})) + +export default useSbtStore \ No newline at end of file diff --git a/app/src/stores/userStore.ts b/app/src/stores/userStore.ts new file mode 100644 index 000000000..558fa6c97 --- /dev/null +++ b/app/src/stores/userStore.ts @@ -0,0 +1,184 @@ +import { create } from 'zustand' +import { + DEFAULT_PNUMBER, + DEFAULT_DOB, + DEFAULT_DOE, +} from '@env'; +import { mockPassportData_sha256WithRSAEncryption_65537 } from '../../../common/src/utils/mockPassportData'; +import { PassportData } from '../../../common/src/utils/types'; +import * as Keychain from 'react-native-keychain'; +import * as amplitude from '@amplitude/analytics-react-native'; +import useNavigationStore from './navigationStore'; +import { Steps } from '../utils/utils'; +import { ethers } from 'ethers'; +import { downloadZkey } from '../utils/zkeyDownload'; + +interface UserState { + passportNumber: string + dateOfBirth: string + dateOfExpiry: string + registered: boolean + passportData: PassportData + secret: string + initUserStore: () => void + registerPassportData: (passportData: PassportData) => void + registerCommitment: (secret: string, passportData: PassportData) => void + clearPassportDataFromStorage: () => void + clearSecretFromStorage: () => void + update: (patch: any) => void + deleteMrzFields: () => void +} + +const useUserStore = create((set, get) => ({ + passportNumber: DEFAULT_PNUMBER ?? "", + dateOfBirth: DEFAULT_DOB ?? "", + dateOfExpiry: DEFAULT_DOE ?? "", + + registered: false, + passportData: mockPassportData_sha256WithRSAEncryption_65537, + secret: "", + + // When user opens the app, checks presence of passportData + // - If passportData is not present, starts the onboarding flow + // - If passportData is present, then secret must be here too (they are always set together). Request the tree. + // - If the commitment is present in the tree, proceed to main screen + // - If the commitment is not present in the tree, proceed to main screen AND try registering it in the background + initUserStore: async () => { + const passportDataCreds = await Keychain.getGenericPassword({ service: "passportData" }); + if (!passportDataCreds) { + console.log("No passport data found, starting onboarding flow") + return; + } + const secretCreds = await Keychain.getGenericPassword({ service: "secret" }) + + const secret = (secretCreds as Keychain.UserCredentials).password + + set({ + passportData: JSON.parse(passportDataCreds.password), + secret, + }); + useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED); // this currently means go to app selection screen + + // download zkeys if they are not already downloaded + // downloadZkey("register_sha256WithRSAEncryption_65537"); // might move after nfc scanning + // downloadZkey("disclose"); + downloadZkey("proof_of_passport"); + + // TODO: check if the commitment is already registered, if not retry registering it + + // set({ + // registered: true, + // }); + }, + + // When reading passport for the first time: + // - Check presence of secret. If there is none, create one and store it + // - Store the passportData and try registering the commitment in the background + registerPassportData: async (passportData) => { + const secretCreds = await Keychain.getGenericPassword({ service: "secret" }); + + if (secretCreds && secretCreds.password) { + // This should only ever happen if the user deletes the passport data in the options + console.log("secret is already registered, let's keep it.") + } else { + const randomWallet = ethers.Wallet.createRandom(); + const secret = randomWallet.privateKey; + await Keychain.setGenericPassword("secret", secret, { service: "secret" }); + } + + const newSecretCreds = await Keychain.getGenericPassword({ service: "secret" }) + const secret = (newSecretCreds as Keychain.UserCredentials).password + + const passportDataCreds = await Keychain.getGenericPassword({ service: "passportData" }); + + if (passportDataCreds && passportDataCreds.password) { + throw new Error("passportData is already registered, this should never happen") + } + + await Keychain.setGenericPassword("passportData", JSON.stringify(passportData), { service: "passportData" }); + + get().registerCommitment( + secret, + passportData + ) + + set({ + passportData, + secret + }); + }, + + registerCommitment: async (secret, passportData) => { + // just like in handleProve, generate inputs and launch commitment registration + const { + toast + } = useNavigationStore.getState(); + + try { + // const inputs = generateCircuitInputsRegister( + // passportData, + // secret, + // { developmentMode: false } + // ); + + // amplitude.track(`Sig alg supported: ${passportData.signatureAlgorithm}`); + + // Object.keys(inputs).forEach((key) => { + // if (Array.isArray(inputs[key as keyof typeof inputs])) { + // console.log(key, inputs[key as keyof typeof inputs].slice(0, 10), '...'); + // } else { + // console.log(key, inputs[key as keyof typeof inputs]); + // } + // }); + + // const start = Date.now(); + + // const proof = await generateProof( + // `Register_${passportData.signatureAlgorithm}`, // TODO format it + // inputs, + // ); + + // const end = Date.now(); + // console.log('Total proof time from frontend:', end - start); + // amplitude.track('Proof generation successful, took ' + ((end - start) / 1000) + ' seconds'); + + // // TODO send the proof to the relayer + + // set({ + // registered: true, + // }); + } catch (error: any) { + console.error(error); + toast?.show('Error', { + message: "Error registering your identity, please relaunch the app", + customData: { + type: "error", + }, + }) + amplitude.track(error.message); + } + }, + + clearPassportDataFromStorage: async () => { + await Keychain.resetGenericPassword({ service: "passportData" }); + }, + + clearSecretFromStorage: async () => { + await Keychain.resetGenericPassword({ service: "secret" }); + }, + + update: (patch) => { + set({ + ...get(), + ...patch, + }); + }, + + deleteMrzFields: () => set({ + passportNumber: "", + dateOfBirth: "", + dateOfExpiry: "", + }, true), +})) + +export default useUserStore \ No newline at end of file diff --git a/app/src/utils/AppClass.ts b/app/src/utils/AppClass.ts deleted file mode 100644 index e129a0ebf..000000000 --- a/app/src/utils/AppClass.ts +++ /dev/null @@ -1,55 +0,0 @@ -type Disclosure = { - [key: string]: boolean; -}; - -export class App { - id: string; - name: string; - disclosure: Disclosure; - mintphrase: string; - disclosurephrase: string; - - constructor( - id: string, - name: string, - disclosure: Disclosure, - mintphrase: string, - disclosurephrase: string, - ) { - this.id = id; - this.name = name; - this.disclosure = disclosure; - this.disclosurephrase = disclosurephrase; - this.mintphrase = mintphrase; - } -} - -export const gitcoin = new App( - 'gitcoin', - 'Gitcoin', - {}, - 'Add to Gitcoin passport', - "Gitcoin passport doesn't require disclosure of any data.", -); - -export const soulbound = new App( - 'soulbound', - 'Soulbound token', - { - nationality: false, - expiry_date: false, - older_than: false - }, - 'Mint Soulbound token', - 'Choose the information you want to disclose and mint your SBT.', -); - -export const zuzalu = new App( - 'zuzalu', - 'Zupass', - { - date_of_expiry: false - }, - 'Add to Zupass', - 'Zupass requires the following information:', -); diff --git a/app/src/utils/appType.ts b/app/src/utils/appType.ts new file mode 100644 index 000000000..fb07c0032 --- /dev/null +++ b/app/src/utils/appType.ts @@ -0,0 +1,43 @@ +import { CircuitName } from "./zkeyDownload"; + +type DisclosureOption = "required" | "optional"; + +type Disclosure = { + [key: string]: DisclosureOption; +}; + +export type AppType = { + id: string; + + // AppScreen UI + title: string, + description: string, + background?: string, + colorOfTheText: string, + selectable: boolean, + icon: any, + tags: React.JSX.Element[] + + // ProveScreen UI + name: string; + disclosureOptions: Disclosure | {}; + + beforeSendText1: string; + beforeSendText2: string; + sendButtonText: string; + sendingButtonText: string; + + successTitle: string; + successText: string; + + successComponent: () => React.JSX.Element; + finalButtonAction: () => void; + finalButtonText: string; + + circuit: CircuitName; // circuit and witness calculator name + + fields: React.FC[]; + + handleProve: () => void; + handleSendProof: () => void; +} diff --git a/app/src/utils/cameraScanner.ts b/app/src/utils/cameraScanner.ts index 18b8ad169..0f0707877 100644 --- a/app/src/utils/cameraScanner.ts +++ b/app/src/utils/cameraScanner.ts @@ -1,32 +1,26 @@ import { NativeModules, Platform } from 'react-native'; import { formatDateToYYMMDD, extractMRZInfo, Steps } from './utils'; import * as amplitude from '@amplitude/analytics-react-native'; +import useUserStore from '../stores/userStore'; +import useNavigationStore from '../stores/navigationStore'; -interface CameraScannerProps { - setPassportNumber: (value: string) => void; - setDateOfBirth: (value: string) => void; - setDateOfExpiry: (value: string) => void; - setStep: (value: number) => void; - toast: any -} +export const startCameraScan = async () => { + const {toast, setStep} = useNavigationStore.getState(); -export const startCameraScan = async ({ - setPassportNumber, - setDateOfBirth, - setDateOfExpiry, - setStep, - toast -}: CameraScannerProps) => { if (Platform.OS === 'ios') { try { const result = await NativeModules.MRZScannerModule.startScanning(); console.log("Scan result:", result); console.log(`Document Number: ${result.documentNumber}, Expiry Date: ${result.expiryDate}, Birth Date: ${result.birthDate}`); - setPassportNumber(result.documentNumber); - setDateOfBirth(formatDateToYYMMDD(result.birthDate)); - setDateOfExpiry(formatDateToYYMMDD(result.expiryDate)); + + useUserStore.setState({ + passportNumber: result.documentNumber, + dateOfBirth: formatDateToYYMMDD(result.birthDate), + dateOfExpiry: formatDateToYYMMDD(result.expiryDate), + }) + setStep(Steps.MRZ_SCAN_COMPLETED); - toast.show("Scan successful", { + toast?.show("Scan successful", { message: 'Nice to meet you!', customData: { type: "success", @@ -42,12 +36,16 @@ export const startCameraScan = async ({ .then((mrzInfo: string) => { try { const { documentNumber, birthDate, expiryDate } = extractMRZInfo(mrzInfo); - setPassportNumber(documentNumber); - setDateOfBirth(birthDate); - setDateOfExpiry(expiryDate); + + useUserStore.setState({ + passportNumber: documentNumber, + dateOfBirth: birthDate, + dateOfExpiry: expiryDate, + }) + setStep(Steps.MRZ_SCAN_COMPLETED); amplitude.track('Camera scan successful'); - toast.show("Scan successful", { + toast?.show("Scan successful", { message: 'Nice to meet you!', customData: { type: "success", diff --git a/app/src/utils/minter.ts b/app/src/utils/minter.ts index 7262fd372..5f6b42e54 100644 --- a/app/src/utils/minter.ts +++ b/app/src/utils/minter.ts @@ -3,104 +3,44 @@ import axios from 'axios'; import groth16ExportSolidityCallData from '../../utils/snarkjs'; import contractAddresses from "../../deployments/addresses.json"; import proofOfPassportArtefact from "../../deployments/ProofOfPassport.json"; -import { Steps } from './utils'; -import { AWS_ENDPOINT } from '../../../common/src/constants/constants'; +import { RELAYER_URL } from '../../../common/src/constants/constants'; +import { Proof } from "../../../common/src/utils/types"; -interface MinterProps { - proof: { proof: string; inputs: string } | null; - setStep: (value: number) => void; - setMintText: (value: string) => void; - toast: any -} - -export const mint = async ({ proof, setStep, setMintText, toast }: MinterProps) => { - setStep(Steps.TX_MINTING); - if (!proof?.proof || !proof?.inputs) { - console.log('proof or inputs is null'); - return; - } +export const mintSBT = async ( + proof: Proof, + provider: ethers.JsonRpcProvider, + chainName: string +) => { if (!contractAddresses.ProofOfPassport || !proofOfPassportArtefact.abi) { console.log('contracts addresses or abi not found'); return; } // Format the proof and publicInputs as calldata for the verifier contract - const p = JSON.parse(proof.proof); - const i = JSON.parse(proof.inputs); - console.log('p', p); - console.log('i', i); - const cd = groth16ExportSolidityCallData(p, i); + const cd = groth16ExportSolidityCallData(proof.proof, proof.pub_signals); const callData = JSON.parse(`[${cd}]`); console.log('callData', callData); - - // format transaction - // for now, we do it all on sepolia try { - const provider = new ethers.JsonRpcProvider('https://gateway.tenderly.co/public/sepolia'); - const proofOfPassportOnSepolia = new ethers.Contract(contractAddresses.ProofOfPassport, proofOfPassportArtefact.abi, provider); + const proofOfPassportContract = new ethers.Contract( + contractAddresses.ProofOfPassport, + proofOfPassportArtefact.abi, + provider + ); - const transactionRequest = await proofOfPassportOnSepolia + const transactionRequest = await proofOfPassportContract .mint.populateTransaction(...callData); console.log('transactionRequest', transactionRequest); - const response = await axios.post(AWS_ENDPOINT, { - chain: "sepolia", + const response = await axios.post(RELAYER_URL, { + chain: chainName, tx_data: transactionRequest }); console.log('response status', response.status); console.log('response data', response.data); - setMintText(`Network: Sepolia. Transaction hash: ${response.data.hash}`); - - const receipt = await provider.waitForTransaction(response.data.hash); - console.log('receipt status:', receipt?.status); - - if (receipt?.status === 1) { - toast.show('🎊', { - message: "SBT minted", - customData: { - type: "success", - }, - }) - setMintText(`SBT minted. Network: Sepolia. Transaction hash: ${response.data.hash}`); - setStep(Steps.TX_MINTED); - } else { - toast.show('Error', { - message: "Proof of passport minting failed", - customData: { - type: "error", - }, - }) - setMintText(`Error minting SBT. Network: Sepolia. Transaction hash: ${response.data.hash}`); - setStep(Steps.PROOF_GENERATED); - } + return response; } catch (err: any) { console.log('err', err); - setStep(Steps.PROOF_GENERATED); - setMintText(`Error minting SBT. Network: Sepolia.`); - if (err.isAxiosError && err.response) { - const errorMessage = err.response.data.error; - console.log('Server error message:', errorMessage); - - // parse blockchain error and show it - const match = errorMessage.match(/execution reverted: "([^"]*)"/); - if (match && match[1]) { - console.log('Parsed blockchain error:', match[1]); - toast.show('Error', { - message: `Error: ${match[1]}`, - customData: { - type: "error", - }, - }) - } else { - toast.show('Error', { - message: `Error: mint failed`, - customData: { - type: "error", - }, - }) - console.log('Failed to parse blockchain error'); - } - } + throw new Error(err); } }; \ No newline at end of file diff --git a/app/src/utils/nfcScanner.ts b/app/src/utils/nfcScanner.ts index fb603f670..34d16abef 100644 --- a/app/src/utils/nfcScanner.ts +++ b/app/src/utils/nfcScanner.ts @@ -8,27 +8,26 @@ import { PassportData } from '../../../common/src/utils/types'; import forge from 'node-forge'; import { Buffer } from 'buffer'; import * as amplitude from '@amplitude/analytics-react-native'; +import useUserStore from '../stores/userStore'; +import useNavigationStore from '../stores/navigationStore'; -interface NFCScannerProps { - passportNumber: string; - dateOfBirth: string; - dateOfExpiry: string; - setPassportData: (data: PassportData) => void; - setStep: (value: number) => void; - toast: any; -} +export const scan = async () => { + const { + passportNumber, + dateOfBirth, + dateOfExpiry + } = useUserStore.getState() + + const {toast, setStep} = useNavigationStore.getState(); + + const check = checkInputs( + passportNumber, + dateOfBirth, + dateOfExpiry + ); -export const scan = async ({ - passportNumber, - dateOfBirth, - dateOfExpiry, - setPassportData, - setStep, - toast -}: NFCScannerProps) => { - const check = checkInputs(passportNumber, dateOfBirth, dateOfExpiry); if (!check.success) { - toast.show("Unvailable", { + toast?.show("Unvailable", { message: check.message, customData: { type: "info", @@ -41,20 +40,20 @@ export const scan = async ({ setStep(Steps.NFC_SCANNING); if (Platform.OS === 'android') { - scanAndroid(passportNumber, dateOfBirth, dateOfExpiry, setPassportData, setStep, toast); + scanAndroid(); } else { - scanIOS(passportNumber, dateOfBirth, dateOfExpiry, setPassportData, setStep, toast); + scanIOS(); } }; -const scanAndroid = async ( - passportNumber: string, - dateOfBirth: string, - dateOfExpiry: string, - setPassportData: (data: PassportData) => void, - setStep: (value: number) => void, - toast: any -) => { +const scanAndroid = async () => { + const { + passportNumber, + dateOfBirth, + dateOfExpiry + } = useUserStore.getState() + const {toast, setStep} = useNavigationStore.getState(); + try { const response = await PassportReader.scan({ documentNumber: passportNumber, @@ -63,12 +62,12 @@ const scanAndroid = async ( }); console.log('scanned'); amplitude.track('NFC scan successful'); - handleResponseAndroid(response, setPassportData, setStep); + handleResponseAndroid(response); } catch (e: any) { console.log('error during scan:', e); setStep(Steps.MRZ_SCAN_COMPLETED); amplitude.track('NFC scan unsuccessful', { error: JSON.stringify(e) }); - toast.show('Error', { + toast?.show('Error', { message: e.message, customData: { type: "error", @@ -78,14 +77,14 @@ const scanAndroid = async ( } }; -const scanIOS = async ( - passportNumber: string, - dateOfBirth: string, - dateOfExpiry: string, - setPassportData: (data: PassportData) => void, - setStep: (value: number) => void, - toast: any -) => { +const scanIOS = async () => { + const { + passportNumber, + dateOfBirth, + dateOfExpiry + } = useUserStore.getState() + const {toast, setStep} = useNavigationStore.getState(); + try { const response = await NativeModules.PassportReader.scanPassport( passportNumber, @@ -93,14 +92,14 @@ const scanIOS = async ( dateOfExpiry ); console.log('scanned'); - handleResponseIOS(response, setPassportData, setStep); + handleResponseIOS(response); amplitude.track('NFC scan successful'); } catch (e: any) { console.log('error during scan:', e); setStep(Steps.MRZ_SCAN_COMPLETED); amplitude.track(`NFC scan unsuccessful, error ${e.message}`); if (!e.message.includes("UserCanceled")) { - toast.show('Failed to read passport', { + toast?.show('Failed to read passport', { message: e.message, customData: { type: "error", @@ -112,8 +111,6 @@ const scanIOS = async ( const handleResponseIOS = async ( response: any, - setPassportData: (data: PassportData) => void, - setStep: (value: number) => void, ) => { const parsed = JSON.parse(response); @@ -170,14 +167,12 @@ const handleResponseIOS = async ( // console.log('passportData', JSON.stringify(passportData, null, 2)); - setPassportData(passportData); - setStep(Steps.NFC_SCAN_COMPLETED); + useUserStore.getState().registerPassportData(passportData) + useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED); }; const handleResponseAndroid = async ( response: any, - setPassportData: (data: PassportData) => void, - setStep: (value: number) => void, ) => { const { mrz, @@ -226,6 +221,6 @@ const handleResponseAndroid = async ( console.log("unicodeVersion", unicodeVersion) console.log("encapContent", encapContent) - setPassportData(passportData); - setStep(Steps.NFC_SCAN_COMPLETED); + useUserStore.getState().registerPassportData(passportData) + useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED); }; \ No newline at end of file diff --git a/app/src/utils/prover.ts b/app/src/utils/prover.ts index 2704dc5a8..62e2c7915 100644 --- a/app/src/utils/prover.ts +++ b/app/src/utils/prover.ts @@ -1,144 +1,46 @@ import { NativeModules, Platform } from 'react-native'; -import { revealBitmapFromMapping } from '../../../common/src/utils/revealBitmap'; -import { generateCircuitInputs } from '../../../common/src/utils/generateInputs'; -import { formatProof, formatInputs } from '../../../common/src/utils/utils'; -import { Steps } from './utils'; -import { PassportData } from '../../../common/src/utils/types'; -import * as amplitude from '@amplitude/analytics-react-native'; +import { parseProofAndroid } from './utils'; +import RNFS from 'react-native-fs'; -interface ProverProps { - passportData: PassportData | null; - disclosure: any; - address: string; - majority: number; - setStep: (value: number) => void; - setGeneratingProof: (value: boolean) => void; - setProofTime: (value: number) => void; - setProof: (value: { proof: string; inputs: string } | null) => void; - toast: any -} - -export const prove = async ({ - passportData, - disclosure, - address, - majority, - setStep, - setGeneratingProof, - setProofTime, - setProof, - toast -}: ProverProps) => { - if (passportData === null) { - console.log('passport data is null'); - return; - } - setStep(Steps.GENERATING_PROOF); - setGeneratingProof(true); - await new Promise(resolve => setTimeout(resolve, 10)); - - const reveal_bitmap = revealBitmapFromMapping(disclosure); - - try { - const inputs = generateCircuitInputs( - passportData, - reveal_bitmap, - address, - majority, - { developmentMode: false } - ); - amplitude.track('Sig alg supported: ' + passportData.signatureAlgorithm); - - Object.keys(inputs).forEach((key) => { - if (Array.isArray(inputs[key as keyof typeof inputs])) { - console.log(key, inputs[key as keyof typeof inputs].slice(0, 10), '...'); - } else { - console.log(key, inputs[key as keyof typeof inputs]); - } - }); - - const start = Date.now(); - await generateProof(inputs, setProofTime, setProof, setGeneratingProof, setStep); - const end = Date.now(); - console.log('Total proof time from frontend:', end - start); - amplitude.track('Proof generation successful, took ' + ((end - start) / 1000) + ' seconds'); - } catch (error: any) { - console.error(error); - toast.show('Error', { - message: error.message, - customData: { - type: "error", - }, - }) - setStep(Steps.NFC_SCAN_COMPLETED); - setGeneratingProof(false); - amplitude.track(error.message); - } -}; - -const generateProof = async ( +export const generateProof = async ( + circuit: string, inputs: any, - setProofTime: (value: number) => void, - setProof: (value: { proof: string; inputs: string } | null) => void, - setGeneratingProof: (value: boolean) => void, - setStep: (value: number) => void, ) => { try { console.log('launching generateProof function'); console.log('inputs in App.tsx', inputs); - if (Platform.OS == "android") { - await NativeModules.Prover.runInitAction(); - } + const zkey_path = `${RNFS.DocumentDirectoryPath}/${circuit}.zkey` + // Example: "/data/user/0/com.proofofpassport/files/proof_of_passport.zkey" on android + const witness_calculator = circuit; + const dat_file_name = circuit + + const response = await NativeModules.Prover.runProveAction( + zkey_path, + witness_calculator, + dat_file_name, + inputs + ); - console.log('running prove action'); - const startTime = Date.now(); - const response = await NativeModules.Prover.runProveAction(inputs); - const endTime = Date.now(); - console.log('time spent:', endTime - startTime); console.log('proof response:', response); - console.log('typeof proof response:', typeof response); - setProofTime(endTime - startTime); if (Platform.OS === 'android') { const parsedResponse = parseProofAndroid(response); - const finalProof = { - proof: JSON.stringify(formatProof(parsedResponse.proof)), - inputs: JSON.stringify(formatInputs(parsedResponse.inputs)), - }; - console.log('finalProof:', finalProof); - - setProof(finalProof); - setGeneratingProof(false); - setStep(Steps.PROOF_GENERATED); + console.log('parsedResponse', parsedResponse); + return parsedResponse } else { const parsedResponse = JSON.parse(response); console.log('parsedResponse', parsedResponse); - console.log('parsedResponse.proof:', parsedResponse.proof); console.log('parsedResponse.inputs:', parsedResponse.inputs); - const finalProof = { - proof: JSON.stringify(parsedResponse.proof), - inputs: JSON.stringify(parsedResponse.inputs), - }; - console.log('finalProof:', finalProof); - - setProof(finalProof); - setGeneratingProof(false); - setStep(Steps.PROOF_GENERATED); + return { + proof: parsedResponse.proof, + pub_signals: parsedResponse.inputs, + } } } catch (err: any) { console.log('err', err); + throw new Error(err); } -}; - -const parseProofAndroid = (response: any) => { - const match = response.match(/GenerateProofResult\(proof=\[(.*?)\], inputs=\[(.*?)\]\)/); - if (!match) throw new Error('Invalid input format'); - - return { - proof: match[1].split(',').map((n: any) => (parseInt(n.trim()) + 256) % 256), - inputs: match[2].split(',').map((n: any) => (parseInt(n.trim()) + 256) % 256) - }; }; \ No newline at end of file diff --git a/app/src/utils/utils.ts b/app/src/utils/utils.ts index e5a7f5fa8..db0b1984e 100644 --- a/app/src/utils/utils.ts +++ b/app/src/utils/utils.ts @@ -1,6 +1,7 @@ // Function to extract information from a two-line MRZ. import { countryCodes } from "../../../common/src/constants/constants"; +import { Proof } from "../../../common/src/utils/types"; // The actual parsing would depend on the standard being used (TD1, TD2, TD3, MRVA, MRVB). export function extractMRZInfo(mrzString: string) { @@ -40,8 +41,8 @@ export const Steps = { APP_SELECTED: 5, GENERATING_PROOF: 6, PROOF_GENERATED: 7, - TX_MINTING: 8, - TX_MINTED: 9 + PROOF_SENDING: 8, + PROOF_SENT: 9 }; export function formatAttribute(key: string, attribute: string) { @@ -54,4 +55,24 @@ export function formatAttribute(key: string, attribute: string) { return countryCodes[attribute as keyof typeof countryCodes] } return attribute; -} \ No newline at end of file +} + +export const parseProofAndroid = (response: string) => { + const match = response.match(/ZkProof\(proof=Proof\(pi_a=\[(.*?)\], pi_b=\[\[(.*?)\], \[(.*?)\], \[1, 0\]\], pi_c=\[(.*?)\], protocol=groth16, curve=bn128\), pub_signals=\[(.*?)\]\)/); + + if (!match) throw new Error('Invalid input format'); + + const [, pi_a, pi_b_1, pi_b_2, pi_c, pub_signals] = match; + + return { + proof: { + a: pi_a.split(',').map((n: string) => n.trim()), + b: [ + pi_b_1.split(',').map((n: string) => n.trim()), + pi_b_2.split(',').map((n: string) => n.trim()), + ], + c: pi_c.split(',').map((n: string) => n.trim()), + }, + pub_signals: pub_signals.split(',').map((n: string) => n.trim()) + } as Proof; +}; \ No newline at end of file diff --git a/app/src/utils/zkeyDownload.ts b/app/src/utils/zkeyDownload.ts index b7691c118..163a9022d 100644 --- a/app/src/utils/zkeyDownload.ts +++ b/app/src/utils/zkeyDownload.ts @@ -1,138 +1,204 @@ -import { - NativeModules, - Platform, -} from 'react-native'; import RNFS from 'react-native-fs'; -import { ARKZKEY_URL, ZKEY_NAME, ZKEY_URL } from '../../../common/src/constants/constants'; import * as amplitude from '@amplitude/analytics-react-native'; import NetInfo from '@react-native-community/netinfo'; import axios from 'axios'; import { unzip } from 'react-native-zip-archive'; +import useNavigationStore from '../stores/navigationStore'; -const localZkeyPath = RNFS.DocumentDirectoryPath + '/proof_of_passport.zkey'; -const localZipPath = RNFS.DocumentDirectoryPath + '/proof_of_passport.zip'; -const localUrlPath = RNFS.DocumentDirectoryPath + '/zkey_url.txt'; +// this should not change, instead update the zkey on the bucket +const zkeyZipUrls = { + register_sha256WithRSAEncryption_65537: "qweqwe", + disclose: "qweqwe", + proof_of_passport: `https://d8o9bercqupgk.cloudfront.net/proof_of_passport.zkey.zip`, +}; -async function initMopro() { - if (Platform.OS === 'android') { - const res = await NativeModules.Prover.runInitAction() - console.log('Mopro init res:', res) - } +export type CircuitName = keyof typeof zkeyZipUrls; + +export type ShowWarningModalProps = { + show: boolean, + circuit: CircuitName | "", + size: number, } +export type IsZkeyDownloading = { + [circuit in CircuitName]: boolean; +} + +// each time we download a zkey, we store the size of the zip file in a file named _zip_size.txt +// we assume a new zkey zip will always have a different size + +// the downloadZkey function downloads a zkey if either: +// 1. the zkey file does not exist +// 2. _zip_size.txt does not show the same size as the server response (zkey is outdated) +// 3. the zkey is currently downloading +// 4. the commitment is already registered and the function is called for a register zkey. If it's the case, there is no need to download the latest zkey. +// => this should be fine is the function is never called after the commitment is registered. + export async function downloadZkey( - setDownloadStatus: (value: "not_started" | "downloading" | "completed" | "error") => void, - toast: any + circuit: CircuitName, ) { - console.log('launching zkey download') - setDownloadStatus('downloading'); - amplitude.track('Downloading zkey...'); + const { + isZkeyDownloading, + update + } = useNavigationStore.getState(); + + const downloadRequired = await isDownloadRequired(circuit, isZkeyDownloading); + if (!downloadRequired) { + console.log(`zkey for ${circuit} already downloaded`) + amplitude.track(`zkey for ${circuit} already downloaded`); + return; + } + + const networkInfo = await NetInfo.fetch(); + console.log('Network type:', networkInfo.type) + if (networkInfo.type === 'wifi') { //todo: no need to check for register circuit as zkey is smol + fetchZkey(circuit); + } else { + const response = await axios.head(zkeyZipUrls[circuit]); + const expectedSize = parseInt(response.headers['content-length'], 10); + + update({ + showWarningModal: { + show: true, + circuit: circuit, + size: expectedSize, + } + }); + } +} + +export async function isDownloadRequired( + circuit: CircuitName, + isZkeyDownloading: IsZkeyDownloading +) { + if (isZkeyDownloading[circuit]) { + return false; + } + + const fileExists = await RNFS.exists(`${RNFS.DocumentDirectoryPath}/${circuit}.zkey`); + if (!fileExists) { + return true; + } + + let storedZipSize = 0; + try { + storedZipSize = Number(await RNFS.readFile(`${RNFS.DocumentDirectoryPath}/${circuit}_zip_size.txt`, 'utf8')); + } catch (error) { + console.log(`${circuit}_zip_size.txt file not found, so assuming zkey is outdated.`); + return true; + } + + console.log('storedZipSize:', storedZipSize) + + const response = await axios.head(zkeyZipUrls[circuit]); + const expectedSize = parseInt(response.headers['content-length'], 10); + + console.log('expectedSize:', expectedSize) + + const isZipComplete = storedZipSize === expectedSize; + + console.log('isZipComplete:', isZipComplete) + + if (!isZipComplete) { + return true; + } + return false +} + +export async function fetchZkey( + circuit: CircuitName, +) { + console.log(`fetching zkey for ${circuit} ...`) + amplitude.track(`fetching zkey for ${circuit} ...`); + + const { + isZkeyDownloading, + toast, + update + } = useNavigationStore.getState(); + + update({ + isZkeyDownloading: { + ...isZkeyDownloading, + [circuit]: true, + } + }); + const startTime = Date.now(); - - const url = Platform.OS === 'android' ? ARKZKEY_URL : ZKEY_URL - let previousPercentComplete = -1; - const options = { - fromUrl: url, - toFile: localZipPath, + fromUrl: zkeyZipUrls[circuit], + toFile: `${RNFS.DocumentDirectoryPath}/${circuit}.zkey.zip`, background: true, begin: () => { console.log('Download has begun'); }, progress: (res: any) => { const percentComplete = Math.floor((res.bytesWritten / res.contentLength) * 100); - if (percentComplete !== previousPercentComplete) { + if (percentComplete % 5 === 0 && percentComplete !== previousPercentComplete) { console.log(`${percentComplete}%`); previousPercentComplete = percentComplete; } - }, + } }; RNFS.downloadFile(options).promise .then(async () => { console.log('Download complete'); + RNFS.readDir(RNFS.DocumentDirectoryPath) .then((result) => { - console.log('Directory contents before:', result); + console.log('Directory contents before unzipping:', result); }) - const unzipPath = RNFS.DocumentDirectoryPath; - await unzip(localZipPath, unzipPath); - const oldPath = `${unzipPath}/${ZKEY_NAME}${Platform.OS === 'android' ? '.arkzkey' : '.zkey'}`; - const newPath = `${unzipPath}/proof_of_passport.zkey`; - await RNFS.moveFile(oldPath, newPath); + await unzip(`${RNFS.DocumentDirectoryPath}/${circuit}.zkey.zip`, RNFS.DocumentDirectoryPath); + RNFS.readDir(RNFS.DocumentDirectoryPath) .then((result) => { - console.log('Directory contents after:', result); + console.log('Directory contents after unzipping:', result); }) console.log('Unzip complete'); - setDownloadStatus('completed') - const endTime = Date.now(); - amplitude.track('zkey download succeeded, took ' + ((endTime - startTime) / 1000) + ' seconds'); - RNFS.writeFile(localUrlPath, url, 'utf8'); - initMopro() + + update({ + isZkeyDownloading: { + ...isZkeyDownloading, + [circuit]: false, + } + }); + + amplitude.track('zkey download succeeded, took ' + ((Date.now() - startTime) / 1000) + ' seconds'); + + const zipSize = await RNFS.stat(`${RNFS.DocumentDirectoryPath}/${circuit}.zkey.zip`); + + console.log('zipSize:', zipSize.size); + + RNFS.writeFile(`${RNFS.DocumentDirectoryPath}/${circuit}_zip_size.txt`, zipSize.size.toString(), 'utf8'); + + console.log('zip size written to file'); + + // delete the zip file + RNFS.unlink(`${RNFS.DocumentDirectoryPath}/${circuit}.zkey.zip`) + .then(() => { + console.log('zip file deleted'); + }) + .catch((error) => { + console.error(error); + }); }) .catch((error) => { console.error(error); - setDownloadStatus('error'); + update({ + isZkeyDownloading: { + ...isZkeyDownloading, + [circuit]: false, + } + }); amplitude.track('zkey download failed: ' + error.message); - toast.show('Error', { + toast?.show('Error', { message: `Error: ${error.message}`, customData: { type: "error", }, }); }); -} - -interface CheckForZkeyProps { - setDownloadStatus: (value: "not_started" | "downloading" | "completed" | "error") => void; - setShowWarning: (value: boolean) => void; - toast: any -} - -export async function checkForZkey({ - setDownloadStatus, - setShowWarning, - toast -}: CheckForZkeyProps) { - console.log('local zip path:', localZipPath) - const url = Platform.OS === 'android' ? ARKZKEY_URL : ZKEY_URL - - let storedUrl = ''; - try { - storedUrl = await RNFS.readFile(localUrlPath, 'utf8'); - } catch (error) { - console.log('zkey_url.txt file not found, so assuming zkey is outdated.'); - } - - const fileExists = await RNFS.exists(localZipPath); - const fileInfo = fileExists ? await RNFS.stat(localZipPath) : null; - - const response = await axios.head(url); - const expectedSize = parseInt(response.headers['content-length'], 10); - const isFileComplete = fileInfo && fileInfo.size === expectedSize; - - console.log('expectedSize:', expectedSize) - console.log('fileInfo.size:', fileInfo?.size) - console.log('isFileComplete:', isFileComplete) - - if (!isFileComplete || url !== storedUrl) { - const state = await NetInfo.fetch(); - console.log('Network start type:', state.type) - if (state.type === 'wifi') { - downloadZkey( - setDownloadStatus, - toast - ) - } else { - setShowWarning(true); - } - } else { - console.log('zkey already downloaded') - amplitude.track('zkey already downloaded'); - setDownloadStatus('completed'); - initMopro() - } -} +} \ No newline at end of file diff --git a/app/utils/utils.ts b/app/utils/utils.ts index 7d404c372..644cbaf48 100644 --- a/app/utils/utils.ts +++ b/app/utils/utils.ts @@ -53,7 +53,7 @@ export const getTx = (input: string | null): string => { return transaction; } -export const shortenInput = (input: string | null): string => { +export const shortenTxHash = (input: string | null): string => { if (!input) return ''; if (input.length > 9) { return input.substring(0, 25) + '\u2026'; diff --git a/app/witnesscalc/build/fr_raw_generic.cpp b/app/witnesscalc/build/fr_raw_generic.cpp index 8378a4ec7..9df3cf796 100644 --- a/app/witnesscalc/build/fr_raw_generic.cpp +++ b/app/witnesscalc/build/fr_raw_generic.cpp @@ -361,4 +361,4 @@ void Fr_rawNot(FrRawElement pRawResult, FrRawElement pRawA) { mpn_sub_n(pRawResult, pRawResult, Fr_rawq, Fr_N64); } -} +} \ No newline at end of file diff --git a/app/witnesscalc/build_gmp.sh b/app/witnesscalc/build_gmp.sh index 8f1b38049..9876dff1c 100755 --- a/app/witnesscalc/build_gmp.sh +++ b/app/witnesscalc/build_gmp.sh @@ -128,7 +128,7 @@ build_android() return 1 fi - export TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64 + export TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64 export TARGET=aarch64-linux-android export API=21 @@ -173,7 +173,7 @@ build_android_x86_64() return 1 fi - export TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64 + export TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64 export TARGET=x86_64-linux-android export API=21 diff --git a/app/witnesscalc/cmake/platform.cmake b/app/witnesscalc/cmake/platform.cmake index 9cd9eab17..f1909ce77 100644 --- a/app/witnesscalc/cmake/platform.cmake +++ b/app/witnesscalc/cmake/platform.cmake @@ -62,7 +62,7 @@ else() endif() -if (CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin") +if (CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin" AND NOT TARGET_PLATFORM MATCHES "^android(_x86_64)?") set(GMP_DEFINIONS -D_LONG_LONG_LIMB) endif() diff --git a/app/witnesscalc/src/CMakeLists.txt b/app/witnesscalc/src/CMakeLists.txt index e8a5e16b1..9bf224ee1 100644 --- a/app/witnesscalc/src/CMakeLists.txt +++ b/app/witnesscalc/src/CMakeLists.txt @@ -17,7 +17,7 @@ endif() if(USE_ASM AND ARCH MATCHES "x86_64") - if (CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin") + if (CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin" AND NOT TARGET_PLATFORM MATCHES "^android(_x86_64)?") set(NASM_FLAGS -fmacho64 --prefix _) else() set(NASM_FLAGS -felf64 -DPIC) diff --git a/app/yarn.lock b/app/yarn.lock index 9afff2fdb..bddaece54 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -1702,7 +1702,7 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@react-native-async-storage/async-storage@^1.17.11", "@react-native-async-storage/async-storage@^1.23.1": +"@react-native-async-storage/async-storage@^1.17.11": version "1.23.1" resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz#cad3cd4fab7dacfe9838dce6ecb352f79150c883" integrity sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA== @@ -4817,6 +4817,11 @@ express@^4.18.2: utils-merge "1.0.1" vary "~1.1.2" +fast-base64-decode@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" + integrity sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -7597,6 +7602,18 @@ react-native-fs@^2.20.0: base-64 "^0.1.0" utf8 "^3.0.0" +react-native-get-random-values@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz#1ca70d1271f4b08af92958803b89dccbda78728d" + integrity sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ== + dependencies: + fast-base64-decode "^1.0.0" + +react-native-keychain@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/react-native-keychain/-/react-native-keychain-8.2.0.tgz#aea82df37aacbb04f8b567a8e0e6d7292025610a" + integrity sha512-SkRtd9McIl1Ss2XSWNLorG+KMEbgeVqX+gV+t3u1EAAqT8q2/OpRmRbxpneT2vnb/dMhiU7g6K/pf3nxLUXRvA== + react-native-passport-reader@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/react-native-passport-reader/-/react-native-passport-reader-1.0.3.tgz#3242bbdb3c1ade4c050a8632cca6f11fe0edc648" @@ -8944,3 +8961,10 @@ zustand@^4.3.8: integrity sha512-XlauQmH64xXSC1qGYNv00ODaQ3B+tNPoy22jv2diYiP4eoDKr9LA+Bh5Bc3gplTrFdb6JVI+N4kc1DZ/tbtfPg== dependencies: use-sync-external-store "1.2.0" + +zustand@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.2.tgz#fddbe7cac1e71d45413b3682cdb47b48034c3848" + integrity sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g== + dependencies: + use-sync-external-store "1.2.0" diff --git a/common/src/constants/constants.ts b/common/src/constants/constants.ts index c9923f5db..37a24916a 100644 --- a/common/src/constants/constants.ts +++ b/common/src/constants/constants.ts @@ -1,8 +1,4 @@ -export const AWS_ENDPOINT = "https://0pw5u65m3a.execute-api.eu-north-1.amazonaws.com/api-stage/mint" - -export const ZKEY_NAME = "proof_of_passport_final_v01" -export const ARKZKEY_URL = `https://d8o9bercqupgk.cloudfront.net/${ZKEY_NAME}.arkzkey.zip` -export const ZKEY_URL = `https://d8o9bercqupgk.cloudfront.net/${ZKEY_NAME}.zkey.zip` +export const RELAYER_URL = "https://0pw5u65m3a.execute-api.eu-north-1.amazonaws.com/api-stage/mint" export const PUBKEY_TREE_DEPTH = 16 export const COMMITMENT_TREE_DEPTH = 16 @@ -20,7 +16,7 @@ export enum SignatureAlgorithm { sha512WithRSAEncryption_65537 = 10 } -export const attributeToPosition: { [key: string]: number[] } = { +export const attributeToPosition = { issuing_state: [2, 4], name: [5, 43], passport_number: [44, 52], diff --git a/common/src/utils/types.ts b/common/src/utils/types.ts index 42179b52f..f7a5be687 100644 --- a/common/src/utils/types.ts +++ b/common/src/utils/types.ts @@ -7,3 +7,12 @@ export type PassportData = { encryptedDigest: number[]; photoBase64: string; }; + +export type Proof = { + proof: { + a: [string, string], + b: [[string, string], [string, string]], + c: [string, string] + }; + pub_signals: string[]; +} \ No newline at end of file diff --git a/common/src/utils/utils.ts b/common/src/utils/utils.ts index f7393951e..2d6a68237 100644 --- a/common/src/utils/utils.ts +++ b/common/src/utils/utils.ts @@ -6,15 +6,11 @@ import { sha256 } from 'js-sha256'; export function formatMrz(mrz: string) { const mrzCharcodes = [...mrz].map(char => char.charCodeAt(0)); - // console.log('mrzCharcodes:', mrzCharcodes); - mrzCharcodes.unshift(88); // the length of the mrz data mrzCharcodes.unshift(95, 31); // the MRZ_INFO_TAG mrzCharcodes.unshift(91); // the new length of the whole array mrzCharcodes.unshift(97); // the tag for DG1 - // console.log('mrzCharcodes with tags:', mrzCharcodes); - return mrzCharcodes; } @@ -25,9 +21,6 @@ export function parsePubKeyString(pubKeyString: string) { const modulus = modulusMatch ? modulusMatch[1] : null; const exponent = publicExponentMatch ? publicExponentMatch[1] : null; - // console.log('Modulus:', modulus); - // console.log('Public Exponent:', exponent); - if (!modulus || !exponent) { throw new Error('Could not parse public key string'); } @@ -196,7 +189,6 @@ export function bigIntToChunkedBytes(num: BigInt | bigint, bytesPerChunk: number return res; } - export function hexStringToSignedIntArray(hexString: string) { let result = []; for (let i = 0; i < hexString.length; i += 2) { @@ -206,53 +198,11 @@ export function hexStringToSignedIntArray(hexString: string) { return result; }; -function bytesToBigInt(bytes: number[]) { - let hex = bytes.reverse().map(byte => byte.toString(16).padStart(2, '0')).join(''); - // console.log('hex', hex) - return BigInt(`0x${hex}`).toString(); -} - -function splitInto(arr: number[], size: number) { - const res = []; - for (let i = 0; i < arr.length; i += size) { - res.push(arr.slice(i, i + size)); - } - return res; -} - export function formatRoot(root: string): string { let rootHex = BigInt(root).toString(16); return rootHex.length % 2 === 0 ? "0x" + rootHex : "0x0" + rootHex; } -function setFirstBitOfLastByteToZero(bytes: number[]) { - bytes[bytes.length - 1] &= 0x7F; // AND with 01111111 to set the first bit of the last byte to 0 - return bytes; -} - -// from reverse engineering ark-serialize. -export function formatProof(proof: number[]) { - const splittedProof = splitInto(proof, 32); - splittedProof[1] = setFirstBitOfLastByteToZero(splittedProof[1]); - splittedProof[5] = setFirstBitOfLastByteToZero(splittedProof[5]); // We might need to do the same for input 3 - splittedProof[7] = setFirstBitOfLastByteToZero(splittedProof[7]); - const proooof = splittedProof.map(bytesToBigInt); - - return { - "a": [proooof[0], proooof[1]], - "b": [ - [proooof[2], proooof[3]], - [proooof[4], proooof[5]] - ], - "c": [proooof[6], proooof[7]] - } -} - -export function formatInputs(inputs: number[]) { - const splitted = splitInto(inputs.slice(8), 32); - return splitted.map(bytesToBigInt); -} - export function getCurrentDateYYMMDD(dayDiff: number = 0): number[] { const date = new Date(); date.setDate(date.getDate() + dayDiff); // Adjust the date by the dayDiff