Merge branch 'dev' into split_circuits

This commit is contained in:
turboblitz
2024-05-14 17:35:35 +09:00
committed by GitHub
88 changed files with 2190 additions and 5855 deletions

View File

@@ -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<string>(ethers.ZeroAddress);
const [passportData, setPassportData] = useState<PassportData>(mockPassportData_sha256WithRSAEncryption_65537 as PassportData);
const [step, setStep] = useState<number>(Steps.MRZ_SCAN);
const [generatingProof, setGeneratingProof] = useState<boolean>(false);
const [proofTime, setProofTime] = useState<number>(0);
const [proof, setProof] = useState<{ proof: string, inputs: string } | null>(null);
const [mintText, setMintText] = useState<string>("");
const [majority, setMajority] = useState<number>(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 (
<YStack f={1} bc="#161616" h="100%" w="100%">
<YStack h="100%" w="100%">
<MainScreen
onStartCameraScan={handleStartCameraScan}
nfcScan={handleNFCScan}
passportData={passportData}
disclosure={disclosure}
handleDisclosureChange={handleDisclosureChange}
address={address}
setAddress={setAddress}
generatingProof={generatingProof}
handleProve={handleProve}
step={step}
mintText={mintText}
proof={proof}
proofTime={proofTime}
handleMint={handleMint}
setStep={setStep}
passportNumber={passportNumber}
setPassportNumber={setPassportNumber}
dateOfBirth={dateOfBirth}
setDateOfBirth={setDateOfBirth}
dateOfExpiry={dateOfExpiry}
setDateOfExpiry={setDateOfExpiry}
majority={majority}
setMajority={setMajority}
zkeydownloadStatus={zkeydownloadStatus}
showWarning={showWarning}
setShowWarning={setShowWarning}
setDownloadStatus={setDownloadStatus}
/>
<MainScreen />
</YStack>
</YStack>
);

View File

@@ -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 `<circuit_name>.zkey` before you zip it, and the zip is then named `<circuit_name>.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/<your-user-name>/Library/Android/sdk/ndk/23.1.7779620`
Build the android native module:
```
export ANDROID_NDK="<your-android-ndk-path>"
./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/<user>/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:

View File

@@ -12,6 +12,7 @@
<application
android:allowBackup="true"
android:largeHeap="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"

View File

@@ -82,6 +82,15 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2
versionName "1.0"
externalNativeBuild {
cmake {
cppFlags += "-fexceptions -frtti -std=c++11"
arguments += "-DANDROID_STL=c++_shared"
}
ndk {
abiFilters += "arm64-v8a"
}
}
}
signingConfigs {
debug {
@@ -109,6 +118,12 @@ android {
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
packagingOptions {
exclude 'META-INF/LICENSE'
@@ -140,6 +155,8 @@ dependencies {
implementation("net.java.dev.jna:jna:5.13.0@aar")
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android'
implementation project(':react-native-fs')
implementation 'com.google.code.gson:gson:2.8.9'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

View File

@@ -1,5 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.proofofpassport"
>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
@@ -9,6 +13,7 @@
<application
android:name=".MainApplication"
android:largeHeap="true"
android:label="@string/app_name"
android:icon="@mipmap/passport_logo"
android:allowBackup="false"

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.22.1)
project("proofofpassport")
include_directories(include)
link_directories(lib)
add_library(rapidsnark SHARED IMPORTED)
set_target_properties(rapidsnark PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/lib/librapidsnark.so)
add_library(proof_of_passport SHARED IMPORTED)
set_target_properties(proof_of_passport PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/lib/libwitnesscalc_proof_of_passport.so)
add_library(${CMAKE_PROJECT_NAME} SHARED
proofofpassport.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME}
rapidsnark proof_of_passport)

View File

@@ -0,0 +1,47 @@
#ifndef PROVER_HPP
#define PROVER_HPP
#ifdef __cplusplus
extern "C" {
#endif
//Error codes returned by the functions.
#define PROVER_OK 0x0
#define PROVER_ERROR 0x1
#define PROVER_ERROR_SHORT_BUFFER 0x2
#define PROVER_INVALID_WITNESS_LENGTH 0x3
/**
* groth16_prover
* @return error code:
* PROVER_OK - in case of success
* PPOVER_ERROR - in case of an error
* PROVER_ERROR_SHORT_BUFFER - in case of a short buffer error, also updates proof_size and public_size with actual proof and public sizess
*/
int
groth16_prover(const void *zkey_buffer, unsigned long zkey_size,
const void *wtns_buffer, unsigned long wtns_size,
char *proof_buffer, unsigned long *proof_size,
char *public_buffer, unsigned long *public_size,
char *error_msg, unsigned long error_msg_maxsize);
/**
* groth16_prover_zkey_file
* @return error code:
* PROVER_OK - in case of success
* PPOVER_ERROR - in case of an error
* PROVER_ERROR_SHORT_BUFFER - in case of a short buffer error, also updates proof_size and public_size with actual proof and public sizess
*/
int
groth16_prover_zkey_file(const char *zkeyPath,
const void *wtns_buffer, unsigned long wtns_size,
char *proof_buffer, unsigned long *proof_size,
char *public_buffer, unsigned long *public_size,
char *error_msg, unsigned long error_msg_maxsize);
#ifdef __cplusplus
}
#endif
#endif // PROVER_HPP

View File

@@ -0,0 +1,39 @@
#ifndef WITNESSCALC_PROOFOFPASSPORT_H
#define WITNESSCALC_PROOFOFPASSPORT_H
#ifdef __cplusplus
extern "C" {
#endif
#define WITNESSCALC_OK 0x0
#define WITNESSCALC_ERROR 0x1
#define WITNESSCALC_ERROR_SHORT_BUFFER 0x2
/**
*
* @return error code:
* WITNESSCALC_OK - in case of success.
* WITNESSCALC_ERROR - in case of an error.
*
* On success wtns_buffer is filled with witness data and
* wtns_size contains the number bytes copied to wtns_buffer.
*
* If wtns_buffer is too small then the function returns WITNESSCALC_ERROR_SHORT_BUFFER
* and the minimum size for wtns_buffer in wtns_size.
*
*/
int
witnesscalc_proof_of_passport(
const char *circuit_buffer, unsigned long circuit_size,
const char *json_buffer, unsigned long json_size,
char *wtns_buffer, unsigned long *wtns_size,
char *error_msg, unsigned long error_msg_maxsize);
#ifdef __cplusplus
}
#endif
#endif // WITNESSCALC_PROOFOFPASSPORT_H

Binary file not shown.

View File

@@ -0,0 +1,136 @@
#include "include/prover.h"
#include "include/witnesscalc_proof_of_passport.h"
#include <jni.h>
#include <iostream>
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<char *>(env->GetByteArrayElements(proof_buffer,
nullptr));
char *publicBuffer = reinterpret_cast<char *>(env->GetByteArrayElements(public_buffer,
nullptr));
char *errorMsg = reinterpret_cast<char *>(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<unsigned long>(zkey_size),
wtnsBuffer, static_cast<unsigned long>(wtns_size),
proofBuffer, &proofSize,
publicBuffer, &publicSize,
errorMsg, static_cast<unsigned long>(error_msg_max_size));
env->SetLongArrayRegion(proof_size, 0, 1, reinterpret_cast<const jlong *>(&proofSize));
env->SetLongArrayRegion(public_size, 0, 1, reinterpret_cast<const jlong *>(&publicSize));
env->ReleaseByteArrayElements(zkey_buffer,
reinterpret_cast<jbyte *>(const_cast<void *>(zkeyBuffer)), 0);
env->ReleaseByteArrayElements(wtns_buffer,
reinterpret_cast<jbyte *>(const_cast<void *>(wtnsBuffer)), 0);
env->ReleaseByteArrayElements(proof_buffer, reinterpret_cast<jbyte *>(proofBuffer), 0);
env->ReleaseByteArrayElements(public_buffer, reinterpret_cast<jbyte *>(publicBuffer), 0);
env->ReleaseByteArrayElements(error_msg, reinterpret_cast<jbyte *>(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<const char *>(env->GetByteArrayElements(
circuit_buffer, nullptr));
const char *jsonBuffer = reinterpret_cast<const char *>(env->GetByteArrayElements(json_buffer,
nullptr));
char *wtnsBuffer = reinterpret_cast<char *>(env->GetByteArrayElements(wtns_buffer, nullptr));
char *errorMsg = reinterpret_cast<char *>(env->GetByteArrayElements(error_msg, nullptr));
unsigned long wtnsSize = env->GetLongArrayElements(wtns_size, nullptr)[0];
int result = witnesscalc_proof_of_passport(
circuitBuffer, static_cast<unsigned long>(circuit_size),
jsonBuffer, static_cast<unsigned long>(json_size),
wtnsBuffer, &wtnsSize,
errorMsg, static_cast<unsigned long>(error_msg_max_size));
// Set the result and release the resources
env->SetLongArrayRegion(wtns_size, 0, 1, reinterpret_cast<jlong *>(&wtnsSize));
env->ReleaseByteArrayElements(circuit_buffer,
reinterpret_cast<jbyte *>(const_cast<char *>(circuitBuffer)), 0);
env->ReleaseByteArrayElements(json_buffer,
reinterpret_cast<jbyte *>(const_cast<char *>(jsonBuffer)), 0);
env->ReleaseByteArrayElements(wtns_buffer, reinterpret_cast<jbyte *>(wtnsBuffer), 0);
env->ReleaseByteArrayElements(error_msg, reinterpret_cast<jbyte *>(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;
}

View File

@@ -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<String, Any?>(
"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<String, List<String>>(
// "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<String, List<String>>()
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<String>,
val pi_b: List<List<String>>,
val pi_c: List<String>,
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<String>
)
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<String> {
val gson = Gson()
val stringArray = gson.fromJson(jsonString, Array<String>::class.java)
return stringArray.toList()
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -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'

View File

@@ -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

View File

@@ -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" }

View File

@@ -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**

View File

@@ -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<String> = env::args().collect();
if args.len() != 2 {
eprintln!("Usage: zkey_to_arkzkey <path_to_zkey_file>");
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
}

View File

@@ -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<Bn254>);
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)]
pub struct SerializableMatrix<F: Field> {
pub data: Vec<Vec<(F, usize)>>,
}
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)]
pub struct SerializableConstraintMatrices<F: Field> {
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<F>,
pub b: SerializableMatrix<F>,
pub c: SerializableMatrix<F>,
}
impl<F: Field> From<Vec<Vec<(F, usize)>>> for SerializableMatrix<F> {
fn from(matrix: Vec<Vec<(F, usize)>>) -> Self {
SerializableMatrix { data: matrix }
}
}
impl<F: Field> From<SerializableMatrix<F>> for Vec<Vec<(F, usize)>> {
fn from(serializable_matrix: SerializableMatrix<F>) -> Self {
serializable_matrix.data
}
}
pub fn serialize_proving_key(pk: &SerializableProvingKey) -> Vec<u8> {
let mut serialized_data = Vec::new();
pk.serialize_compressed(&mut serialized_data)
.expect("Serialization failed");
serialized_data
}
pub fn deserialize_proving_key(data: Vec<u8>) -> SerializableProvingKey {
SerializableProvingKey::deserialize_compressed_unchecked(&mut &data[..])
.expect("Deserialization failed")
}
pub fn read_arkzkey(
arkzkey_path: &str,
) -> Result<(SerializableProvingKey, SerializableConstraintMatrices<Fr>)> {
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<Bn254>, ConstraintMatrices<Fr>?
pub fn read_arkzkey_from_bytes(
arkzkey_bytes: &[u8],
) -> Result<(ProvingKey<Bn254>, ConstraintMatrices<Fr>)> {
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<Bn254> = serialized_proving_key.0;
let constraint_matrices: ConstraintMatrices<Fr> = 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<Fr>)> {
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<Fr>,
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",
)
}
}

View File

@@ -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

View File

@@ -33,9 +33,11 @@
<key>NFCReaderUsageDescription</key>
<string>Need NFC to read Passport</string>
<key>NSAppTransportSecurity</key>
<dict/>
<string></string>
<key>NSFaceIDUsageDescription</key>
<string>Needed to secure the secret</string>
<key>NSCameraUsageDescription</key>
<string>Needed to scan your passport MRZ, you can however enter it manually.</string>
<string>Needed to scan the passport MRZ.</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>NSLocationWhenInUseUsageDescription</key>

View File

@@ -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;
}

View File

@@ -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<UInt8>.allocate(capacity: (100 * 1024 * 1024))
let errorBuffer = UnsafeMutablePointer<UInt8>.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..<publicInputsNullIndex]
return (proof: proof, publicInputs: publicInputs)
}
}

View File

@@ -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

View File

@@ -1,51 +0,0 @@
[package]
name = "mopro-core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[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" }
[features]
default = []
dylib = ["wasmer/dylib"]
[dependencies]
ark-circom = { git = "https://github.com/arkworks-rs/circom-compat.git" }
serde = { version = "1.0", features = ["derive"] }
ark-serialize = { version = "=0.4.1", features = ["derive"] }
num-bigint = { version = "=0.4.3", default-features = false, features = [
"rand",
] }
instant = "0.1"
wasmer = { git = "https://github.com/oskarth/wasmer.git", rev = "09c7070" }
once_cell = "1.8"
# ZKP generation
ark-ec = { version = "=0.4.1", default-features = false, features = [
"parallel",
] }
ark-crypto-primitives = { version = "=0.4.0" }
ark-std = { version = "=0.4.0", default-features = false, features = [
"parallel",
] }
ark-bn254 = { version = "=0.4.0" }
ark-groth16 = { version = "=0.4.0", default-features = false, features = [
"parallel",
] }
ark-relations = { version = "0.4", default-features = false }
ark-zkey = { path = "../ark-zkey" }
# Error handling
thiserror = "=1.0.39"
color-eyre = "=0.6.2"
criterion = "=0.3.6"
[build-dependencies]
color-eyre = "0.6"
enumset = "1.0.8"
wasmer = { git = "https://github.com/oskarth/wasmer.git", rev = "09c7070" }

View File

@@ -1,47 +0,0 @@
# mopro-core
Core mobile Rust library. For FFI, see `mopro-ffi` which is a thin wrapper for exposing UniFFI bindings around this library.
## Overview
TBD.
## Examples
Run `cargo run --example circom`. Also see `examples/circom/README.md` for more information.
## Build dylib
Experimental support.
Turns `.wasm` file into a dynamic library (`.dylib`).
Run:
`cargo build --features dylib`
After that you'll see location of the dylib file:
```
warning: Building dylib for aarch64-apple-darwin
warning: Dylib location: /Users/user/repos/github.com/oskarth/mopro/mopro-core/target/debug/aarch64-apple-darwin/keccak256.dylib
```
Right now this is hardcoded for `rsa`.
Note that:
- It has to be built for the right architecture
- Have to run `install_name_tool` to adjust install name
- Run `codesign` to sign dylib for use on iOS
### Script
Add third argument: `dylib`:
`./scripts/update_bindings.sh device release dylib`
Note that `APPLE_SIGNING_IDENTITY` must be set.
## To use ark-zkey
Experimental support for significantly faster zkey loading. See `../ark-zkey` README for how to build arkzkey.

View File

@@ -1,95 +0,0 @@
use color_eyre::eyre::Result;
use std::env;
use std::path::PathBuf;
fn prepare_env(zkey_path: String, wasm_path: String, arkzkey_path: String) -> 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(())
}

View File

@@ -1,8 +0,0 @@
pub mod middleware;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MoproError {
#[error("CircomError: {0}")]
CircomError(String),
}

View File

@@ -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<Bn254>;
type CircuitInputs = HashMap<String, Vec<BigInt>>;
// TODO: Split up this namespace a bit, right now quite a lot of things going on
pub struct CircomState {
builder: Option<CircomBuilder<Bn254>>,
circuit: Option<CircomCircuit<Bn254>>,
params: Option<ProvingKey<Bn254>>,
}
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<Mutex<WitnessCalculator>> = OnceCell::new();
static ARKZKEY: Lazy<(ProvingKey<Bn254>, ConstraintMatrices<Fr>)> = 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<Bn254>, ConstraintMatrices<Fr>), 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<Bn254>, ConstraintMatrices<Fr>) {
// load_arkzkey_from_file(zkey_path).unwrap()
ARKZKEY.clone()
}
pub fn witness_calculator() -> &'static Mutex<WitnessCalculator> {
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::<Bn254, _>(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<bool, MoproError> {
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<SerializableProvingKey, MoproError> {
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<T: Into<BigInt>> 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<bool, MoproError> {
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(&params.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<CircomConfig<Bn254>, MoproError> {
CircomConfig::<Bn254>::new(wasm_path, r1cs_path)
.map_err(|e| MoproError::CircomError(e.to_string()))
}
fn run_trusted_setup(&mut self) -> Result<ProvingKey<Bn254>, 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<BigInt> {
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::<Bn254, _>(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<String, Vec<BigInt>> = 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<String, Vec<BigInt>> = 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
}
}

View File

@@ -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<Bn254>);
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug)]
pub struct SerializableProof(pub Proof<Bn254>);
#[derive(CanonicalSerialize, CanonicalDeserialize, Clone, Debug, PartialEq)]
pub struct SerializableInputs(pub Vec<<Bn254 as Pairing>::ScalarField>);
pub fn serialize_proof(proof: &SerializableProof) -> Vec<u8> {
let mut serialized_data = Vec::new();
proof
.serialize_uncompressed(&mut serialized_data)
.expect("Serialization failed");
serialized_data
}
pub fn deserialize_proof(data: Vec<u8>) -> SerializableProof {
SerializableProof::deserialize_uncompressed(&mut &data[..]).expect("Deserialization failed")
}
pub fn serialize_proving_key(pk: &SerializableProvingKey) -> Vec<u8> {
let mut serialized_data = Vec::new();
pk.serialize_uncompressed(&mut serialized_data)
.expect("Serialization failed");
serialized_data
}
pub fn deserialize_proving_key(data: Vec<u8>) -> SerializableProvingKey {
SerializableProvingKey::deserialize_uncompressed(&mut &data[..])
.expect("Deserialization failed")
}
pub fn serialize_inputs(inputs: &SerializableInputs) -> Vec<u8> {
let mut serialized_data = Vec::new();
inputs
.serialize_uncompressed(&mut serialized_data)
.expect("Serialization failed");
serialized_data
}
pub fn deserialize_inputs(data: Vec<u8>) -> 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<Bn254>;
fn generate_serializable_proving_key(
wasm_path: &str,
r1cs_path: &str,
) -> Result<SerializableProvingKey, MoproError> {
assert_paths_exists(wasm_path, r1cs_path)?;
let cfg = CircomConfig::<Bn254>::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"
);
}
}

View File

@@ -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<bool> {
let mut bits = Vec::new();
for &byte in bytes {
for j in 0..8 {
let bit = (byte >> j) & 1;
bits.push(bit == 1);
}
}
bits
}

View File

@@ -1 +0,0 @@
pub mod circom;

View File

@@ -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/

View File

@@ -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" }

View File

@@ -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)

View File

@@ -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`

View File

@@ -1,3 +0,0 @@
fn main() {
uniffi::generate_scaffolding("src/mopro.udl").expect("Building the UDL file failed");
}

View File

@@ -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<u8>,
pub inputs: Vec<u8>,
}
// 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<u8>,
}
// pub inputs: Vec<u8>,
impl From<mopro_core::MoproError> for FFIError {
fn from(error: mopro_core::MoproError) -> Self {
FFIError::MoproError(error)
}
}
pub struct MoproCircom {
state: RwLock<circom::CircomState>,
}
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<String, Vec<String>>,
) -> Result<GenerateProofResult, MoproError> {
// 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<u8>, public_input: Vec<u8>) -> Result<bool, MoproError> {
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<SetupResult, MoproError> {
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<String, Vec<String>>,
) -> Result<GenerateProofResult, MoproError> {
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<u8>, public_input: Vec<u8>) -> Result<bool, MoproError> {
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<u8>) -> HashMap<String, Vec<String>> {
let bits = circom::utils::bytes_to_bits(&input_vec);
let converted_vec: Vec<String> = 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<u8> {
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(())
}
}

View File

@@ -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<string, sequence<string>> 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<string, sequence<string>> circuit_inputs);
[Throws=MoproError]
boolean verify_proof(bytes proof, bytes public_input);
};

