Merge branch 'dev' into android-release

This commit is contained in:
0xturboblitz
2024-02-16 14:17:49 +01:00
32 changed files with 3762 additions and 3081 deletions

View File

@@ -1,23 +1,9 @@
import React, { useEffect, useState } from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
useColorScheme,
NativeModules,
DeviceEventEmitter,
TextInput,
Platform,
} from 'react-native';
import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
import Toast, { BaseToast, ErrorToast, SuccessToast, ToastProps } from 'react-native-toast-message';
// @ts-ignore
import PassportReader from 'react-native-passport-reader';
@@ -28,7 +14,7 @@ import {
DEFAULT_DOE,
DEFAULT_ADDRESS,
} from '@env';
import { DataHash, PassportData, mockPassportData } from '../common/src/utils/types';
import { DataHash, PassportData } from '../common/src/utils/types';
import { AWS_ENDPOINT } from '../common/src/constants/constants';
import {
hash,
@@ -45,7 +31,7 @@ import {
import { samplePassportData } from '../common/src/utils/passportDataStatic';
import "@ethersproject/shims"
import { ethers } from "ethers";
import { ethers, ZeroAddress } from "ethers";
import axios from 'axios';
import groth16ExportSolidityCallData from './utils/snarkjs';
import contractAddresses from "./deployments/addresses.json"
@@ -59,8 +45,6 @@ global.Buffer = Buffer;
console.log('DEFAULT_PNUMBER', DEFAULT_PNUMBER);
const SKIP_SCAN = false;
const attributeToPosition = {
issuing_state: [2, 5],
name: [5, 44],
@@ -72,23 +56,19 @@ const attributeToPosition = {
}
function App(): JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const [passportNumber, setPassportNumber] = useState(DEFAULT_PNUMBER ?? "");
const [dateOfBirth, setDateOfBirth] = useState(DEFAULT_DOB ?? '');
const [dateOfExpiry, setDateOfExpiry] = useState(DEFAULT_DOE ?? '');
const [address, setAddress] = useState(DEFAULT_ADDRESS ?? '');
const [passportData, setPassportData] = useState(samplePassportData);
const [step, setStep] = useState(Steps.MRZ_SCAN);
const [testResult, setTestResult] = useState<any>(null);
const [address, setAddress] = useState<string>(ethers.ZeroAddress);
const [passportData, setPassportData] = useState<PassportData>(samplePassportData as PassportData);
const [step, setStep] = useState<number>(Steps.MRZ_SCAN);
const [error, setError] = useState<any>(null);
const [generatingProof, setGeneratingProof] = useState<boolean>(false);
const [proofTime, setProofTime] = useState<number>(0);
const [totalTime, setTotalTime] = useState<number>(0);
const [proof, setProof] = useState<{ proof: string, inputs: string } | null>(null);
const [minting, setMinting] = useState<boolean>(false);
const [mintText, setMintText] = useState<string | null>(null);
const [mintText, setMintText] = useState<string>("");
const [disclosure, setDisclosure] = useState({
issuing_state: false,
name: false,
@@ -115,7 +95,7 @@ function App(): JSX.Element {
setDateOfBirth(birthDate);
setDateOfExpiry(expiryDate);
setStep(Steps.MRZ_SCAN_COMPLETED);
} catch (error) {
} catch (error: any) {
console.error('Invalid MRZ format:', error.message);
}
})
@@ -133,19 +113,6 @@ function App(): JSX.Element {
});
};
const backgroundStyle = {
backgroundColor: Colors.white,
flex: 1
};
const inputStyle = StyleSheet.create({
inputField: {
minHeight: 45, // Set a minimum height that fits the text
// Add other styles as needed to match your design
},
// Include any other styles you want to apply to the input component
});
useEffect(() => {
const logEventListener = DeviceEventEmitter.addListener('LOG_EVENT', e => {
console.log(e);
@@ -160,21 +127,23 @@ function App(): JSX.Element {
if (Platform.OS !== 'android') {
NativeModules.Prover.runInitAction() // for mopro, ios only rn
}
if (SKIP_SCAN && passportData === null) {
setPassportData(samplePassportData as PassportData);
setStep(Steps.NFC_SCAN_COMPLETED);
}
}, []);
async function handleResponseIOS(response: any) {
const parsed = JSON.parse(response);
const eContentBase64 = parsed.eContentBase64; // this is what we call concatenatedDataHashes in our world
const signedAttributes = parsed.signedAttributes; // this is what we call eContent in our world
const eContentBase64 = parsed.eContentBase64; // this is what we call concatenatedDataHashes in android world
const signedAttributes = parsed.signedAttributes; // this is what we call eContent in android world
const signatureAlgorithm = parsed.signatureAlgorithm;
const mrz = parsed.passportMRZ;
const dataGroupHashes = parsed.dataGroupHashes;
const signatureBase64 = parsed.signatureBase64;
console.log('dataGroupsPresent', parsed.dataGroupsPresent)
console.log('placeOfBirth', parsed.placeOfBirth)
console.log('activeAuthenticationPassed', parsed.activeAuthenticationPassed)
console.log('isPACESupported', parsed.isPACESupported)
console.log('isChipAuthenticationSupported', parsed.isChipAuthenticationSupported)
console.log('residenceAddress', parsed.residenceAddress)
console.log('passportPhoto', parsed.passportPhoto.substring(0, 100) + '...')
console.log('parsed.documentSigningCertificate', parsed.documentSigningCertificate)
const pem = JSON.parse(parsed.documentSigningCertificate).PEM.replace(/\\\\n/g, '\n')
@@ -192,17 +161,6 @@ function App(): JSX.Element {
const concatenatedDataHashesArray = Array.from(Buffer.from(eContentBase64, 'base64'));
const concatenatedDataHashesArraySigned = concatenatedDataHashesArray.map(byte => byte > 127 ? byte - 256 : byte);
const dgHashes = JSON.parse(dataGroupHashes);
console.log('dgHashes', dgHashes)
const dataGroupHashesArray = Object.keys(dgHashes)
.map(key => {
const dgNumber = parseInt(key.replace('DG', ''));
const hashArray = hexStringToSignedIntArray(dgHashes[key].computedHash);
return [dgNumber, hashArray];
})
.sort((a, b) => (a[0] as number) - (b[0] as number));
const encryptedDigestArray = Array.from(Buffer.from(signatureBase64, 'base64')).map(byte => byte > 127 ? byte - 256 : byte);
const passportData = {
@@ -214,6 +172,7 @@ function App(): JSX.Element {
dataGroupHashes: concatenatedDataHashesArraySigned,
eContent: signedEContentArray,
encryptedDigest: encryptedDigestArray,
photoBase64: "data:image/jpeg;base64," + parsed.passportPhoto,
};
console.log('mrz', passportData.mrz);
@@ -224,7 +183,7 @@ function App(): JSX.Element {
console.log('encryptedDigest', passportData.encryptedDigest);
setPassportData(passportData);
setStep('scanCompleted');
setStep(Steps.NFC_SCAN_COMPLETED);
}
async function handleResponseAndroid(response: any) {
@@ -237,6 +196,7 @@ function App(): JSX.Element {
dataGroupHashes,
eContent,
encryptedDigest,
photo
} = response;
const passportData: PassportData = {
@@ -250,6 +210,7 @@ function App(): JSX.Element {
dataGroupHashes: dataHashesObjToArray(JSON.parse(dataGroupHashes)),
eContent: JSON.parse(eContent),
encryptedDigest: JSON.parse(encryptedDigest),
photoBase64: photo.base64,
};
console.log('mrz', passportData.mrz);
@@ -258,6 +219,7 @@ function App(): JSX.Element {
console.log('dataGroupHashes', passportData.dataGroupHashes);
console.log('eContent', passportData.eContent);
console.log('encryptedDigest', passportData.encryptedDigest);
console.log("photoBase64", passportData.photoBase64.substring(0, 100) + '...')
setPassportData(passportData);
setStep(Steps.NFC_SCAN_COMPLETED);
@@ -290,7 +252,7 @@ function App(): JSX.Element {
dateOfBirth: dateOfBirth,
dateOfExpiry: dateOfExpiry,
});
console.log('response', response);
// console.log('response', response);
console.log('scanned');
handleResponseAndroid(response);
} catch (e: any) {
@@ -391,7 +353,6 @@ function App(): JSX.Element {
}
const end = Date.now();
console.log('Total proof time from frontend:', end - start);
setTotalTime(end - start);
};
async function proveAndroid(inputs: any, path: string) {
@@ -449,7 +410,7 @@ function App(): JSX.Element {
// setProofTime(response.duration);
setGeneratingProof(false)
setStep('proofGenerated');
setStep(Steps.PROOF_GENERATED);
} catch (err: any) {
console.log('err', err);
setError(
@@ -540,87 +501,37 @@ function App(): JSX.Element {
};
return (
<YStack f={1}>
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={Colors.red}
<YStack f={1} bg="white" h="100%" w="100%">
<YStack h="100%" w="100%">
<MainScreen
onStartCameraScan={startCameraScan}
nfcScan={scan}
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}
/>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.black,
}}
contentContainerStyle={{ flexGrow: 1 }}
>
<YStack style={styles.view}>
<MainScreen
onStartCameraScan={startCameraScan}
nfcScan={scan}
passportData={passportData}
disclosure={disclosure}
handleDisclosureChange={handleDisclosureChange}
address={address}
setAddress={setAddress}
generatingProof={generatingProof}
handleProve={handleProve}
step={step}
mintText={mintText}
proof={proof}
proofTime={proofTime}
handleMint={handleMint}
totalTime={totalTime}
setStep={setStep}
passportNumber={passportNumber}
setPassportNumber={setPassportNumber}
dateOfBirth={dateOfBirth}
setDateOfBirth={setDateOfBirth}
dateOfExpiry={dateOfExpiry}
setDateOfExpiry={setDateOfExpiry}
/>
</YStack>
</ScrollView>
</SafeAreaView>
</YStack>
<Toast config={toastConfig} />
</YStack>
);
}
const styles = StyleSheet.create({
view: {
flex: 1,
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
header: {
fontSize: 22,
fontWeight: 'bold',
textAlign: 'center',
marginTop: 20,
},
testSection: {
backgroundColor: '#f2f2f2', // different background color
padding: 10,
borderTopWidth: 1,
borderTopColor: '#dcdcdc', // adding a border top with a light color
marginTop: 15,
},
});
export default App;

View File

@@ -35,6 +35,12 @@ 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
This you modify the circuits, you might have to modify `ark-circom-passport/src/passport.rs` too.
#### Build the iOS native module

View File

@@ -498,87 +498,84 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
}
override fun onPostExecute(result: Exception?) {
if (scanPromise == null) return
if (scanPromise == null) return
if (result != null) {
// Log.w(TAG, exceptionStack(result))
if (result is IOException) {
scanPromise?.reject("E_SCAN_FAILED_DISCONNECT", "Lost connection to chip on card")
} else {
scanPromise?.reject("E_SCAN_FAILED", result)
}
if (result != null) {
// Log.w(TAG, exceptionStack(result))
if (result is IOException) {
scanPromise?.reject("E_SCAN_FAILED_DISCONNECT", "Lost connection to chip on card")
} else {
scanPromise?.reject("E_SCAN_FAILED", result)
}
resetState()
return
}
resetState()
return
}
val mrzInfo = dg1File.mrzInfo
val mrzInfo = dg1File.mrzInfo
// var quality = 100
// if (opts?.hasKey("quality") == true) {
// quality = (opts?.getDouble("quality") ?: 1.0 * 100).toInt()
// }
val gson = Gson()
val gson = Gson()
val signedDataField = SODFile::class.java.getDeclaredField("signedData")
signedDataField.isAccessible = true
// val signedData = signedDataField.get(sodFile) as SignedData
val eContentAsn1InputStream = ASN1InputStream(sodFile.eContent.inputStream())
// val eContentDecomposed: ASN1Primitive = eContentAsn1InputStream.readObject()
val passport = Arguments.createMap()
passport.putString("mrz", mrzInfo.toString())
passport.putString("signatureAlgorithm", sodFile.docSigningCertificate.sigAlgName) // this one is new
val publicKey = sodFile.docSigningCertificate.publicKey
if (publicKey is RSAPublicKey) {
passport.putString("modulus", publicKey.modulus.toString())
} else if (publicKey is ECPublicKey) {
// Handle the elliptic curve public key case
val signedDataField = SODFile::class.java.getDeclaredField("signedData")
signedDataField.isAccessible = true
// val w = publicKey.getW()
// passport.putString("publicKeyW", w.toString())
// val signedData = signedDataField.get(sodFile) as SignedData
// val ecParams = publicKey.getParams()
// passport.putInt("cofactor", ecParams.getCofactor())
// passport.putString("curve", ecParams.getCurve().toString())
// passport.putString("generator", ecParams.getGenerator().toString())
// passport.putString("order", ecParams.getOrder().toString())
// if (ecParams is ECNamedCurveSpec) {
// passport.putString("curveName", ecParams.getName())
// }
val eContentAsn1InputStream = ASN1InputStream(sodFile.eContent.inputStream())
// val eContentDecomposed: ASN1Primitive = eContentAsn1InputStream.readObject()
val passport = Arguments.createMap()
passport.putString("mrz", mrzInfo.toString())
passport.putString("signatureAlgorithm", sodFile.docSigningCertificate.sigAlgName) // this one is new
val publicKey = sodFile.docSigningCertificate.publicKey
if (publicKey is RSAPublicKey) {
passport.putString("modulus", publicKey.modulus.toString())
} else if (publicKey is ECPublicKey) {
// Handle the elliptic curve public key case
// val w = publicKey.getW()
// passport.putString("publicKeyW", w.toString())
// val ecParams = publicKey.getParams()
// passport.putInt("cofactor", ecParams.getCofactor())
// passport.putString("curve", ecParams.getCurve().toString())
// passport.putString("generator", ecParams.getGenerator().toString())
// passport.putString("order", ecParams.getOrder().toString())
// if (ecParams is ECNamedCurveSpec) {
// passport.putString("curveName", ecParams.getName())
// }
// Old one, probably wrong:
// passport.putString("curveName", (publicKey.parameters as ECNamedCurveSpec).name)
// passport.putString("curveName", (publicKey.parameters.algorithm)) or maybe this
passport.putString("publicKeyQ", publicKey.q.toString())
}
// Old one, probably wrong:
// passport.putString("curveName", (publicKey.parameters as ECNamedCurveSpec).name)
// passport.putString("curveName", (publicKey.parameters.algorithm)) or maybe this
passport.putString("publicKeyQ", publicKey.q.toString())
}
passport.putString("dataGroupHashes", gson.toJson(sodFile.dataGroupHashes))
passport.putString("eContent", gson.toJson(sodFile.eContent))
passport.putString("encryptedDigest", gson.toJson(sodFile.encryptedDigest))
// Another way to get signing time is to get into signedData.signerInfos, then search for the ICO identifier 1.2.840.113549.1.9.5
// passport.putString("signerInfos", gson.toJson(signedData.signerInfos))
// Log.d(TAG, "signedData.digestAlgorithms: ${gson.toJson(signedData.digestAlgorithms)}")
// Log.d(TAG, "signedData.signerInfos: ${gson.toJson(signedData.signerInfos)}")
// Log.d(TAG, "signedData.certificates: ${gson.toJson(signedData.certificates)}")
// val base64 = bitmap?.let { toBase64(it, quality) }
// val photo = Arguments.createMap()
// photo.putString("base64", base64 ?: "")
// photo.putInt("width", bitmap?.width ?: 0)
// photo.putInt("height", bitmap?.height ?: 0)
// passport.putMap("photo", photo)
// passport.putString("dg2File", gson.toJson(dg2File))
scanPromise?.resolve(passport)
resetState()
}
passport.putString("dataGroupHashes", gson.toJson(sodFile.dataGroupHashes))
passport.putString("eContent", gson.toJson(sodFile.eContent))
passport.putString("encryptedDigest", gson.toJson(sodFile.encryptedDigest))
// Another way to get signing time is to get into signedData.signerInfos, then search for the ICO identifier 1.2.840.113549.1.9.5
// passport.putString("signerInfos", gson.toJson(signedData.signerInfos))
// Log.d(TAG, "signedData.digestAlgorithms: ${gson.toJson(signedData.digestAlgorithms)}")
// Log.d(TAG, "signedData.signerInfos: ${gson.toJson(signedData.signerInfos)}")
// Log.d(TAG, "signedData.certificates: ${gson.toJson(signedData.certificates)}")
var quality = 100
val base64 = bitmap?.let { toBase64(it, quality) }
val photo = Arguments.createMap()
photo.putString("base64", base64 ?: "")
photo.putInt("width", bitmap?.width ?: 0)
photo.putInt("height", bitmap?.height ?: 0)
passport.putMap("photo", photo)
// passport.putString("dg2File", gson.toJson(dg2File))
scanPromise?.resolve(passport)
resetState()
}
}
private fun convertDate(input: String?): String? {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2,17 +2,23 @@
* @format
*/
import { AppRegistry } from 'react-native';
import { AppRegistry, LogBox } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
import { TamaguiProvider } from 'tamagui';
import { createTamagui, createTokens } from 'tamagui';
import { createTamagui } from 'tamagui';
import { config } from '@tamagui/config/v2-native'
import myAppConfig from './tamagui.config';
const tamaguiConfig = createTamagui(config)
LogBox.ignoreLogs([
/bad setState/,
])
const Root = () => (
<TamaguiProvider config={tamaguiConfig}>
<TamaguiProvider config={myAppConfig}>
<App />
</TamaguiProvider>
);

View File

@@ -118,6 +118,14 @@ class PassportReader: NSObject{
ret["phoneNumber"] = passport.phoneNumber
ret["personalNumber"] = passport.personalNumber
let passportPhotoData = passport.passportPhoto // [UInt8]
if let passportPhotoData = passport.passportPhoto {
let data = Data(passportPhotoData)
let base64String = data.base64EncodedString()
ret["passportPhoto"] = base64String
}
// documentSigningCertificate
// countrySigningCertificate

View File

@@ -34,7 +34,7 @@ target 'ProofOfPassport' do
flags = get_default_flags()
use_frameworks!
pod 'NFCPassportReader', git: 'https://github.com/0xturboblitz/NFCPassportReader.git', commit: 'd36952eeaa2025ff1a9c9abbc244bd5ff53eb0f9'
pod 'NFCPassportReader', git: 'https://github.com/0xturboblitz/NFCPassportReader.git', commit: '310ecb519655d9ed8b1afc5eb490b2f51a4d3595'
pod 'MoproKit', :path => './MoproKit'
use_react_native!(

View File

@@ -403,7 +403,7 @@ DEPENDENCIES:
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- MoproKit (from `./MoproKit`)
- NFCPassportReader (from `https://github.com/0xturboblitz/NFCPassportReader.git`, commit `d36952eeaa2025ff1a9c9abbc244bd5ff53eb0f9`)
- NFCPassportReader (from `https://github.com/0xturboblitz/NFCPassportReader.git`, commit `310ecb519655d9ed8b1afc5eb490b2f51a4d3595`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
@@ -460,7 +460,7 @@ EXTERNAL SOURCES:
MoproKit:
:path: "./MoproKit"
NFCPassportReader:
:commit: d36952eeaa2025ff1a9c9abbc244bd5ff53eb0f9
:commit: 310ecb519655d9ed8b1afc5eb490b2f51a4d3595
:git: https://github.com/0xturboblitz/NFCPassportReader.git
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
@@ -533,7 +533,7 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
NFCPassportReader:
:commit: d36952eeaa2025ff1a9c9abbc244bd5ff53eb0f9
:commit: 310ecb519655d9ed8b1afc5eb490b2f51a4d3595
:git: https://github.com/0xturboblitz/NFCPassportReader.git
SPEC CHECKSUMS:
@@ -555,13 +555,13 @@ SPEC CHECKSUMS:
React-Core: 8293312ad137ea82fd2c29deb163dbc24aa4e00e
React-CoreModules: 32fab1d62416849a3b6dac6feff9d54e5ddc2d1e
React-cxxreact: 55d0f7cb6b4cc09ba9190797f1da87182d1a2fb6
React-debug: 7e61555c8158126c6cd98c3154381ad3821aaaca
React-debug: 878f0c4026b30a6240f7a15f8612efcf5d8c3df9
React-jsc: 0db8e8cc2074d979c37ffa7b8d7c914833960497
React-jsi: 58677ff4848ceb6aeb9118fe03448a843ea5e16a
React-jsiexecutor: 2c15ba1bace70177492368d5180b564f165870fd
React-jsinspector: b511447170f561157547bc0bef3f169663860be7
React-logger: c5b527272d5f22eaa09bb3c3a690fee8f237ae95
React-NativeModulesApple: 0438665fc7473be6edc496e823e6ea0b0537b46c
React-NativeModulesApple: 3a49a4bc38b979b804525816b781eb6612dba5fa
React-perflogger: 6bd153e776e6beed54c56b0847e1220a3ff92ba5
React-RCTActionSheet: c0b62af44e610e69d9a2049a682f5dba4e9dff17
React-RCTAnimation: fe7005136b58f58871cab2f70732343b6e330d30
@@ -575,13 +575,13 @@ SPEC CHECKSUMS:
React-RCTVibration: ea3a68a49873a54ced927c90923fc6932baf344a
React-rncore: 9672a017af4a7da7495d911f0b690cbcae9dd18d
React-runtimeexecutor: 369ae9bb3f83b65201c0c8f7d50b72280b5a1dbc
React-runtimescheduler: ec1066a4f2d1152eb1bc3fb61d69376b3bc0dde0
React-utils: d55ba834beb39f01b0b470ae43478c0a3a024abe
ReactCommon: 68e3a815fbb69af3bb4196e04c6ae7abb306e7a8
React-runtimescheduler: 116fb55732ddfd96298350528cf13ceaf94759c8
React-utils: a8681f0d721ff080373ae9e4afb1f380707b55f9
ReactCommon: df6a7f5665621529ee01b89fb0c3c93eb014f276
RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce
PODFILE CHECKSUM: d401e6efe0c933985349c8c115c7edca8fef3182
PODFILE CHECKSUM: 7568291077da8ee6387464cd1a7e01559a46ab1f
COCOAPODS: 1.14.3
COCOAPODS: 1.15.0

View File

@@ -490,8 +490,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ProofOfPassport/ProofOfPassport.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 5B29R5LYHQ;
DEVELOPMENT_TEAM = X53ZR86F22;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = ProofOfPassport/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -512,8 +514,9 @@
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.proofofpassport;
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.proofofpassportdev;
PRODUCT_NAME = ProofOfPassport;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -528,7 +531,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = ProofOfPassport/ProofOfPassport.entitlements;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 5B29R5LYHQ;
DEVELOPMENT_TEAM = X53ZR86F22;
INFOPLIST_FILE = ProofOfPassport/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@@ -547,7 +550,7 @@
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.proofofpassport;
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.proofofpassportdev;
PRODUCT_NAME = ProofOfPassport;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -12,23 +12,26 @@
"dependencies": {
"@babel/plugin-transform-private-methods": "^7.23.3",
"@ethersproject/shims": "^5.7.0",
"@tamagui/config": "^1.84.0",
"@tamagui/lucide-icons": "^1.84.0",
"@tamagui/config": "^1.89.20",
"@tamagui/core": "^1.89.20",
"@tamagui/lucide-icons": "^1.89.20",
"@tamagui/types": "^1.89.20",
"axios": "^1.6.3",
"body-parser": "^1.20.2",
"buffer": "^6.0.3",
"crypto-js": "^4.1.1",
"ethers": "^6.9.1",
"ethers": "^6.11.0",
"express": "^4.18.2",
"js-sha256": "^0.9.0",
"node-forge": "^1.3.1",
"pvutils": "^1.1.3",
"react": "18.2.0",
"react-native": "0.72.3",
"react-native-canvas": "^0.1.39",
"react-native-passport-reader": "^1.0.3",
"react-native-svg": "13.4.0",
"react-native-toast-message": "^2.2.0",
"tamagui": "^1.84.0"
"tamagui": "^1.89.20"
},
"devDependencies": {
"@babel/core": "^7.20.0",

View File

@@ -0,0 +1,7 @@
module.exports = {
project: {
ios: {},
android: {},
},
assets: ['./assets/fonts'],
};

View File

@@ -1,40 +1,70 @@
import React from 'react';
import { Text, YStack, XStack, Card, H3, Image } from 'tamagui';
import { ChevronRight } from '@tamagui/lucide-icons';
import { Platform } from 'react-native';
const AppCard = ({ title, description, colorOfTheText, background, id, onTouchStart, eleva }) => {
interface AppCardProps {
title: string;
description: string;
colorOfTheText: string;
background: string | undefined;
id: string | number;
onTouchStart?: () => void;
selected?: boolean;
}
const AppCard: React.FC<AppCardProps> = ({
title,
description,
colorOfTheText,
background,
id,
onTouchStart,
selected
}) => {
return (
<Card
key={id}
<XStack
overflow="hidden"
elevation={selected ? "$3" : "$3"}
borderRadius="$10"
elevation={eleva}
onTouchStart={onTouchStart}
borderColor={(selected) ? "#3185FC" : ((Platform.OS === 'ios') ? "white" : "transparent")}
borderWidth={(selected) ? 3 : 3}
shadowColor={selected ? "#3185FC" : "black"}
>
<XStack
<Card
key={id}
elevation={0}
onTouchStart={onTouchStart}
>
<Card.Header w="100%">
<XStack w="100%" ai="center" py="$1" >
<YStack>
<H3 color={colorOfTheText} selectable={false} >{title}</H3>
<Text theme="alt2" color={colorOfTheText} selectable={false}>{description}</Text>
</YStack>
<XStack flex={1} />
<ChevronRight size="$4" color={colorOfTheText} />
</XStack>
</Card.Header>
{background && (
<Card.Background>
<Image
flex={1}
borderRadius="$10"
source={{
uri: background
}}
/>
</Card.Background>
)}
</XStack>
</Card >
<XStack w="100%"
>
<Card.Header w="100%">
<XStack ai="center" py="$1">
<YStack width={250}>
<H3 color={colorOfTheText} selectable={false} >{title}</H3>
<Text mt="$1" theme="alt2" color={colorOfTheText} selectable={false}>{description}</Text>
</YStack>
<XStack flex={1} />
<ChevronRight size="$4" color={selected ? "#3185FC" : colorOfTheText} minWidth="$4" />
</XStack>
</Card.Header>
{(
<Card.Background
>
{background &&
<Image
flex={1}
source={{
uri: background
}}
/>}
</Card.Background>
)}
</XStack>
</Card >
</XStack>
);
}

View File

@@ -1,21 +0,0 @@
import { styled, Text,View } from '@tamagui/core';
// Correctly capitalized component name
const CircleContainer = styled(View, {
alignItems: 'center',
justifyContent: 'center',
display: 'flex',
borderRadius: 2,
borderWidth: 2,
borderColor: '#000',
width: 30,
height: 30,});
// Usage
const CircleText = () => (
<CircleContainer>
<Text>1</Text>
</CircleContainer>
);
export default CircleText;

View File

@@ -0,0 +1,74 @@
import React, { useMemo } from 'react';
import { Svg, Rect } from 'react-native-svg';
import { YStack } from 'tamagui';
interface ProofGridProps {
proof: { proof: string; inputs: string } | null;
}
const ProofGrid: React.FC<ProofGridProps> = ({ proof }) => {
const gridSize = 8;
const pixelSize = 15;
const sumAndScaleDigits = useMemo(() => (values: string[]) => {
const sum = values.reduce((acc, val) => acc + BigInt(val), BigInt(0));
const digits = sum.toString().split('').map(Number);
return digits.map(digit => Math.round(digit * (256 / 9)));
}, []);
// Prepare the RGB values
const { rValues, gValues, bValues } = useMemo(() => {
if (!proof) {
return { rValues: [], gValues: [], bValues: [] };
}
const parsedProof = JSON.parse(proof.proof);
return {
rValues: sumAndScaleDigits(parsedProof.a),
gValues: sumAndScaleDigits(parsedProof.b.flat()),
bValues: sumAndScaleDigits(parsedProof.c)
};
}, [proof, sumAndScaleDigits]);
// Generate the grid data
const gridData = useMemo(() =>
Array.from({ length: gridSize }, (_, rowIndex) =>
Array.from({ length: gridSize }, (_, colIndex) => {
const index = rowIndex * gridSize + colIndex;
const r = index < rValues.length ? rValues[index] : 0;
const g = index < gValues.length ? gValues[index] : 0;
const b = index < bValues.length ? bValues[index] : 0;
return `rgb(${r}, ${g}, ${b})`;
})
)
, [gridSize, rValues, gValues, bValues]);
// Render the grid using SVG and Rect
return (
<YStack
width={gridSize * pixelSize}
borderRadius={40}
overflow="hidden"
elevation="$4"
style={{ backdropFilter: 'blur(10px)' }}
>
<Svg height={gridSize * pixelSize} width={gridSize * pixelSize} >
{gridData.map((row, i) =>
row.map((fill, j) => (
<Rect
key={`${i}-${j}`}
x={j * pixelSize}
y={i * pixelSize}
width={pixelSize}
height={pixelSize}
fill={fill}
/>
))
)}
</Svg>
</YStack>
);
};
export default ProofGrid;

BIN
app/src/images/nfc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -3,18 +3,32 @@ import ZUPASS from '../images/zupass.png';
import GITCOIN from '../images/gitcoin.png';
import { YStack } from 'tamagui';
import AppCard from '../components/AppCard';
import { App, gitcoin, soulbond, zuzalu } from '../utils/AppClass';
const AppScreen = ({ selectedApp, setSelectedApp }) => {
import { App, gitcoin, soulbound, zuzalu } from '../utils/AppClass';
import { Steps } from '../utils/utils';
interface AppScreenProps {
selectedApp: App | null;
setSelectedApp: (app: App | null) => void;
step: number;
setStep: (step: number) => void;
}
const AppScreen: React.FC<AppScreenProps> = ({ selectedApp, setSelectedApp, step, setStep }) => {
const handleCardSelect = (app: App) => {
setSelectedApp(app);
if (selectedApp != app) {
setSelectedApp(app);
if (step >= Steps.NFC_SCAN_COMPLETED) {
setStep(Steps.NFC_SCAN_COMPLETED);
}
}
};
const cardsData = [
{
app: zuzalu,
title: 'Add to Zupasss',
description: 'And prove your identity at in person',
description: 'Prove your identity at in person events',
background: ZUPASS,
colorOfTheText: 'white',
},
@@ -26,15 +40,15 @@ const AppScreen = ({ selectedApp, setSelectedApp }) => {
colorOfTheText: 'white',
},
{
app: soulbond,
app: soulbound,
title: 'Mint SBT',
description: 'And prove your identity at in person events',
description: 'And prove you\'re a human',
colorOfTheText: 'black',
},
];
return (
<YStack gap="$5" w="100%" p="$5">
<YStack flex={1} gap="$5" px="$5" jc="center" alignItems='center' >
{cardsData.map(card => (
<AppCard
@@ -45,7 +59,7 @@ const AppScreen = ({ selectedApp, setSelectedApp }) => {
background={card.background}
id={card.app.id}
onTouchStart={() => handleCardSelect(card.app)}
eleva={selectedApp && selectedApp.id === card.app.id ? "$0" : "$12"}
selected={selectedApp && selectedApp.id === card.app.id ? true : false}
/>
))}

View File

@@ -1,40 +1,70 @@
import React, { useState, useEffect } from 'react';
import { YStack, XStack, Text, Button, SizableText, Tabs, styled, Dialog, Adapt, Sheet, Label, Fieldset, Input, Switch, ThemeableStack } from 'tamagui'
import { Scan, UserCheck, HelpCircle, XCircle, IterationCw, LayoutGrid, Sparkles } from '@tamagui/lucide-icons';
import { YStack, XStack, Text, Button, Tabs, styled, Dialog, Adapt, Sheet, Label, Fieldset, Input, Switch, ThemeableStack, Separator, H3, H2, Image } from 'tamagui'
import { Scan, UserCheck, HelpCircle, IterationCw, LayoutGrid, VenetianMask, Cog, CheckCircle2 } from '@tamagui/lucide-icons';
import ScanScreen from './ScanScreen';
import ProveScreen from './ProveScreen';
import { Steps } from '../utils/utils';
import AppScreen from './AppScreen';
import { App } from '../utils/AppClass';
const MainScreen = (
{ onStartCameraScan,
nfcScan,
passportData,
disclosure,
handleDisclosureChange,
address,
setAddress,
generatingProof,
handleProve,
step,
mintText,
proof,
proofTime,
handleMint,
totalTime,
setStep,
passportNumber,
setPassportNumber,
dateOfBirth,
setDateOfBirth,
dateOfExpiry,
setDateOfExpiry
}
) => {
import { Platform } from 'react-native';
import { Keyboard } from 'react-native';
import NFC_IMAGE from '../images/nfc.png'
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;
}
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
}) => {
const [isOpen, setIsOpen] = 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 [open, setOpen] = useState(false)
const [hideData, setHideData] = useState(false);
const [open, setOpen] = useState(true)
const AppCard = styled(ThemeableStack, {
hoverTheme: true,
pressTheme: true,
@@ -56,31 +86,206 @@ const MainScreen = (
setDateOfExpiry("");
}
const handleHideData = () => {
setHideData(!hideData);
}
const handleNFCScan = () => {
if ((Platform.OS === 'ios')) {
console.log('ios');
nfcScan();
}
else {
console.log('android :)');
setIsOpen(true);
nfcScan();
}
}
useEffect(() => {
// Check if length of each field is correct and move to step MRZ_SCAN_COMPLETED if so
if (passportNumber?.length === 9 && (dateOfBirth?.length === 6 && dateOfExpiry?.length === 6)) {
setStep(Steps.MRZ_SCAN_COMPLETED);
}
}, [passportNumber, dateOfBirth, dateOfExpiry]);
return (
<YStack f={1} ai="center" jc="space-between" bc="#fff">
useEffect(() => {
let timeoutId: ReturnType<typeof setTimeout>;
if (step >= Steps.NFC_SCAN_COMPLETED) {
// Set the timeout and store its ID
timeoutId = setTimeout(() => {
setIsOpen(false);
}, 700);
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [step]);
<YStack w="100%">
<XStack w="100%" jc="space-between" ai="center" ph="$4" pv="$2" bc="#fff" p="$3">
<XStack></XStack>
<Text>
{selectedTab === "scan" ? "Scan" : (selectedTab === "app" ? "App" : "Prove")}
// 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();
};
}, []);
return (
<YStack f={1} bc="white" mt={Platform.OS === 'ios' ? "$8" : "$0"} mb={Platform.OS === 'ios' ? "$3" : "$0"}>
<YStack >
<XStack jc="space-between" ai="center" p="$2" px="$3">
<Dialog
modal
>
<Dialog.Trigger >
<YStack p="$2" pr="$7">
<Cog />
</YStack>
</Dialog.Trigger>
<Adapt when="sm" platform="touch">
<Sheet animation="medium" zIndex={200000} modal dismissOnSnapToBottom>
<Sheet.Frame padding="$4" gap="$4">
<Adapt.Contents />
</Sheet.Frame>
<Sheet.Overlay
animation="lazy"
enterStyle={{ opacity: 0 }}
exitStyle={{ opacity: 0 }}
/>
</Sheet>
</Adapt>
<Dialog.Portal>
<Dialog.Overlay
key="overlay"
animation="quick"
opacity={0.5}
enterStyle={{ opacity: 0 }}
exitStyle={{ opacity: 0 }}
/>
<Dialog.Content
bordered
elevate
key="content"
animateOnly={['transform', 'opacity']}
animation={[
'quick',
{
opacity: {
overshootClamping: true,
},
},
]}
enterStyle={{ x: 0, y: -20, opacity: 0, scale: 0.9 }}
exitStyle={{ x: 0, y: 10, opacity: 0, scale: 0.95 }}
>
<YStack f={1} gap="$2">
<XStack gap="$2" >
<Dialog.Title>Settings</Dialog.Title>
<Cog mt="$1" alignSelf='center' size="$2" />
</XStack>
<Fieldset mt="$2" horizontal>
<Label width={225} justifyContent="flex-end" htmlFor="restart" fow="bold">
Private mode
</Label>
<Switch size="$4" checked={hideData} onCheckedChange={handleHideData}>
<Switch.Thumb animation="bouncy" backgroundColor="white" />
</Switch>
</Fieldset>
<Fieldset mt="$1" horizontal>
<Label width={225} justifyContent="flex-end" htmlFor="name" fow="bold">
Broken camera
</Label>
<Switch size="$4" checked={brokenCamera} onCheckedChange={setBrokenCamera}>
<Switch.Thumb animation="bouncy" backgroundColor="white" />
</Switch>
</Fieldset>
{
brokenCamera &&
<YStack pl="$3" gap="$3" mt="$4">
<Fieldset gap="$4" horizontal>
<Label width={160} justifyContent="flex-end" fontSize={13}>
Passport Number
</Label>
<Input borderColor={passportNumber?.length === 9 ? "green" : "unset"} flex={1} id="passport_number" onChangeText={(text) => setPassportNumber(text.toUpperCase())} value={passportNumber} keyboardType="default" />
</Fieldset>
<Fieldset gap="$4" horizontal>
<Label width={160} justifyContent="flex-end" fontSize={13}>
Date of birth (yymmdd)
</Label>
<Input borderColor={dateOfBirth?.length === 6 ? "green" : "unset"} flex={1} id="date_of_birth" onChangeText={setDateOfBirth} value={dateOfBirth} keyboardType="numeric" />
</Fieldset>
<Fieldset gap="$4" horizontal>
<Label width={160} justifyContent="flex-end" fontSize={13}>
Date of expiry (yymmdd)
</Label>
<Input borderColor={dateOfExpiry?.length === 6 ? "green" : "unset"} flex={1} id="date_of_expiry" onChangeText={setDateOfExpiry} value={dateOfExpiry} keyboardType="numeric" />
</Fieldset>
</YStack>
}
<Fieldset gap="$4" mt="$3" horizontal>
<Label width={200} justifyContent="flex-end" htmlFor="restart" fow="bold">
Restart to step 1
</Label>
<Button size="$4" m="$2" onPress={handleRestart}>
<IterationCw />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$2" horizontal>
<Label width={200} justifyContent="flex-end" htmlFor="skip" fow="bold">
Use mock passport data
</Label>
<Button size="$4" m="$2" onPress={handleSkip}>
<VenetianMask />
</Button>
</Fieldset>
<YStack flex={1}>
<YStack flex={1} />
<Dialog.Close mb="$6" displayWhenAdapted alignSelf='center' asChild >
<Button>
<Text w="80%" textAlign='center' fow="bold">Close</Text>
</Button>
</Dialog.Close>
</YStack>
</YStack>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
<Text fow="bold">
{selectedTab === "scan" ? "Scan" : (selectedTab === "app" ? "Apps" : "Prove")}
</Text>
<Dialog
modal
onOpenChange={(open) => {
setOpen(open)
}}
>
<Dialog.Trigger asChild p="$2">
<HelpCircle />
<Dialog.Trigger>
<YStack p="$2" pl="$8">
<HelpCircle />
</YStack>
</Dialog.Trigger>
<Adapt when="sm" platform="touch">
@@ -122,160 +327,167 @@ const MainScreen = (
exitStyle={{ x: 0, y: 10, opacity: 0, scale: 0.95 }}
gap="$4"
>
<XStack space >
<Dialog.Title>Settings</Dialog.Title>
</XStack>
<Fieldset gap="$4" mt="$2" horizontal>
<Label width={160} justifyContent="flex-end" htmlFor="name" fow="bold">
Restart to step 1
</Label>
<Button size="$4" m="$2" onPress={handleRestart}>
<IterationCw />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$2" horizontal>
<Label width={160} justifyContent="flex-end" htmlFor="name" fow="bold">
Skip to App selection
</Label>
<Button size="$4" m="$2" onPress={handleSkip}>
<Sparkles />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$2" horizontal>
<Label width={160} justifyContent="flex-end" htmlFor="name" fow="bold">
Broken camera
</Label>
<Switch size="$4" checked={brokenCamera} onCheckedChange={setBrokenCamera}>
<Switch.Thumb animation="bouncy" backgroundColor="white" color />
</Switch>
</Fieldset>
{
brokenCamera &&
<YStack space pl="$3">
<Fieldset gap="$4" horizontal>
<Label width={160} justifyContent="flex-end" htmlFor="name">
Passport Number
</Label>
<Input borderColor={passportNumber?.length === 9 ? "green" : "unset"} flex={1} id="passport_number" onChangeText={(text) => setPassportNumber(text.toUpperCase())} value={passportNumber} keyboardType="default" />
</Fieldset>
<Fieldset gap="$4" mt="$2" horizontal>
<Label width={160} justifyContent="flex-end" htmlFor="name">
Date of birth (yymmdd)
</Label>
<Input borderColor={dateOfBirth?.length === 6 ? "green" : "unset"} flex={1} id="date_of_birth" onChangeText={setDateOfBirth} value={dateOfBirth} keyboardType="numeric" />
</Fieldset>
<Fieldset gap="$4" mt="$2" horizontal>
<Label width={160} justifyContent="flex-end" htmlFor="name">
Date of expiry (yymmdd)
</Label>
<Input borderColor={dateOfExpiry?.length === 6 ? "green" : "unset"} flex={1} id="date_of_expiry" onChangeText={setDateOfExpiry} value={dateOfExpiry} keyboardType="numeric" />
</Fieldset>
</YStack>
}
<YStack flex={1}>
<XStack gap="$2">
<Dialog.Title>Help</Dialog.Title>
<HelpCircle mt="$1" alignSelf='center' size="$2" />
</XStack>
<H3 fontFamily="Luciole" mt="$3">How to scan your passport ?</H3>
<YStack>
<Text>1. Find the location of the NFC chip of your passport.</Text>
<Text>If you are struggling <Text color="#3185FC">this post</Text> will help you to find it.</Text>
<Text mt="$2">2. Find where is the NFC lector of your phone.</Text>
<Text mt="$2">3. Keep both part pressed together when this app ask for it.</Text>
</YStack>
<H3 mt="$3">Security and User data Privacy</H3>
<YStack gap="$2">
<Text>This app gerates ZK proofs to ...</Text>
</YStack>
<H3 mt="$3">What are ZK proofs ?</H3>
<YStack gap="$2">
<Text>Zero Knowledge proofs are .....</Text>
</YStack>
<H3 mt="$3">Contacts</H3>
<YStack gap="$2">
<Text>telegram</Text>
</YStack>
<H3 mt="$3">Credits</H3>
<YStack >
<Text>Ethereum Foundation</Text>
<Text>turboblitz.eth</Text>
<Text>???.eth</Text>
</YStack>
<YStack flex={1}></YStack>
<Dialog.Close mb="$4" displayWhenAdapted asChild alignSelf='center'>
<XCircle size="$3" />
<Dialog.Close displayWhenAdapted alignSelf='center' asChild >
<Button>
<Text w="80%" textAlign='center' fow="bold">Close</Text>
</Button>
</Dialog.Close>
<YStack flex={1}></YStack>
</YStack>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
</XStack>
<YStack w="100%" h={2} backgroundColor="#DCDCDC" opacity={0.16}></YStack>
<Sheet open={isOpen} onOpenChange={setIsOpen} modal dismissOnOverlayPress={false} disableDrag animation="medium" snapPoints={[40]}>
<Sheet.Overlay />
<Sheet.Handle />
<Sheet.Frame>
<YStack gap="$5" f={1} pt="$3">
<H2 textAlign='center' color="gray">Ready to scan</H2>
{step >= Steps.NFC_SCAN_COMPLETED ?
<CheckCircle2
size="$8"
alignSelf='center'
color="#3185FC"
animation="quick"
/> :
<Image
h="$8"
w="$8"
alignSelf='center'
borderRadius={1000}
source={{
uri: NFC_IMAGE
}}
/>
}
<Text textAlign='center'>Hold your device near the NFC tag.</Text>
<Button my="$2" w="$20" alignSelf='center' onPress={() => setIsOpen(false)}>
<Text textAlign='center' fow="bold"> Cancel</Text>
</Button>
</YStack>
</Sheet.Frame>
</Sheet>
<Separator />
</YStack>
<Tabs f={1} orientation="horizontal" flexDirection="column" defaultValue="scan" onValueChange={setSelectedTab}>
<Tabs.Content value="scan" f={1}>
<ScanScreen
onStartCameraScan={onStartCameraScan}
handleNFCScan={handleNFCScan}
step={step} />
</Tabs.Content>
<Tabs.Content value="app" f={1}>
<AppScreen
selectedApp={selectedApp}
setSelectedApp={setSelectedApp}
step={step}
setStep={setStep}
/>
</Tabs.Content>
<Tabs f={1} defaultValue="scan" orientation='horizontal' dir='ltr' shadowColor="black" onValueChange={(newValue) => setSelectedTab(newValue)}>
<YStack ai="center" jc="space-between" bc="" >
<XStack flexGrow={0} ai="center" />
<Tabs.Content value="scan">
<ScanScreen
onStartCameraScan={onStartCameraScan}
nfcScan={nfcScan}
step={step} />
</Tabs.Content>
<Tabs.Content value="app">
<AppScreen
selectedApp={selectedApp}
setSelectedApp={setSelectedApp}
/>
</Tabs.Content>
<Tabs.Content value="generate">
<ProveScreen
passportData={passportData}
disclosure={disclosure}
selectedApp={selectedApp}
handleDisclosureChange={handleDisclosureChange}
address={address}
setAddress={setAddress}
generatingProof={generatingProof}
handleProve={handleProve}
step={step}
mintText={mintText}
proof={proof}
proofTime={proofTime}
handleMint={handleMint}
totalTime={totalTime}
setStep={setStep}
/>
</Tabs.Content>
<YStack w="100%" backgroundColor="white">
<YStack w="100%" h={2} backgroundColor="#DCDCDC" opacity={0.16}></YStack>
<Tabs.List w="100%" pt="$4" pb="$3">
<Tabs.Tab unstyled value="scan" w="33%" backgroundColor="transparent" >
<Tabs.Content value="generate" f={1}>
<ProveScreen
passportData={passportData}
disclosure={disclosure}
selectedApp={selectedApp}
handleDisclosureChange={handleDisclosureChange}
address={address}
setAddress={setAddress}
generatingProof={generatingProof}
handleProve={handleProve}
step={step}
mintText={mintText}
proof={proof}
proofTime={proofTime}
handleMint={handleMint}
hideData={hideData}
ens={ens}
setEns={setEns} />
</Tabs.Content>
<Separator />
{(!keyboardVisible || Platform.OS == "ios") &&
<Tabs.List separator={<Separator vertical />} pt="$4" pb="$3">
<Tabs.Tab value="scan" unstyled w="33%">
<YStack ai="center">
<Scan color={selectedTab === "scan" ? '#3185FC' : 'black'} />
<Text color={selectedTab === "scan" ? '#3185FC' : 'black'}>Scan</Text>
</YStack>
</Tabs.Tab>
{step >= Steps.NFC_SCAN_COMPLETED ?
<Tabs.Tab value="app" unstyled w="33%">
<YStack ai="center">
<Scan color={selectedTab === "scan" ? '#3185FC' : 'black'} />
<SizableText color={selectedTab === "scan" ? '#3185FC' : 'black'}>Scan</SizableText>
<LayoutGrid color={selectedTab === "app" ? '#3185FC' : 'black'} />
<Text color={selectedTab === "app" ? '#3185FC' : 'black'}>Apps</Text>
</YStack>
</Tabs.Tab>
:
<Tabs.Tab value="scan" unstyled w="33%">
<YStack ai="center">
<LayoutGrid color="#bcbcbc" />
<Text color="#bcbcbc">Apps</Text>
</YStack>
</Tabs.Tab>
{step < Steps.NFC_SCAN_COMPLETED ?
<Tabs.Tab unstyled value="scan" w="33%" backgroundColor="transparent" >
<YStack ai="center">
<LayoutGrid color="#eeeeee" />
<SizableText color="#eeeeee">App</SizableText>
</YStack>
</Tabs.Tab>
:
<Tabs.Tab unstyled value="app" w="33%" backgroundColor="transparent" >
<YStack ai="center">
<LayoutGrid color={selectedTab === "app" ? '#3185FC' : 'black'} />
<SizableText color={selectedTab === "app" ? '#3185FC' : 'black'}>App</SizableText>
</YStack>
</Tabs.Tab>
}
{selectedApp === null ?
<Tabs.Tab unstyled value={step < Steps.NFC_SCAN_COMPLETED ? "scan" : "app"} w="33%" backgroundColor="transparent">
<YStack ai="center">
<UserCheck color="#eeeeee" />
<SizableText color="#eeeeee">Prove</SizableText>
</YStack>
</Tabs.Tab>
:
<Tabs.Tab unstyled value="generate" w="33%" backgroundColor="transparent">
}
{
(step >= Steps.NFC_SCAN_COMPLETED) && (selectedApp != null) ?
<Tabs.Tab value="generate" unstyled w="33%">
<YStack ai="center">
<UserCheck color={selectedTab === "generate" ? '#3185FC' : 'black'} />
<SizableText color={selectedTab === "generate" ? '#3185FC' : 'black'}>Prove</SizableText>
<Text color={selectedTab === "generate" ? '#3185FC' : 'black'}>Prove</Text>
</YStack>
</Tabs.Tab>
}
</Tabs.List>
</YStack>
</YStack>
</Tabs >
:
<Tabs.Tab value={step >= Steps.NFC_SCAN_COMPLETED ? "app" : "scan"} unstyled w="33%">
<YStack ai="center">
<UserCheck color="#bcbcbc" />
<Text color="#bcbcbc">Prove</Text>
</YStack>
</Tabs.Tab>
}
</Tabs.List>
}
</Tabs>
</YStack >
);
};

View File

@@ -1,16 +1,39 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { NativeModules } from 'react-native';
import { YStack, XStack, Text, Checkbox, Input, Button, Spinner, SizableText, Image } from 'tamagui';
import { Check } from '@tamagui/lucide-icons';
import { YStack, XStack, Text, Checkbox, Input, Button, Spinner, Image } from 'tamagui';
import { Check, LayoutGrid, Scan } from '@tamagui/lucide-icons';
import { getFirstName, formatDuration } from '../../utils/utils';
import { attributeToPosition } from '../../../common/src/constants/constants';
import { Steps } from '../utils/utils';
import USER from '../images/user.png'
import ProofGrid from '../components/ProofGrid';
import { App } from '../utils/AppClass';
import { Keyboard, Platform } from 'react-native';
const { ethers } = require('ethers');
const fileName = "passport.arkzkey"
const path = "/data/user/0/com.proofofpassport/files/" + fileName
const ProveScreen = ({
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;
}
const ProveScreen: React.FC<ProveScreenProps> = ({
passportData,
disclosure,
selectedApp,
@@ -20,12 +43,13 @@ const ProveScreen = ({
generatingProof,
handleProve,
step,
setStep,
mintText,
proof,
proofTime,
handleMint,
totalTime
hideData,
ens,
setEns
}) => {
const [downloadingFile, setDownloadingFile] = useState(false);
const [zkeyLoaded, setZkeyLoaded] = useState(false);
@@ -51,61 +75,121 @@ const ProveScreen = ({
}
};
const maskString = (input: string): string => {
if (input.length <= 5) {
return input.charAt(0) + '*'.repeat(input.length - 1);
} else {
return input.charAt(0) + input.charAt(1) + '*'.repeat(input.length - 2);
}
}
const [inputValue, setInputValue] = useState('');
const provider = new ethers.JsonRpcProvider(`https://eth-mainnet.g.alchemy.com/v2/lpOn3k6Fezetn1e5QF-iEsn-J0C6oGE0`);
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 {
const resolvedAddress = await provider.resolveName(inputValue);
if (resolvedAddress) {
console.log("new address settled:" + resolvedAddress);
setAddress(resolvedAddress);
setEns(inputValue);
if (hideData) {
console.log(maskString(address));
// setInputValue(maskString(address));
}
else {
// setInputValue(address);
}
} else {
console.error("Could not resolve ENS name.");
}
} catch (error) {
console.error("Error resolving ENS name:", error);
}
}
else if (inputValue.length === 42 && inputValue.startsWith('0x')) {
setAddress(inputValue);
}
};
};
resolveENS();
}, [inputValue]);
// 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();
};
}, []);
return (
<YStack space="$4" p="$4" >
{
step < Steps.PROOF_GENERATED ? (
<YStack >
<YStack w="100%" ai="center">
<Image
w="$12"
h="$12"
flex={1}
<YStack px="$4" f={1}>
{(step >= Steps.NFC_SCAN_COMPLETED && selectedApp != null) ?
(step < Steps.PROOF_GENERATED ? (
<YStack flex={1} m="$2" gap="$2">
<XStack flex={1} />
<YStack alignSelf='center' mt="$1">
{hideData ? <Image
w="$13"
h="$15"
borderRadius="$10"
source={{
uri: USER
uri: USER,
}}
/>
/> :
<Image
w="$13"
h="$15"
borderRadius="$10"
source={{
uri: passportData.photoBase64 ?? USER,
}}
/>
}
</YStack>
<YStack mt="$12">
<SizableText mb="$1">Hi {getFirstName(passportData.mrz)},</SizableText>
<Text mb="$2">{selectedApp.name} is asking for the following information:</Text>
{Object.keys(selectedApp.disclosure).map((key) => {
const keyy = key as keyof typeof disclosure;
const indexes = attributeToPosition[keyy];
const keyFormatted = keyy.replace(/_/g, ' ').split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
const mrzAttribute = passportData.mrz.slice(indexes[0], indexes[1]);
const mrzAttributeFormatted = mrzAttribute.replace(/</g, ' ');
<XStack f={1} />
return (
<XStack key={key} m="$2" w="$full" gap="$2">
<Checkbox
value={key}
checked={disclosure[keyy]}
onCheckedChange={() => handleDisclosureChange(keyy)}
aria-label={keyFormatted}
size="$5"
>
<Checkbox.Indicator >
<Check />
</Checkbox.Indicator>
</Checkbox>
<Text fontWeight="bold">{keyFormatted}: </Text>
<Text>{mrzAttributeFormatted}</Text>
</XStack>
);
})}
</YStack>
<Text mt="$3">Enter your address or ens</Text>
<Text mt="$8" fontWeight="bold">Hi {hideData ? maskString(getFirstName(passportData.mrz)) : getFirstName(passportData.mrz)},</Text>
<Text mt="$2">Enter your address or ens:</Text>
<Input
size="md"
fontSize={13}
mt="$3"
placeholder="Your Address or ens name"
value={address}
onChangeText={setAddress}
value={inputValue}
onChangeText={setInputValue}
autoCorrect={false}
autoCapitalize='none'
borderColor={address != ethers.ZeroAddress ? "#3185FC" : "unset"}
/>
{downloadingFile ? (
@@ -125,34 +209,90 @@ const ProveScreen = ({
</Button>
)}
<Button borderRadius={100} onPress={() => {handleProve(path)}} mt="$6" backgroundColor="#3185FC" >
{(!keyboardVisible || Platform.OS == "ios") && <YStack mt="$6" f={1}>
<Text h="$3">{selectedApp?.disclosurephrase}</Text>
<YStack mt="$1">
{selectedApp && Object.keys(selectedApp.disclosure).map((key) => {
const key_ = key as string;
const indexes = attributeToPosition[key_];
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 = mrzAttribute;
return (
<XStack key={key} m="$2" gap="$4">
<Checkbox
value={key}
checked={disclosure[key_]}
onCheckedChange={() => handleDisclosureChange(key_)}
aria-label={keyFormatted}
size="$5"
>
<Checkbox.Indicator >
<Check />
</Checkbox.Indicator>
</Checkbox>
<Text fontWeight="bold">{keyFormatted}: </Text>
<Text>{hideData ? maskString(mrzAttributeFormatted) : mrzAttributeFormatted}</Text>
</XStack>
);
})}
</YStack>
</YStack>}
<XStack f={1} />
<XStack f={1} />
<XStack f={1} />
{(!keyboardVisible || Platform.OS == "ios") && <Button disabled={address == ethers.ZeroAddress} borderRadius={100} onPress={handleProve} mt="$8" backgroundColor={address == ethers.ZeroAddress ? "#cecece" : "#3185FC"} alignSelf='center' >
{generatingProof ? (
<XStack ai="center">
<Spinner />
<Text color="white" marginLeft="$2" fow="bold">Generating zk proof</Text>
<Text color="white" marginLeft="$2" fow="bold" >Generating ZK proof</Text>
</XStack>
) : (
<Text color="white" fow="bold">Generate zk proof</Text>
<Text color="white" fow="bold">Generate ZK proof</Text>
)}
</Button>
</Button>}
<Text fontSize={10} color={generatingProof ? "gray" : "white"} alignSelf='center'>This operation can take up to 2 mn</Text>
<Text fontSize={9} color={generatingProof ? "gray" : "white"} pb="$2" alignSelf='center'>The application may freeze during this time (hard work)</Text>
</YStack>
) : (
<YStack m="$2">
<Text>Zero-knowledge proof generated</Text>
<YStack flex={1} m="$2" justifyContent='center' alignItems='center' gap="$5">
<XStack flex={1} />
<ProofGrid proof={proof} />
<Text fontWeight="bold">Proof:</Text>
<Text>{JSON.stringify(proof)}</Text>
<YStack>
<Text fontWeight="bold" fontSize="$6" mt="$6">Congrats 🎉</Text>
<Text fontWeight="bold" fontSize="$5">You just generated this Zero Knowledge proof !</Text>
<Text color="gray" fontSize="$5" mt="$1" fow="bold" textAlign='left'>You can now share this proof with the selected app.</Text>
<Text fontWeight="bold">Proof Duration: {formatDuration(proofTime)}</Text>
<Text fontWeight="bold">Total Duration: {formatDuration(totalTime)}</Text>
<Text color="gray" mt="$3">Proof generation duration: {formatDuration(proofTime)}</Text>
<Button borderRadius={100} onPress={handleMint} marginTop="$4" mb="$4" backgroundColor="#3185FC">
<Text color="white" fow="bold">Mint Proof of Passport</Text>
</YStack>
<XStack flex={1} />
{mintText && <Text color="gray">{mintText}</Text>}
<Button borderRadius={100} onPress={handleMint} marginTop="$4" mb="$8" backgroundColor="#3185FC">
<Text color="white" fow="bold" >{selectedApp?.mintphrase}</Text>
</Button>
{mintText && <Text>{mintText}</Text>}
</YStack>
)
) :
(
<YStack flex={1} justifyContent='center' alignItems='center'>
<Text fontSize={17} textAlign='center' fow="bold">Please scan your passport and select an app to generate ZK proof</Text>
<XStack mt="$8" gap="$7">
<Scan size="$4" color={step < Steps.NFC_SCAN_COMPLETED ? "black" : "#3185FC"} />
<LayoutGrid size="$4" color={selectedApp == null ? "black" : "#3185FC"} />
</XStack>
</YStack>
)
}
</YStack >
);

View File

@@ -1,72 +1,45 @@
import React from 'react';
import { YStack, Text, Spinner, Circle, ZStack, XStack, SizableText } from 'tamagui'; // Ensure correct import paths based on your project setup
import { YStack, Text, Spinner, XStack } from 'tamagui';
import { Steps } from '../utils/utils';
const ScanScreen = ({ onStartCameraScan, nfcScan, step }) => {
interface ScanScreenProps {
onStartCameraScan: () => void;
handleNFCScan: () => void;
step: number;
}
const ScanScreen: React.FC<ScanScreenProps> = ({ onStartCameraScan, handleNFCScan, step }) => {
return (
<YStack >
<ZStack alignSelf='center' maxWidth={50} maxHeight={50} width={50} flex={1} space="$0">
<Circle
alignSelf='center'
h={22}
w={22}
borderWidth={1.6}
p={0}
/>
<SizableText
alignSelf='center'
h={22}
w={22}
y={-1}
x={7}
color="black"
fow="bold"
>1</SizableText>
</ZStack>
<Text textAlign='center' mt="$-3" px="$4" fow={step === Steps.MRZ_SCAN ? "bold" : "normal"} >Scan the machine readable zone on the main page of your passport</Text>
<YStack f={1} p="$5" gap="$1" px="$5" justifyContent='center'>
<XStack
jc="center"
borderColor="black"
borderWidth={1.5}
borderRadius="$10"
f={0}
w="$1"
h="$1"
alignSelf='center'>
<Text fontSize={12} alignSelf='center' fow="bold">1</Text>
</XStack>
<Text textAlign='center' mt="$2" fow={step === Steps.MRZ_SCAN ? "bold" : "normal"} >Scan the machine readable zone on the main page of your passport</Text>
<XStack
mt="$9"
jc="center"
borderColor="black"
borderWidth={1.5}
borderRadius={10}
f={0}
w="$1"
h="$1"
alignSelf='center'>
<Text fontSize={12} alignSelf='center' fow="bold">2</Text>
</XStack>
<Text textAlign='center' mt="$2" fow={(step === Steps.MRZ_SCAN_COMPLETED) || (step === Steps.NFC_SCANNING) ? "bold" : "normal"}>Hold your passport against your device to read the biometric chip</Text>
<ZStack alignSelf='center' mt="$8" maxWidth={50} maxHeight={50} width={50} flex={1} space="$0">
<Circle
alignSelf='center'
h={22}
w={22}
borderWidth={1.6}
p={0}
/>
<SizableText
alignSelf='center'
h={22}
w={22}
y={-1}
x={7}
color="black"
fow="bold"
>2</SizableText>
</ZStack>
<Text textAlign='center' mt="$-3" px="$4" fow={(step === Steps.MRZ_SCAN_COMPLETED) || (step === Steps.NFC_SCANNING) ? "bold" : "normal"}>Hold your passport against your device to read the biometric chip</Text>
<ZStack alignSelf='center' mt="$8" maxWidth={50} maxHeight={50} width={50} flex={1} space="$0">
<Circle
alignSelf='center'
h={22}
w={22}
borderWidth={1.6}
p={0}
/>
<SizableText
alignSelf='center'
h={22}
w={22}
y={-1}
x={7}
color="black"
fow="bold"
>3</SizableText>
</ZStack>
<Text textAlign='center' mt="$-3" px="$4" fow={step >= Steps.NFC_SCAN_COMPLETED ? "bold" : "normal"}>Select App</Text>
<YStack w="100%" ai="center">
<YStack ai="center">
{
step < Steps.NFC_SCAN_COMPLETED
? (
@@ -79,18 +52,18 @@ const ScanScreen = ({ onStartCameraScan, nfcScan, step }) => {
</YStack>
: (
<YStack mt="$10">
<Text size="$4" br="$2" color="#3185FC" onPress={nfcScan}>
<Text size="$4" br="$2" color="#3185FC" onPress={handleNFCScan}>
{step === Steps.NFC_SCANNING ? "Scanning" : "Scan passport with NFC"}
</Text>
<Spinner mt="$4" color={step === Steps.NFC_SCANNING ? "#3185FC" : "transparent"} />
</YStack>
)
)
: <XStack />
:
<XStack />
}
</YStack>
</YStack>
</YStack >
);
};

View File

@@ -1,9 +0,0 @@
// src/tamagui.config.js
import { createTamagui } from 'tamagui';
export const tamaguiConfig = createTamagui({
// Your basic Tamagui configuration goes here
// You can start with an empty config and extend it as needed
});

View File

@@ -1,5 +1,3 @@
// AppClass.ts
type Disclosure = {
[key: string]: boolean;
};
@@ -8,15 +6,18 @@ export class App {
id: string;
name: string;
disclosure: Disclosure;
mintphrase: string;
disclosurephrase: string;
constructor(id: string, name: string, disclosure: Disclosure) {
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;
}
}
// Create instances of the App class
export const gitcoin = new App("gitcoin", "Gitcoin", {});
export const soulbond = new App("soulbond", "Soulbond token", { nationality: false, expiry_date: false });
export const zuzalu = new App("zuzalu", "Zupass", { date_of_birth: false });
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: true, expiry_date: false }, "Mint Soulbound token", "Choose the information you want to disclose and mint your SBT.");
export const zuzalu = new App("zuzalu", "Zupass", { date_of_birth: false }, "Add to Zupass", "Zupass requires the following information:");

15
app/tamagui.config.ts Normal file
View File

@@ -0,0 +1,15 @@
import { config } from '@tamagui/config/v3'
import { createTamagui } from 'tamagui' // or '@tamagui/core'
const appConfig = createTamagui(config)
export type AppConfig = typeof appConfig
declare module 'tamagui' {
// or '@tamagui/core'
// overrides TamaguiCustomConfig so your custom types
// work everywhere you import `tamagui`
interface TamaguiCustomConfig extends AppConfig { }
}
export default appConfig

File diff suppressed because it is too large Load Diff