diff --git a/app/App.tsx b/app/App.tsx
index 399f23578..82b6d9da4 100644
--- a/app/App.tsx
+++ b/app/App.tsx
@@ -1,18 +1,20 @@
import React, { useEffect } from 'react';
+import "react-native-get-random-values"
import "@ethersproject/shims"
import MainScreen from './src/screens/MainScreen';
import { Buffer } from 'buffer';
import { YStack } from 'tamagui';
import { useToastController } from '@tamagui/toast';
-import { downloadZkey } from './src/utils/zkeyDownload';
import useNavigationStore from './src/stores/navigationStore';
import { AMPLITUDE_KEY } from '@env';
import * as amplitude from '@amplitude/analytics-react-native';
+import useUserStore from './src/stores/userStore';
global.Buffer = Buffer;
function App(): JSX.Element {
const toast = useToastController();
const setToast = useNavigationStore((state) => state.setToast);
+ const initUserStore = useUserStore((state) => state.initUserStore);
useEffect(() => {
setToast(toast);
@@ -20,10 +22,7 @@ function App(): JSX.Element {
useEffect(() => {
amplitude.init(AMPLITUDE_KEY);
-
- // downloadZkey("register_sha256WithRSAEncryption_65537"); // might move after nfc scanning
- // downloadZkey("disclose");
- downloadZkey("proof_of_passport");
+ initUserStore();
}, []);
// TODO: when passportData already stored, retrieve and jump to main screen
diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock
index 3750b9896..fcab64cf5 100644
--- a/app/ios/Podfile.lock
+++ b/app/ios/Podfile.lock
@@ -291,6 +291,8 @@ PODS:
- React-jsinspector (0.72.3)
- React-logger (0.72.3):
- glog
+ - react-native-get-random-values (1.11.0):
+ - React-Core
- react-native-netinfo (11.3.1):
- React-Core
- React-NativeModulesApple (0.72.3):
@@ -398,12 +400,12 @@ PODS:
- React-jsi (= 0.72.3)
- React-logger (= 0.72.3)
- React-perflogger (= 0.72.3)
- - RNCAsyncStorage (1.23.1):
- - React-Core
- RNCClipboard (1.5.1):
- React-Core
- RNFS (2.20.0):
- React-Core
+ - RNKeychain (8.2.0):
+ - React-Core
- RNSVG (13.4.0):
- React-Core
- RNZipArchive (6.1.0):
@@ -443,6 +445,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
+ - react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
@@ -461,9 +464,9 @@ DEPENDENCIES:
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
- RNFS (from `../node_modules/react-native-fs`)
+ - RNKeychain (from `../node_modules/react-native-keychain`)
- RNSVG (from `../node_modules/react-native-svg`)
- RNZipArchive (from `../node_modules/react-native-zip-archive`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -524,6 +527,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
React-logger:
:path: "../node_modules/react-native/ReactCommon/logger"
+ react-native-get-random-values:
+ :path: "../node_modules/react-native-get-random-values"
react-native-netinfo:
:path: "../node_modules/@react-native-community/netinfo"
React-NativeModulesApple:
@@ -560,12 +565,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/utils"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
- RNCAsyncStorage:
- :path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
:path: "../node_modules/@react-native-community/clipboard"
RNFS:
:path: "../node_modules/react-native-fs"
+ RNKeychain:
+ :path: "../node_modules/react-native-keychain"
RNSVG:
:path: "../node_modules/react-native-svg"
RNZipArchive:
@@ -605,6 +610,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 2c15ba1bace70177492368d5180b564f165870fd
React-jsinspector: b511447170f561157547bc0bef3f169663860be7
React-logger: c5b527272d5f22eaa09bb3c3a690fee8f237ae95
+ react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06
react-native-netinfo: bdb108d340cdb41875c9ced535977cac6d2ff321
React-NativeModulesApple: 0438665fc7473be6edc496e823e6ea0b0537b46c
React-perflogger: 6bd153e776e6beed54c56b0847e1220a3ff92ba5
@@ -623,9 +629,9 @@ SPEC CHECKSUMS:
React-runtimescheduler: ec1066a4f2d1152eb1bc3fb61d69376b3bc0dde0
React-utils: d55ba834beb39f01b0b470ae43478c0a3a024abe
ReactCommon: 68e3a815fbb69af3bb4196e04c6ae7abb306e7a8
- RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
+ RNKeychain: bfe3d12bf4620fe488771c414530bf16e88f3678
RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2
RNZipArchive: ef9451b849c45a29509bf44e65b788829ab07801
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
diff --git a/app/ios/ProofOfPassport/Info.plist b/app/ios/ProofOfPassport/Info.plist
index 87c2f53b8..08aba7a5c 100644
--- a/app/ios/ProofOfPassport/Info.plist
+++ b/app/ios/ProofOfPassport/Info.plist
@@ -31,9 +31,11 @@
NFCReaderUsageDescription
Need NFC to read Passport
NSAppTransportSecurity
-
+
+ NSFaceIDUsageDescription
+ Needed to secure the secret
NSCameraUsageDescription
- Needed to scan your passport MRZ, you can however enter it manually.
+ Needed to scan the passport MRZ.
NSHumanReadableCopyright
NSLocationWhenInUseUsageDescription
diff --git a/app/package.json b/app/package.json
index bac2e8860..7609a7cc4 100644
--- a/app/package.json
+++ b/app/package.json
@@ -13,7 +13,6 @@
"@amplitude/analytics-react-native": "^1.4.7",
"@babel/plugin-transform-private-methods": "^7.23.3",
"@ethersproject/shims": "^5.7.0",
- "@react-native-async-storage/async-storage": "^1.23.1",
"@react-native-community/clipboard": "^1.5.1",
"@react-native-community/netinfo": "^11.3.1",
"@tamagui/colors": "^1.94.3",
@@ -40,6 +39,8 @@
"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",
diff --git a/app/src/apps/sbt.tsx b/app/src/apps/sbt.tsx
index f1c19882c..cc83debdb 100644
--- a/app/src/apps/sbt.tsx
+++ b/app/src/apps/sbt.tsx
@@ -128,17 +128,8 @@ export const sbtApp: AppType = {
{ developmentMode: false }
);
- // remove that when it's only disclosure proof
- 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]);
- }
- });
-
+ console.log('inputs:', inputs);
+
const start = Date.now();
const proof = await generateProof(
diff --git a/app/src/screens/MainScreen.tsx b/app/src/screens/MainScreen.tsx
index 2a6b03579..0dae8a908 100644
--- a/app/src/screens/MainScreen.tsx
+++ b/app/src/screens/MainScreen.tsx
@@ -31,7 +31,9 @@ const MainScreen: React.FC = () => {
dateOfBirth,
dateOfExpiry,
deleteMrzFields,
- update
+ update,
+ clearPassportDataFromStorage,
+ clearSecretFromStorage,
} = useUserStore()
const {
@@ -73,6 +75,7 @@ const MainScreen: React.FC = () => {
scan();
}
}
+
useEffect(() => {
if (passportNumber?.length === 9 && (dateOfBirth?.length === 6 && dateOfExpiry?.length === 6)) {
setStep(Steps.MRZ_SCAN_COMPLETED);
@@ -251,6 +254,25 @@ const MainScreen: React.FC = () => {
+
+
+
+
+
diff --git a/app/src/screens/ProveScreen.tsx b/app/src/screens/ProveScreen.tsx
index fea53083e..1c76e8553 100644
--- a/app/src/screens/ProveScreen.tsx
+++ b/app/src/screens/ProveScreen.tsx
@@ -40,7 +40,12 @@ const ProveScreen: React.FC = () => {
majority,
disclosure,
update
- } = useAppStore() ;
+ } = useAppStore();
+
+ const {
+ registered,
+ passportData,
+ } = useUserStore();
const handleDisclosureChange = (field: string) => {
const requiredOrOptional = selectedApp.disclosureOptions[field as keyof typeof selectedApp.disclosureOptions];
@@ -58,7 +63,6 @@ const ProveScreen: React.FC = () => {
};
const { height } = useWindowDimensions();
- const passportData = useUserStore(state => state.passportData);
useEffect(() => {
// this already checks if downloading is required
@@ -210,7 +214,14 @@ const ProveScreen: React.FC = () => {
backgroundColor={address == ethers.ZeroAddress ? "#cecece" : "#3185FC"}
alignSelf='center'
>
- {isZkeyDownloading[selectedApp.circuit] ? (
+ {!registered ? (
+
+
+
+ Registering identity...
+
+
+ ) : isZkeyDownloading[selectedApp.circuit] ? (
diff --git a/app/src/stores/userStore.ts b/app/src/stores/userStore.ts
index e56d48ee9..558fa6c97 100644
--- a/app/src/stores/userStore.ts
+++ b/app/src/stores/userStore.ts
@@ -6,12 +6,25 @@ import {
} 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
}
@@ -21,7 +34,138 @@ const useUserStore = create((set, get) => ({
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({
diff --git a/app/src/utils/nfcScanner.ts b/app/src/utils/nfcScanner.ts
index 4edcc396d..34d16abef 100644
--- a/app/src/utils/nfcScanner.ts
+++ b/app/src/utils/nfcScanner.ts
@@ -40,32 +40,34 @@ export const scan = async () => {
setStep(Steps.NFC_SCANNING);
if (Platform.OS === 'android') {
- scanAndroid(setStep, toast);
+ scanAndroid();
} else {
- scanIOS(setStep, toast);
+ scanIOS();
}
};
-const scanAndroid = async (
- setStep: (value: number) => void,
- toast: any
-) => {
- const userState = useUserStore.getState()
+const scanAndroid = async () => {
+ const {
+ passportNumber,
+ dateOfBirth,
+ dateOfExpiry
+ } = useUserStore.getState()
+ const {toast, setStep} = useNavigationStore.getState();
try {
const response = await PassportReader.scan({
- documentNumber: userState.passportNumber,
- dateOfBirth: userState.dateOfBirth,
- dateOfExpiry: userState.dateOfExpiry
+ documentNumber: passportNumber,
+ dateOfBirth: dateOfBirth,
+ dateOfExpiry: dateOfExpiry
});
console.log('scanned');
amplitude.track('NFC scan successful');
- handleResponseAndroid(response, 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",
@@ -75,27 +77,29 @@ const scanAndroid = async (
}
};
-const scanIOS = async (
- setStep: (value: number) => void,
- toast: any
- ) => {
- const userState = useUserStore.getState()
+const scanIOS = async () => {
+ const {
+ passportNumber,
+ dateOfBirth,
+ dateOfExpiry
+ } = useUserStore.getState()
+ const {toast, setStep} = useNavigationStore.getState();
try {
const response = await NativeModules.PassportReader.scanPassport(
- userState.passportNumber,
- userState.dateOfBirth,
- userState.dateOfExpiry
+ passportNumber,
+ dateOfBirth,
+ dateOfExpiry
);
console.log('scanned');
- handleResponseIOS(response, 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",
@@ -107,7 +111,6 @@ const scanIOS = async (
const handleResponseIOS = async (
response: any,
- setStep: (value: number) => void,
) => {
const parsed = JSON.parse(response);
@@ -164,15 +167,12 @@ const handleResponseIOS = async (
// console.log('passportData', JSON.stringify(passportData, null, 2));
- useUserStore.setState({
- passportData
- })
- setStep(Steps.NFC_SCAN_COMPLETED);
+ useUserStore.getState().registerPassportData(passportData)
+ useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED);
};
const handleResponseAndroid = async (
response: any,
- setStep: (value: number) => void,
) => {
const {
mrz,
@@ -221,9 +221,6 @@ const handleResponseAndroid = async (
console.log("unicodeVersion", unicodeVersion)
console.log("encapContent", encapContent)
- useUserStore.setState({
- passportData
- })
-
- setStep(Steps.NFC_SCAN_COMPLETED);
+ useUserStore.getState().registerPassportData(passportData)
+ useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED);
};
\ No newline at end of file
diff --git a/app/yarn.lock b/app/yarn.lock
index b15c24689..bddaece54 100644
--- a/app/yarn.lock
+++ b/app/yarn.lock
@@ -1702,7 +1702,7 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
-"@react-native-async-storage/async-storage@^1.17.11", "@react-native-async-storage/async-storage@^1.23.1":
+"@react-native-async-storage/async-storage@^1.17.11":
version "1.23.1"
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz#cad3cd4fab7dacfe9838dce6ecb352f79150c883"
integrity sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA==
@@ -4817,6 +4817,11 @@ express@^4.18.2:
utils-merge "1.0.1"
vary "~1.1.2"
+fast-base64-decode@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418"
+ integrity sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -7597,6 +7602,18 @@ react-native-fs@^2.20.0:
base-64 "^0.1.0"
utf8 "^3.0.0"
+react-native-get-random-values@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz#1ca70d1271f4b08af92958803b89dccbda78728d"
+ integrity sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==
+ dependencies:
+ fast-base64-decode "^1.0.0"
+
+react-native-keychain@^8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/react-native-keychain/-/react-native-keychain-8.2.0.tgz#aea82df37aacbb04f8b567a8e0e6d7292025610a"
+ integrity sha512-SkRtd9McIl1Ss2XSWNLorG+KMEbgeVqX+gV+t3u1EAAqT8q2/OpRmRbxpneT2vnb/dMhiU7g6K/pf3nxLUXRvA==
+
react-native-passport-reader@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/react-native-passport-reader/-/react-native-passport-reader-1.0.3.tgz#3242bbdb3c1ade4c050a8632cca6f11fe0edc648"