View File

@@ -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<String, List<String>>()
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)
}

View File

@@ -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)")
}

View File

@@ -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<String, List<String>>()
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)
}

View File

@@ -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)")
}

View File

@@ -1,273 +0,0 @@
import uniffi.mopro.*
try {
initializeMopro()
val inputs = mutableMapOf<String, List<String>>()
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)
}

View File

@@ -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)")
}

View File

@@ -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<String, List<String>>()
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);
}

View File

@@ -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)")
}

View File

@@ -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)")
}

View File

@@ -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",
);

View File

@@ -1,3 +0,0 @@
fn main() {
uniffi::uniffi_bindgen_main()
}

View File

@@ -1,2 +0,0 @@
[bindings.swift]
module_name = "mopro"

View File

@@ -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",

View File

@@ -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/
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/

View File

@@ -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

22
app/scripts/common.sh Normal file
View File

@@ -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

30
app/src/apps/gitcoin.tsx Normal file
View File

@@ -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 = () => (
<YStack ml="$2" p="$2" px="$3" bc="#282828" borderRadius="$10">
<Text color="#a0a0a0" fontWeight="bold">coming soon</Text>
</YStack>
);
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;

262
app/src/apps/sbt.tsx Normal file
View File

@@ -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 = () => (
<YStack ml="$2" p="$2" px="$3" bc="#0d1e18" borderRadius="$10">
<Text color="#3bb178" fow="bold">Sepolia</Text>
</YStack>
);
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 (
<Pressable onPress={() => {
Clipboard.setString(txHash);
toast?.show('🖨️', {
message: "Tx copied to clipboard",
customData: {
type: "success",
},
})
}}
>
<XStack jc='space-between' h="$2" ai="center">
<Text color={textColor1} fontWeight="bold" fontSize="$5">
Tx: {shortenTxHash(txHash)}
</Text>
</XStack>
</Pressable>
)
},
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;

