mirror of
https://github.com/selfxyz/self.git
synced 2026-01-23 21:48:01 -05:00
Merge branch 'dev' into split_circuits
This commit is contained in:
140
app/App.tsx
140
app/App.tsx
@@ -1,148 +1,36 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
DEFAULT_PNUMBER,
|
||||
DEFAULT_DOB,
|
||||
DEFAULT_DOE,
|
||||
AMPLITUDE_KEY
|
||||
} from '@env';
|
||||
import { PassportData } from '../common/src/utils/types';
|
||||
import { mockPassportData_sha256WithRSAEncryption_65537 } from '../common/src/utils/mockPassportData';
|
||||
import React, { useEffect } from 'react';
|
||||
import "react-native-get-random-values"
|
||||
import "@ethersproject/shims"
|
||||
import { ethers } from "ethers";
|
||||
import MainScreen from './src/screens/MainScreen';
|
||||
import { Steps } from './src/utils/utils';
|
||||
import { startCameraScan } from './src/utils/cameraScanner';
|
||||
import { scan } from './src/utils/nfcScanner';
|
||||
import { mint } from './src/utils/minter';
|
||||
import { Buffer } from 'buffer';
|
||||
import { YStack } from 'tamagui';
|
||||
import { prove } from './src/utils/prover';
|
||||
import { useToastController } from '@tamagui/toast';
|
||||
import useNavigationStore from './src/stores/navigationStore';
|
||||
import { AMPLITUDE_KEY } from '@env';
|
||||
import * as amplitude from '@amplitude/analytics-react-native';
|
||||
import { checkForZkey } from './src/utils/zkeyDownload';
|
||||
import useUserStore from './src/stores/userStore';
|
||||
global.Buffer = Buffer;
|
||||
|
||||
function App(): JSX.Element {
|
||||
const [passportNumber, setPassportNumber] = useState(DEFAULT_PNUMBER ?? "");
|
||||
const [dateOfBirth, setDateOfBirth] = useState(DEFAULT_DOB ?? '');
|
||||
const [dateOfExpiry, setDateOfExpiry] = useState(DEFAULT_DOE ?? '');
|
||||
const [address, setAddress] = useState<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>
|
||||
);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
21
app/android/app/src/main/cpp/CMakeLists.txt
Normal file
21
app/android/app/src/main/cpp/CMakeLists.txt
Normal 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)
|
||||
47
app/android/app/src/main/cpp/include/prover.h
Normal file
47
app/android/app/src/main/cpp/include/prover.h
Normal 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
|
||||
@@ -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
|
||||
BIN
app/android/app/src/main/cpp/lib/librapidsnark.so
Normal file
BIN
app/android/app/src/main/cpp/lib/librapidsnark.so
Normal file
Binary file not shown.
BIN
app/android/app/src/main/cpp/lib/libwitnesscalc_proof_of_passport.so
Executable file
BIN
app/android/app/src/main/cpp/lib/libwitnesscalc_proof_of_passport.so
Executable file
Binary file not shown.
136
app/android/app/src/main/cpp/proofofpassport.cpp
Normal file
136
app/android/app/src/main/cpp/proofofpassport.cpp
Normal 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;
|
||||
}
|
||||
@@ -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.
BIN
app/android/app/src/main/res/raw/proof_of_passport.dat
Normal file
BIN
app/android/app/src/main/res/raw/proof_of_passport.dat
Normal file
Binary file not shown.
@@ -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'
|
||||
|
||||
14
app/ark-zkey/.gitignore
vendored
14
app/ark-zkey/.gitignore
vendored
@@ -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
|
||||
@@ -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" }
|
||||
@@ -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**
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
14
app/mopro-core/.gitignore
vendored
14
app/mopro-core/.gitignore
vendored
@@ -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
|
||||
@@ -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" }
|
||||
@@ -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.
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
pub mod middleware;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MoproError {
|
||||
#[error("CircomError: {0}")]
|
||||
CircomError(String),
|
||||
}
|
||||
@@ -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(¶ms.vk).map_err(|e| MoproError::CircomError(e.to_string()))?;
|
||||
|
||||
let proof_verified =
|
||||
GrothBn::verify_with_processed_vk(&pvk, &serialized_inputs.0, &serialized_proof.0)
|
||||
.map_err(|e| MoproError::CircomError(e.to_string()))?;
|
||||
|
||||
let verification_duration = start.elapsed();
|
||||
println!("Verification time: {:?}", verification_duration);
|
||||
Ok(proof_verified)
|
||||
}
|
||||
|
||||
fn load_config(
|
||||
&self,
|
||||
wasm_path: &str,
|
||||
r1cs_path: &str,
|
||||
) -> Result<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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod circom;
|
||||
18
app/mopro-ffi/.gitignore
vendored
18
app/mopro-ffi/.gitignore
vendored
@@ -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/
|
||||
@@ -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" }
|
||||
@@ -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)
|
||||
@@ -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`
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
uniffi::generate_scaffolding("src/mopro.udl").expect("Building the UDL file failed");
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
@@ -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",
|
||||
);
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
uniffi::uniffi_bindgen_main()
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
[bindings.swift]
|
||||
module_name = "mopro"
|
||||
@@ -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",
|
||||
|
||||
@@ -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/
|
||||
@@ -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
22
app/scripts/common.sh
Normal 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
30
app/src/apps/gitcoin.tsx
Normal 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
262
app/src/apps/sbt.tsx
Normal 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
30
app/src/apps/zupass.tsx
Normal 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;
|
||||
117
app/src/components/EnterAddress.tsx
Normal file
117
app/src/components/EnterAddress.tsx
Normal 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;
|
||||
@@ -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()),
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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 >
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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" />
|
||||
|
||||
120
app/src/screens/SendProofScreen.tsx
Normal file
120
app/src/screens/SendProofScreen.tsx
Normal 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;
|
||||
51
app/src/stores/navigationStore.ts
Normal file
51
app/src/stores/navigationStore.ts
Normal 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
|
||||
55
app/src/stores/sbtStore.ts
Normal file
55
app/src/stores/sbtStore.ts
Normal 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
184
app/src/stores/userStore.ts
Normal 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
|
||||
@@ -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
43
app/src/utils/appType.ts
Normal 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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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)
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -361,4 +361,4 @@ void Fr_rawNot(FrRawElement pRawResult, FrRawElement pRawA)
|
||||
{
|
||||
mpn_sub_n(pRawResult, pRawResult, Fr_rawq, Fr_N64);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user