30
app/src/apps/zupass.tsx Normal file
View File

@@ -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 = () => (
<YStack ml="$2" p="$2" px="$3" bc="#282828" borderRadius="$10">
<Text color="#a0a0a0" fontWeight="bold">coming soon</Text>
</YStack>
);
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;

View File

@@ -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 (
<YStack bc={componentBgColor} borderRadius="$6" borderWidth={1.5} borderColor={borderColor}>
<YStack p="$3">
<XStack gap="$4" ai="center">
<XStack p="$2" bc="#232323" borderWidth={1.2} borderColor="#343434" borderRadius="$3">
<Image
source={{ uri: ENS }}
w="$1"
h="$1" />
</XStack>
<YStack gap="$1">
<Text fontSize={16} fow="bold" color="#ededed">Address or ENS</Text>
</YStack>
</XStack>
</YStack>
<YStack bc={componentBgColor2} borderTopWidth={1.5} borderColor={borderColor} borderBottomLeftRadius="$6" borderBottomRightRadius="$6">
<Input
bg="transparent"
color={textColor1}
fontSize={13}
placeholder="anon.eth or 0x023…"
value={inputValue}
onChangeText={setInputValue}
autoCorrect={false}
autoCapitalize='none'
borderColor="transparent"
borderWidth={0}
/>
</YStack>
</YStack>
);
};
export default EnterAddress;

View File

@@ -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<ProofGridProps> = ({ proof }) => {
@@ -22,7 +23,7 @@ const ProofGrid: React.FC<ProofGridProps> = ({ 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()),

View File

@@ -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<AppScreenProps> = ({ 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 = () => (
<YStack ml="$2" p="$2" px="$3" bc="#2b1400" borderRadius="$10">
<Text color="#f7670a" fontWeight="bold">age</Text>
</YStack>
);
const comingSoon = () => (
<YStack ml="$2" p="$2" px="$3" bc="#282828" borderRadius="$10">
<Text color="#a0a0a0" fontWeight="bold">coming soon</Text>
</YStack>
);
const nationality = () => (
<YStack ml="$2" p="$2" px="$3" bc="#0d1e18" borderRadius="$10">
<Text color="#3bb178" fow="bold">nationality</Text>
</YStack>
);
const sepolia = () => (
<YStack ml="$2" p="$2" px="$3" bc="#0d1e18" borderRadius="$10">
<Text color="#3bb178" fow="bold">Sepolia</Text>
</YStack>
);
// 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 (
<ScrollView f={1} >
< YStack my="$8" gap="$5" px="$5" jc="center" alignItems='center' >
<ScrollView f={1}>
<YStack my="$8" gap="$5" px="$5" jc="center" alignItems='center'>
{
cardsData.map(card => (
cardsData.map(app => (
<AppCard
key={card.app.id}
title={card.title}
description={card.description}
id={card.app.id}
onTouchStart={() => 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}
/>
))
}
</YStack >
</ScrollView >
</YStack>
</ScrollView>
);
}

View File

@@ -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<MainScreenProps> = ({
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<string>('');
const [selectedTab, setSelectedTab] = useState("scan");
const [selectedApp, setSelectedApp] = useState<App | null>(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<MainScreenProps> = ({
}, 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<MainScreenProps> = ({
};
}, [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 (
<>
<YStack f={1} bc="#161616" mt={Platform.OS === 'ios' ? "$8" : "$0"} >
<YStack >
<XStack jc="space-between" ai="center" px="$3">
<Button p="$2" py="$3" pr="$7" unstyled onPress={() => setSettingsIsOpen(true)}><Cog color="#a0a0a0" /></Button>
<Text fontSize="$6" color="#a0a0a0">
{selectedTab === "scan" ? "Scan" : (selectedTab === "app" ? "Apps" : "Prove")}
</Text>
<Button p="$2" py="$3" pl="$7" unstyled onPress={() => setHelpIsOpen(true)}><HelpCircle color="#a0a0a0" /></Button>
</XStack>
<Sheet open={NFCScanIsOpen} onOpenChange={setNFCScanIsOpen} modal dismissOnOverlayPress={false} disableDrag animation="medium" snapPoints={[35]}>
<Sheet.Overlay />
@@ -238,19 +175,55 @@ const MainScreen: React.FC<MainScreenProps> = ({
<Label color={textColor1} width={160} justifyContent="flex-end" fontSize={13}>
Passport Number
</Label>
<Input bg={componentBgColor} color={textColor1} h="$3.5" borderColor={passportNumber?.length === 9 ? "green" : "unset"} flex={1} id="passport_number" onChangeText={(text) => setPassportNumber(text.toUpperCase())} value={passportNumber} keyboardType="default" />
<Input
bg={componentBgColor}
color={textColor1}
h="$3.5"
borderColor={passportNumber?.length === 9 ? "green" : "unset"}
flex={1}
id="passportnumber"
onChangeText={(text) => {
update({passportNumber: text.toUpperCase()})
}}
value={passportNumber}
keyboardType="default"
/>
</Fieldset>
<Fieldset gap="$4" horizontal>
<Label color={textColor1} width={160} justifyContent="flex-end" fontSize={13}>
Date of birth (yymmdd)
</Label>
<Input bg={componentBgColor} color={textColor1} h="$3.5" borderColor={dateOfBirth?.length === 6 ? "green" : "unset"} flex={1} id="date_of_birth" onChangeText={setDateOfBirth} value={dateOfBirth} keyboardType={Platform.OS == "ios" ? "default" : "number-pad"} />
<Input
bg={componentBgColor}
color={textColor1}
h="$3.5"
borderColor={dateOfBirth?.length === 6 ? "green" : "unset"}
flex={1}
id="dateofbirth"
onChangeText={(text) => {
update({dateOfBirth: text})
}}
value={dateOfBirth}
keyboardType={Platform.OS === "ios" ? "default" : "number-pad"}
/>
</Fieldset>
<Fieldset gap="$4" horizontal>
<Label color={textColor1} width={160} justifyContent="flex-end" fontSize={13}>
Date of expiry (yymmdd)
</Label>
<Input bg={componentBgColor} color={textColor1} h="$3.5" borderColor={dateOfExpiry?.length === 6 ? "green" : "unset"} flex={1} id="date_of_expiry" onChangeText={setDateOfExpiry} value={dateOfExpiry} keyboardType={Platform.OS == "ios" ? "default" : "number-pad"} />
<Input
bg={componentBgColor}
color={textColor1}
h="$3.5"
borderColor={dateOfExpiry?.length === 6 ? "green" : "unset"}
flex={1}
id="dateofexpiry"
onChangeText={(text) => {
update({dateOfExpiry: text})
}}
value={dateOfExpiry}
keyboardType={Platform.OS === "ios" ? "default" : "number-pad"}
/>
</Fieldset>
</YStack>
}
@@ -264,8 +237,6 @@ const MainScreen: React.FC<MainScreenProps> = ({
</Switch>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="restart">
Restart to step 1
@@ -283,6 +254,25 @@ const MainScreen: React.FC<MainScreenProps> = ({
<VenetianMask color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="skip" >
Delete passport data
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={clearPassportDataFromStorage}>
<VenetianMask color={textColor1} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textColor1} width={200} justifyContent="flex-end" htmlFor="skip" >
Delete secret (caution)
</Label>
<Button bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={clearSecretFromStorage}>
<VenetianMask color={textColor1} />
</Button>
</Fieldset>
<YStack flex={1} />
<YStack mb="$0">
@@ -351,53 +341,6 @@ const MainScreen: React.FC<MainScreenProps> = ({
</YStack>
</YStack>
{/* <YStack flex={1} jc="space-evenly">
<YStack >
<H4 color={textColor1}>How do I scan my passport ?</H4>
<Text color={textColor1}>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.
<Button pl="$1" unstyled h="$1" w="$3" jc="flex-end" onPress={() => Linking.openURL('https://zk-passport.github.io/posts/where-is-my-chip/')}>
<ExternalLink color="#3185FC" size={12} />
</Button>
</Text>
<Text color={textColor1} mt="$2">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.
<Button pl="$1" unstyled h="$1" w="$3" jc="flex-end" onPress={() => Linking.openURL('https://zk-passport.github.io/posts/locate-NFC-reader/')}>
<ExternalLink color="#3185FC" size={12} />
</Button>
</Text>
<Text color={textColor1} mt="$2">3. Keep your passport pressed against your phone when the NFC popup shows up and hold still.</Text>
</YStack>
<YStack gap="$1">
<H4 color={textColor1} mt="$2">Security and Privacy</H4>
<Text color={textColor1}>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.</Text>
</YStack>
<YStack gap="$2">
<H4 color={textColor1} mt="$1">What are zero-knowledge proofs ?</H4>
<Text color={textColor1}>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.</Text>
</YStack>
<YStack gap="$2">
<H4 >Contacts</H4>
<XStack mt="$2" ml="$3" gap="$5">
<Pressable onPress={() => Linking.openURL('https://t.me/proofofpassport')}>
<Image
source={{ uri: Telegram, width: 24, height: 24 }}
/>
</Pressable>
<Pressable onPress={() => Linking.openURL('https://x.com/proofofpassport')}>
<Image
source={{ uri: X, width: 24, height: 24 }}
/>
</Pressable>
<Pressable onPress={() => Linking.openURL('https://github.com/zk-passport/proof-of-passport')}>
<Image
source={{ uri: Github, width: 24, height: 24 }}
/>
</Pressable>
</XStack>
</YStack>
</YStack> */}
<Button mt="$3" bg={componentBgColor} jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" alignSelf='center' w="80%" onPress={() => setHelpIsOpen(false)}>
<Text color={textColor1} w="80%" textAlign='center' fow="bold">Close</Text>
</Button>
@@ -407,71 +350,52 @@ const MainScreen: React.FC<MainScreenProps> = ({
</Sheet>
<XStack bc="#343434" h={1.2} />
</YStack>
<Tabs f={1} orientation="horizontal" flexDirection="column" defaultValue="scan" value={selectedTab} onValueChange={setSelectedTab}>
<Tabs f={1} orientation="horizontal" flexDirection="column" defaultValue="scan"
value={selectedTab}
onValueChange={(value) => updateNavigationStore({ selectedTab: value })}
>
<ToastViewport flexDirection="column-reverse" top={15} right={0} left={0} />
<ToastMessage />
<Tabs.Content value="scan" f={1}>
<ScanScreen
onStartCameraScan={onStartCameraScan}
handleNFCScan={handleNFCScan}
step={step} />
step={step}
/>
</Tabs.Content>
<Tabs.Content value="app" f={1}>
<AppScreen
selectedApp={selectedApp}
setSelectedApp={setSelectedApp}
step={step}
setStep={setStep}
setSelectedTab={setSelectedTab}
/>
<AppScreen />
</Tabs.Content>
<Tabs.Content value="prove" f={1}>
<ProveScreen
passportData={passportData}
disclosure={disclosure}
selectedApp={selectedApp}
handleDisclosureChange={handleDisclosureChange}
address={address}
setAddress={setAddress}
generatingProof={generatingProof}
handleProve={handleProve}
hideData={hideData}
ens={ens}
setEns={setEns}
majority={majority}
setMajority={setMajority}
zkeydownloadStatus={zkeydownloadStatus}
/>
<ProveScreen />
</Tabs.Content>
<Tabs.Content value="mint" f={1}>
<MintScreen
selectedApp={selectedApp}
step={step}
mintText={mintText}
proof={proof}
proofTime={proofTime}
handleMint={handleMint}
/>
<SendProofScreen />
</Tabs.Content>
</Tabs>
</YStack>
<Modal visible={showWarning} animationType="slide" transparent={true}>
<Modal visible={showWarningModal.show} animationType="slide" transparent={true}>
<YStack bc="#161616" p={20} ai="center" jc="center" position="absolute" top={0} bottom={0} left={0} right={0}>
<YStack bc="#343434" p={20} borderRadius={10} ai="center" jc="center">
<Text fontWeight="bold" fontSize={18} color="#a0a0a0">👋 Hi</Text>
<Text mt={10} textAlign="center" color="#a0a0a0">
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.
</Text>
<XStack mt={20}>
<Button onPress={() => {
downloadZkey(
setDownloadStatus,
toast
);
setShowWarning(false)
}} bc="#4caf50" borderRadius={5} padding={10}>
<Button
onPress={() => {
fetchZkey(showWarningModal.circuit as CircuitName);
updateNavigationStore({
showWarningModal: {
show: false,
circuit: '',
size: 0,
}
});
}}
bc="#4caf50" borderRadius={5} padding={10}
>
<Text color="#ffffff">Continue</Text>
</Button>
</XStack>

View File

@@ -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<MintScreenProps> = ({
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 (
<YStack px="$4" f={1} mb={Platform.OS === 'ios' ? "$5" : "$0"}>
{step === Steps.TX_MINTED ? (
<YStack flex={1} justifyContent='center' alignItems='center' gap="$5">
<XStack flex={1} />
<ProofGrid proof={proof} />
<YStack gap="$1">
<Text color={textColor1} fontWeight="bold" fontSize="$5" >You just have minted a Soulbound token 🎉</Text>
<Text color={textColor1} fontSize="$4" fow="bold" textAlign='left'>You can now share this proof with the selected app.</Text>
<Text color={textColor1} fontSize="$4" fow="bold" mt="$5">Network: Sepolia</Text>
<XStack jc='space-between' h="$2" ai="center">
<Text color={textColor1} fontWeight="bold" fontSize="$5">Tx: {shortenInput(getTx(mintText))}</Text>
</XStack>
</YStack>
<XStack flex={1} />
<Button borderRadius={100} onPress={() => copyToClipboard(getTx(mintText))} marginTop="$4" mb="$8" backgroundColor="#3185FC">
<Copy color="white" size="$1" /><Text color={textColor1} fow="bold" >Copy to clipboard</Text>
</Button>
</YStack>
) : (
<YStack flex={1} justifyContent='center' alignItems='center' gap="$5" pt="$8">
<ProofGrid proof={proof} />
<YStack mt="$6" >
<Text color={textColor1} fontWeight="bold" fontSize="$5" mt="$3">ZK proof generated 🎉</Text>
<Text color={textColor2} mt="$1">Proof generation duration: {formatDuration(proofTime)}</Text>
<Text color={textColor2} fontSize="$5" mt="$4" textAlign='left'>You can now use this proof to mint a Soulbound token.</Text>
<Text color={textColor2} fontSize="$4" mt="$2" textAlign='left'>Disclosed information will be displayed on SBT.</Text>
</YStack>
<XStack flex={1} />
<Button borderColor={borderColor} borderWidth={1.3} disabled={step === Steps.TX_MINTING} borderRadius={100} onPress={handleMint} marginTop="$4" mb="$4" backgroundColor="#0090ff">
{step === Steps.TX_MINTING ?
<XStack gap="$2">
<Spinner />
<Text color={textColor1} fow="bold" > Minting </Text>
</XStack>
: <Text color={textColor1} fow="bold" >{selectedApp?.mintphrase}</Text>}
</Button>
</YStack>
)}
</YStack >
);
};
export default MintScreen;

View File

@@ -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<ProveScreenProps> = ({
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 (
<YStack px="$4" f={1} mb={Platform.OS === 'ios' ? "$5" : "$0"}>
<YStack flex={1} mx="$2" gap="$2">
<YStack alignSelf='center' my="$3">
{hideData ?
<Image
w={height > 750 ? 150 : 100}
h={height > 750 ? 190 : 80}
borderRadius={height > 800 ? "$7" : "$6"}
source={{
uri: USER,
}}
/> :
<Image
w={height > 750 ? 150 : 110}
h={height > 750 ? 190 : 130}
borderRadius={height > 750 ? "$7" : "$6"}
source={{
uri: passportData.photoBase64 ?? USER,
}}
/>
{hideData
? <Image
w={height > 750 ? 150 : 100}
h={height > 750 ? 190 : 80}
borderRadius={height > 800 ? "$7" : "$6"}
source={{
uri: USER,
}}
/>
: <Image
w={height > 750 ? 150 : 110}
h={height > 750 ? 190 : 130}
borderRadius={height > 750 ? "$7" : "$6"}
source={{
uri: passportData.photoBase64 ?? USER,
}}
/>
}
</YStack>
<Text color={textColor1} fontSize="$5" fontWeight="bold" ml="$2" mb="$1">Hi {hideData ? maskString(getFirstName(passportData.mrz)) : getFirstName(passportData.mrz)} 👋</Text>
<Text color={textColor1} fontSize="$5" fontWeight="bold" ml="$2" mb="$1">
Hi {" "}
{
hideData
? maskString(getFirstName(passportData.mrz))
: getFirstName(passportData.mrz)
}
👋
</Text>
<YStack bc={componentBgColor} borderRadius="$6" borderWidth={1.5} borderColor={borderColor}>
<YStack p="$3">
<XStack gap="$4" ai="center">
<XStack p="$2" bc="#232323" borderWidth={1.2} borderColor="#343434" borderRadius="$3">
<Image
source={{ uri: ENS }}
w="$1"
h="$1" />
</XStack>
<YStack gap="$1">
<Text fontSize={16} fow="bold" color="#ededed">Address or ENS</Text>
</YStack>
</XStack>
</YStack>
<YStack bc={componentBgColor2} borderTopWidth={1.5} borderColor={borderColor} borderBottomLeftRadius="$6" borderBottomRightRadius="$6">
<Input
bg="transparent"
color={textColor1}
fontSize={13}
placeholder="anon.eth or 0x023…"
value={inputValue}
onChangeText={setInputValue}
autoCorrect={false}
autoCapitalize='none'
borderColor="transparent"
borderWidth={0}
/>
</YStack>
</YStack>
{fields.map((Field, index) => (
<Field key={index} />
))}
<YStack f={1} >
@@ -186,29 +117,39 @@ const ProveScreen: React.FC<ProveScreenProps> = ({
<YStack gap="$1">
<XStack gap="$2">
<Text fontSize={16} fow="bold" color="#ededed">Disclose</Text>
{/* <Info size="$1" color={textColor2} /> */}
</XStack>
<Text color="#a0a0a0">Select optional data </Text>
<Text color="#a0a0a0">Select what to disclose</Text>
</YStack>
</XStack>
</YStack>
<YStack gap="$2" p="$3" bc="#232323" borderWidth={1.2} borderLeftWidth={0} borderRightWidth={0} borderBottomWidth={0} borderColor="#343434" borderBottomLeftRadius="$6" borderBottomRightRadius="$6">
<YStack
gap="$2"
p="$3"
bc="#232323"
borderWidth={1.2}
borderLeftWidth={0}
borderRightWidth={0}
borderBottomWidth={0}
borderColor="#343434"
borderBottomLeftRadius="$6"
borderBottomRightRadius="$6"
>
<ScrollView h={height < 750 ? "$6" : ""} >
{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 (
<XStack key={key} mx="$2" gap="$3" alignItems='center' >
<XStack key={key} mx="$2" gap="$3" alignItems='center'>
<XStack p="$2" onPress={() => handleDisclosureChange(key_)} >
<Checkbox
bg={componentBgColor}
borderColor={borderColor}
value={key}
checked={disclosure[key_]}
checked={disclosure[key_ as keyof typeof disclosure]}
onCheckedChange={() => handleDisclosureChange(key_)}
aria-label={keyFormatted}
size="$6"
@@ -219,28 +160,52 @@ const ProveScreen: React.FC<ProveScreenProps> = ({
</Checkbox>
</XStack>
<Text color={textColor2} >{keyFormatted}: </Text>
{key_ === 'older_than' ? (
<XStack gap="$1.5" jc='center' ai='center'>
<XStack mr="$2">
<Text color={textColor1} w="$1" fontSize={16}>{majority}</Text>
<Text color={textColor1} fontSize={16}> yo</Text>
</XStack>
<Button bg={componentBgColor} borderColor={borderColor} h="$2" w="$3" onPress={() => setMajority(majority - 1)}><Minus color={textColor1} size={18} /></Button>
<Button bg={componentBgColor} borderColor={borderColor} h="$2" w="$3" onPress={() => setMajority(majority + 1)}><Plus color={textColor1} size={18} /></Button>
<Button
bg={componentBgColor}
borderColor={borderColor}
h="$2"
w="$3"
onPress={() => update({
majority: majority - 1
})}
>
<Minus color={textColor1} size={18} />
</Button>
<Button
bg={componentBgColor}
borderColor={borderColor}
h="$2"
w="$3"
onPress={() => update({
majority: majority + 1
})}
>
<Plus color={textColor1} size={18} />
</Button>
</XStack>
) : (
<Text color={textColor1} >{hideData ? maskString(mrzAttributeFormatted) : mrzAttributeFormatted}</Text>
<Text
color={textColor1}
>
{hideData ? maskString(mrzAttributeFormatted) : mrzAttributeFormatted}
</Text>
)}
</XStack>
);
})}
</ScrollView >
</YStack >
</YStack >
</YStack >
<Button
disabled={zkeydownloadStatus != "completed" || (address == ethers.ZeroAddress)}
disabled={isZkeyDownloading[selectedApp.circuit] || (address == ethers.ZeroAddress)}
borderWidth={1.3}
borderColor={borderColor}
borderRadius={100}
@@ -249,21 +214,21 @@ const ProveScreen: React.FC<ProveScreenProps> = ({
backgroundColor={address == ethers.ZeroAddress ? "#cecece" : "#3185FC"}
alignSelf='center'
>
{zkeydownloadStatus === "downloading" ? (
{!registered ? (
<XStack ai="center" gap="$1">
<Spinner />
<Text color={textColor1} fow="bold">
Registering identity...
</Text>
</XStack>
) : isZkeyDownloading[selectedApp.circuit] ? (
<XStack ai="center" gap="$1">
<Spinner />
<Text color={textColor1} fow="bold">
Downloading ZK proving key
</Text>
</XStack>
) : zkeydownloadStatus === "error" ? (
<XStack ai="center" gap="$1">
<Spinner />
<Text color={textColor1} fow="bold">
Error downloading ZK proving key
</Text>
</XStack>
) : generatingProof ? (
) : step === Steps.GENERATING_PROOF ? (
<XStack ai="center" gap="$1">
<Spinner />
<Text color={textColor2} marginLeft="$2" fow="bold">
@@ -280,9 +245,18 @@ const ProveScreen: React.FC<ProveScreenProps> = ({
</Text>
)}
</Button>
{(height > 750) && <Text fontSize={10} color={generatingProof ? "#a0a0a0" : "#161616"} py="$2" alignSelf='center'>This operation can take up to 2 mn, phone may freeze during this time</Text>}
{
(height > 750) &&
<Text
fontSize={10}
color={step === Steps.GENERATING_PROOF ? "#a0a0a0" : "#161616"}
py="$2"
alignSelf='center'
>
This operation can take up to 1 mn, phone may freeze during this time
</Text>
}
</YStack >
</YStack >
);
};

View File

@@ -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<ScanScreenProps> = ({ onStartCameraScan, handleNFCScan, step }) => {
const toast = useToastController();
const ScanScreen: React.FC<ScanScreenProps> = ({ handleNFCScan, step }) => {
return (
<ScrollView f={1}>
<YStack mt="$4" mb="$6" f={1} p="$5" gap="$5" px="$5" justifyContent='center'>
@@ -56,7 +55,7 @@ const ScanScreen: React.FC<ScanScreenProps> = ({ onStartCameraScan, handleNFCSca
</XStack>
)}
<XStack f={1} />
<Button h="$3" onPress={onStartCameraScan} p="$2" borderRadius="$4" borderWidth={1} backgroundColor="#282828" borderColor="#343434">
<Button h="$3" onPress={startCameraScan} p="$2" borderRadius="$4" borderWidth={1} backgroundColor="#282828" borderColor="#343434">
<XStack gap="$2">
<Text color="#ededed" fontSize="$5" >Open camera</Text>
<ExternalLink size="$1" color="#ededed" />

View File

@@ -0,0 +1,120 @@
import React 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 { Platform, Pressable } from 'react-native';
import { blueColor, borderColor, componentBgColor, textColor1, textColor2 } from '../utils/colors';
import useNavigationStore from '../stores/navigationStore';
import { AppType } from '../utils/appType';
import { appStoreMapping } from './ProveScreen';
const SendProofScreen: React.FC = () => {
const {
step,
} = useNavigationStore();
const selectedApp = useNavigationStore(state => state.selectedApp) as AppType;
const {
handleSendProof,
beforeSendText1,
beforeSendText2,
sendButtonText,
sendingButtonText,
successTitle,
successText,
successComponent,
finalButtonAction,
finalButtonText,
} = selectedApp;
const useAppStore = appStoreMapping[selectedApp.id as keyof typeof appStoreMapping]
const {
proof,
proofTime
} = useAppStore();
return (
<YStack px="$4" f={1} mb={Platform.OS === 'ios' ? "$5" : "$0"}>
{step === Steps.PROOF_SENT ? (
<YStack flex={1} justifyContent='center' alignItems='center' gap="$5">
<XStack flex={1} />
<ProofGrid proof={proof} />
<YStack gap="$1">
<Text color={textColor1} fontWeight="bold" fontSize="$5">
{successTitle}
</Text>
<Text color={textColor1} fontSize="$4" fow="bold" textAlign='left'>
{successText}
</Text>
{successComponent()}
</YStack>
<XStack flex={1} />
<Button
borderRadius={100}
onPress={finalButtonAction}
marginTop="$4"
mb="$8"
backgroundColor="#3185FC"
>
<Copy color="white" size="$1" /><Text color={textColor1} fow="bold">
{finalButtonText}
</Text>
</Button>
</YStack>
) : (
<YStack flex={1} justifyContent='center' alignItems='center' gap="$5" pt="$8">
<ProofGrid proof={proof} />
<YStack mt="$6" >
<Text color={textColor1} fontWeight="bold" fontSize="$5" mt="$3">
ZK proof generated 🎉
</Text>
<Text color={textColor2} mt="$1">
Proof generation duration: {formatDuration(proofTime as number)}
</Text>
<Text color={textColor2} fontSize="$5" mt="$4" textAlign='left'>
{beforeSendText1}
</Text>
<Text color={textColor2} fontSize="$4" mt="$2" textAlign='left'>
{beforeSendText2}
</Text>
</YStack>
<XStack flex={1} />
<Button
borderColor={borderColor}
borderWidth={1.3}
disabled={step === Steps.PROOF_SENDING}
borderRadius={100}
onPress={handleSendProof}
marginTop="$4"
mb="$4"
backgroundColor="#0090ff"
>
{step === Steps.PROOF_SENDING ?
<XStack gap="$2">
<Spinner />
<Text color={textColor1} fow="bold">
{sendingButtonText}
</Text>
</XStack>
: <Text color={textColor1} fow="bold">
{sendButtonText}
</Text>
}
</Button>
</YStack>
)}
</YStack>
);
};
export default SendProofScreen;

View File

@@ -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<typeof useToastController> | null
selectedTab: string
selectedApp: AppType | null
setToast: (toast: ReturnType<typeof useToastController>) => void;
setStep: (step: number) => void
update: (patch: any) => void
}
const useNavigationStore = create<NavigationState>((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

View File

@@ -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<SbtState>((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

184
app/src/stores/userStore.ts Normal file
View File

@@ -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<UserState>((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

View File

@@ -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:',
);

43
app/src/utils/appType.ts Normal file
View File

@@ -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;
}

View File

@@ -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",

View File

@@ -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);
}
};

View File

@@ -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);
};

View File

@@ -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)
};
};

View File

@@ -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;
}
}
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;
};

View File

@@ -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 <circuit_name>_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. <circuit_name>_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()
}
}
}

View File

@@ -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';

View File

@@ -361,4 +361,4 @@ void Fr_rawNot(FrRawElement pRawResult, FrRawElement pRawA)
{
mpn_sub_n(pRawResult, pRawResult, Fr_rawq, Fr_N64);
}
}
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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"

View File

@@ -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],

View File

@@ -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[];
}

View File

@@ -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