First lint and fmt pass

This commit is a first pass of lint and fmt, with a few manual fixes. Only manual issues left to do.
This commit is contained in:
thomas-senechal
2025-01-05 23:36:28 +01:00
parent d03c193810
commit 126711d326
45 changed files with 2675 additions and 1620 deletions

View File

@@ -1,5 +1,3 @@
{
"plugins": [
["module:react-native-dotenv"]
]
}
"plugins": [["module:react-native-dotenv"]]
}

View File

@@ -1,3 +1 @@
nodeLinker: node-modules
# yarnPath: .yarn/releases/yarn-4.5.0.cjs

View File

@@ -1,23 +1,27 @@
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 useNavigationStore from './src/stores/navigationStore';
import { AMPLITUDE_KEY } from '@env';
import * as amplitude from '@amplitude/analytics-react-native';
import { AMPLITUDE_KEY } from '@env';
import '@ethersproject/shims';
import { Buffer } from 'buffer';
import 'react-native-get-random-values';
import { useToastController } from '@tamagui/toast';
import { YStack } from 'tamagui';
import MainScreen from './src/screens/MainScreen';
import useNavigationStore from './src/stores/navigationStore';
import useUserStore from './src/stores/userStore';
import { bgWhite } from './src/utils/colors';
import { setupUniversalLinkListener } from './src/utils/qrCode'; // Adjust the import path as needed
global.Buffer = Buffer;
function App(): React.JSX.Element {
const toast = useToastController();
const setToast = useNavigationStore((state) => state.setToast);
const initUserStore = useUserStore((state) => state.initUserStore);
const setSelectedTab = useNavigationStore((state) => state.setSelectedTab);
const setToast = useNavigationStore(state => state.setToast);
const initUserStore = useUserStore(state => state.initUserStore);
const setSelectedTab = useNavigationStore(state => state.setSelectedTab);
useEffect(() => {
initUserStore();

View File

@@ -2,28 +2,28 @@
## Requirements
| Requirement | Version | Installation Guide |
|-------------|---------|--------------------|
| nodejs | > v18 | [Install nodejs](https://nodejs.org/) |
| circom | Latest | [Install circom](https://docs.circom.io/) |
| snarkjs | Latest | [Install snarkjs](https://github.com/iden3/snarkjs) |
| watchman | Latest | [Install watchman](https://facebook.github.io/watchman/) |
| Requirement | Version | Installation Guide |
| ----------- | ------- | -------------------------------------------------------- |
| nodejs | > v18 | [Install nodejs](https://nodejs.org/) |
| circom | Latest | [Install circom](https://docs.circom.io/) |
| snarkjs | Latest | [Install snarkjs](https://github.com/iden3/snarkjs) |
| watchman | Latest | [Install watchman](https://facebook.github.io/watchman/) |
### Android
| Requirement | Version | Installation Guide |
|-------------|---------|--------------------|
| Java | 17 | [Install Java](https://www.oracle.com/java/technologies/javase-jdk17-downloads.html)|
| Android Studio | Latest | [Install Android Studio](https://developer.android.com/studio) |
| Android SDK | Latest | [Install Android SDK](https://developer.android.com/studio#downloads) |
| Android Ndk | 23.1.7779620 | [Install NDK](https://developer.android.com/studio) or [GPT4 guide](https://chatgpt.com/share/a6e2544b-d32a-4554-a452-402511d03ffc) |
| Requirement | Version | Installation Guide |
| -------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| Java | 17 | [Install Java](https://www.oracle.com/java/technologies/javase-jdk17-downloads.html) |
| Android Studio | Latest | [Install Android Studio](https://developer.android.com/studio) |
| Android SDK | Latest | [Install Android SDK](https://developer.android.com/studio#downloads) |
| Android Ndk | 23.1.7779620 | [Install NDK](https://developer.android.com/studio) or [GPT4 guide](https://chatgpt.com/share/a6e2544b-d32a-4554-a452-402511d03ffc) |
### iOS
| Requirement | Version | Installation Guide |
|-------------|---------|--------------------|
| Xcode | Latest | [Install Xcode](https://developer.apple.com/xcode/) |
| cocoapods | Latest | [Install cocoapods](https://cocoapods.org/) |
| Requirement | Version | Installation Guide |
| ----------- | ------- | --------------------------------------------------- |
| Xcode | Latest | [Install Xcode](https://developer.apple.com/xcode/) |
| cocoapods | Latest | [Install cocoapods](https://cocoapods.org/) |
## Installation
@@ -38,11 +38,13 @@ First, connect your phone to your computer and allow access.
### Android
Create the file `android/local.properties` with the following content:
```
sdk.dir=/Users/<your-user-name>/Library/Android/sdk
```
Launch the react-native server:
```
yarn start
```
@@ -61,6 +63,7 @@ To see the Android logs you'll have to use the Android Studio Logcat.
Open the ios project on Xcode and add your provisionning profile in Targets > OpenPassport > Signing and Capabilities
Then, install pods:
```
cd ios
pod install
@@ -82,6 +85,7 @@ Adapt the input generation in `common/src/utils/generateInputs.ts`, and adapt an
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
@@ -90,6 +94,7 @@ export ANDROID_NDK="<your-android-ndk-path>"
### iOS
Find your [development team id](https://chat.openai.com/share/9d52c37f-d9da-4a62-acb9-9e4ee8179f95) and run:
```
export DEVELOPMENT_TEAM="<your-development-team-id>"
./scripts/build_ios_module.sh
@@ -105,16 +110,21 @@ export DEVELOPMENT_TEAM="<your-development-team-id>"
cd android
./gradlew assembleRelease
```
The built apk it located at `android/app/build/outputs/apk/release/app-release.apk`
#### Publish on the Play Store
As explained [here](https://reactnative.dev/docs/signed-apk-android), first setup `android/app/my-upload-key.keystore` and the private vars in `~/.gradle/gradle.properties`, then run:
```
npx react-native build-android --mode=release
```
This builds `android/app/build/outputs/bundle/release/app-release.aab`.
Then to test the release on an android phone, delete the previous version of the app and run:
```
yarn android --mode release
```
@@ -130,10 +140,13 @@ Don't forget to bump the build number.
## FAQ
If you get something like this:
```
'std::__1::system_error: open: /openpassport/app: Operation not permitted'
```
You might want to try [this](https://stackoverflow.com/questions/49443341/watchman-crawl-failed-retrying-once-with-node-crawler):
```
watchman watch-del-all
watchman shutdown-server

View File

@@ -1,14 +1,17 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
["@babel/plugin-transform-private-methods", { "loose": true }],
["module:react-native-dotenv", {
"moduleName": "@env",
"path": ".env",
"blacklist": null,
"whitelist": null,
"safe": false,
"allowUndefined": true
}]
['@babel/plugin-transform-private-methods', { loose: true }],
[
'module:react-native-dotenv',
{
moduleName: '@env',
path: '.env',
blacklist: null,
whitelist: null,
safe: false,
allowUndefined: true,
},
],
],
};

10
app/declarations.d.ts vendored
View File

@@ -1,9 +1,9 @@
declare module '@env';
declare module '*.png' {
const value: string;
export = value;
const value: string;
export = value;
}
declare module '*.jpeg' {
const value: string;
export = value;
}
const value: string;
export = value;
}

View File

@@ -2,19 +2,23 @@
* @format
*/
import React from 'react';
import { config } from '@tamagui/config/v2-native';
import { ToastProvider } from '@tamagui/toast';
import { AppRegistry, LogBox } from 'react-native';
import { TamaguiProvider, createTamagui } from 'tamagui';
import App from './App';
import { name as appName } from './app.json';
import { TamaguiProvider, createTamagui } from 'tamagui';
import { config } from '@tamagui/config/v2-native'
import { ToastProvider } from '@tamagui/toast';
const tamaguiConfig = createTamagui(config)
const tamaguiConfig = createTamagui(config);
LogBox.ignoreLogs([
/bad setState/,
'Warning, duplicate ID for input',
/Warning, duplicate ID for input/
])
/Warning, duplicate ID for input/,
]);
const Root = () => (
<TamaguiProvider config={tamaguiConfig}>
@@ -22,7 +26,6 @@ const Root = () => (
<App />
</ToastProvider>
</TamaguiProvider>
);
AppRegistry.registerComponent(appName, () => Root);

View File

@@ -2,19 +2,23 @@
* @format
*/
import React from 'react';
import { config } from '@tamagui/config/v2-native';
import { ToastProvider } from '@tamagui/toast';
import { AppRegistry, LogBox } from 'react-native';
import { TamaguiProvider, createTamagui } from 'tamagui';
import App from './App';
import { name as appName } from './app.json';
import { TamaguiProvider, createTamagui } from 'tamagui';
import { config } from '@tamagui/config/v2-native'
import { ToastProvider } from '@tamagui/toast';
const tamaguiConfig = createTamagui(config)
const tamaguiConfig = createTamagui(config);
LogBox.ignoreLogs([
/bad setState/,
'Warning, duplicate ID for input',
/Warning, duplicate ID for input/
])
/Warning, duplicate ID for input/,
]);
const Root = () => (
<TamaguiProvider config={tamaguiConfig}>
@@ -22,7 +26,6 @@ const Root = () => (
<App />
</ToastProvider>
</TamaguiProvider>
);
AppRegistry.registerComponent(appName, () => Root);

View File

@@ -1,11 +1,11 @@
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('path');
const commonPath = path.join(__dirname, '/../common');
const extraNodeModules = {
'common': path.resolve(__dirname + '/../common'),
common: path.resolve(commonPath),
};
const watchFolders = [
path.resolve(__dirname + '/../common')
];
const watchFolders = [path.resolve(commonPath)];
/**
* Metro configuration

View File

@@ -1,11 +1,11 @@
module.exports = {
project: {
ios: {},
android: {},
},
assets: [
'./assets/fonts',
'./node_modules/@tamagui/font-inter/otf',
'./node_modules/@tamagui/font-silkscreen/files',
],
};
project: {
ios: {},
android: {},
},
assets: [
'./assets/fonts',
'./node_modules/@tamagui/font-inter/otf',
'./node_modules/@tamagui/font-silkscreen/files',
],
};

View File

@@ -1,112 +1,160 @@
import React, { useState } from 'react'
import { AnimatePresence } from '@tamagui/animate-presence'
import { ArrowLeft, Nfc } from '@tamagui/lucide-icons'
import { Button, Image, XStack, YStack, styled, Text } from 'tamagui'
import { bgGreen, textBlack } from '../utils/colors'
import CustomButton from './CustomButton'
import React, { useState } from 'react';
import { AnimatePresence } from '@tamagui/animate-presence';
import { ArrowLeft, Nfc } from '@tamagui/lucide-icons';
import { Button, Image, styled, Text, XStack, YStack } from 'tamagui';
import { bgGreen, textBlack } from '../utils/colors';
import CustomButton from './CustomButton';
const GalleryItem = styled(YStack, {
zIndex: 1,
x: 0,
opacity: 1,
fullscreen: true,
zIndex: 1,
x: 0,
opacity: 1,
fullscreen: true,
variants: {
going: {
':number': (going) => ({
enterStyle: {
x: going > 0 ? 1000 : -1000,
opacity: 0,
},
exitStyle: {
zIndex: 0,
x: going < 0 ? 1000 : -1000,
opacity: 0,
},
}),
variants: {
going: {
':number': going => ({
enterStyle: {
x: going > 0 ? 1000 : -1000,
opacity: 0,
},
} as const,
})
exitStyle: {
zIndex: 0,
x: going < 0 ? 1000 : -1000,
opacity: 0,
},
}),
},
} as const,
});
const wrap = (min: number, max: number, v: number) => {
const rangeSize = max - min
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min
}
const rangeSize = max - min;
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
};
interface CarouselProps {
images: string[];
height?: number;
handleNfcScan?: () => void;
images: string[];
height?: number;
handleNfcScan?: () => void;
}
export function Carousel({ images, height = 300, handleNfcScan }: CarouselProps) {
const [[page, going], setPage] = useState([0, 0])
export function Carousel({
images,
height = 300,
handleNfcScan,
}: CarouselProps) {
const [[page, going], setPage] = useState([0, 0]);
const imageIndex = wrap(0, images.length, page)
const paginate = (going: number) => {
const newPage = page + going
setPage([newPage, going])
}
const imageIndex = wrap(0, images.length, page);
const paginate = (direction: number) => {
const newPage = page + direction;
setPage([newPage, direction]);
};
const isLastImage = imageIndex === images.length - 1
const slideTexts = [
{ header: "Follow this guide carefully", subtitle: "", acknowledgment: "I'm ready to start" },
{ header: "Open your passport to the last page", subtitle: "", acknowledgment: "I have opened my passport to the last page" },
{ header: "Put your phone on the passport", subtitle: "Press your phone against the last page of the passport as in the image.", acknowledgment: "I have placed my phone on the passport" },
{ header: "Start scanning", subtitle: "Press Start NFC Scan and follow the on-screen instructions.", acknowledgment: "Start scanning" },
]
const isLastImage = imageIndex === images.length - 1;
const slideTexts = [
{
header: 'Follow this guide carefully',
subtitle: '',
acknowledgment: "I'm ready to start",
},
{
header: 'Open your passport to the last page',
subtitle: '',
acknowledgment: 'I have opened my passport to the last page',
},
{
header: 'Put your phone on the passport',
subtitle:
'Press your phone against the last page of the passport as in the image.',
acknowledgment: 'I have placed my phone on the passport',
},
{
header: 'Start scanning',
subtitle: 'Press Start NFC Scan and follow the on-screen instructions.',
acknowledgment: 'Start scanning',
},
];
const currentSlide = slideTexts[imageIndex] || { header: "No header", subtitle: "No subtitle for this slide", acknowledgment: "Continue" }
const currentSlide = slideTexts[imageIndex] || {
header: 'No header',
subtitle: 'No subtitle for this slide',
acknowledgment: 'Continue',
};
return (
<YStack f={1}>
<YStack f={1} jc='space-evenly'>
return (
<YStack f={1}>
<YStack f={1} jc="space-evenly">
<Text textAlign="center" fontSize="$9" color={textBlack}>
Verify your passport using{' '}
<Text
color={textBlack}
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
NFC
</Text>
</Text>
<Text textAlign='center' fontSize="$9" color={textBlack} >Verify your passport using <Text color={textBlack} style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>NFC</Text></Text>
<XStack
overflow="hidden"
backgroundColor="#000"
position="relative"
height={height}
alignItems="center"
borderRadius="$10"
>
<AnimatePresence initial={false} custom={{ going }}>
<GalleryItem key={page} animation="medium" going={going}>
<Image
source={{ uri: images[imageIndex] }}
height={height}
resizeMode="contain"
/>
</GalleryItem>
</AnimatePresence>
<XStack
overflow="hidden"
backgroundColor="#000"
position="relative"
height={height}
alignItems="center"
borderRadius="$10"
>
<AnimatePresence initial={false} custom={{ going }}>
<GalleryItem key={page} animation="medium" going={going}>
<Image source={{ uri: images[imageIndex] }} height={height} resizeMode="contain" />
</GalleryItem>
</AnimatePresence>
{imageIndex > 0 && (
<Button
icon={ArrowLeft}
size="$5"
position="absolute"
left="$4"
circular
elevate
onPress={() => paginate(-1)}
zi={100}
/>
)}
</XStack>
<YStack>
<Text fontSize="$8" color={textBlack} textAlign='center'>{currentSlide.header}</Text>
<Text color={textBlack} fontSize="$5" textAlign='center' style={{ opacity: 0.7 }} fontStyle='italic'>{currentSlide.subtitle}</Text>
</YStack>
</YStack>
<CustomButton
onPress={isLastImage ? () => handleNfcScan?.() : () => paginate(+1)}
text={currentSlide.acknowledgment}
Icon={isLastImage ? <Nfc /> : undefined}
blueVariant={!isLastImage}
{imageIndex > 0 && (
<Button
icon={ArrowLeft}
size="$5"
position="absolute"
left="$4"
circular
elevate
onPress={() => paginate(-1)}
zi={100}
/>
)}
</XStack>
<YStack>
<Text fontSize="$8" color={textBlack} textAlign="center">
{currentSlide.header}
</Text>
<Text
color={textBlack}
fontSize="$5"
textAlign="center"
style={{ opacity: 0.7 }}
fontStyle="italic"
>
{currentSlide.subtitle}
</Text>
</YStack>
)
}
</YStack>
<CustomButton
onPress={isLastImage ? () => handleNfcScan?.() : () => paginate(+1)}
text={currentSlide.acknowledgment}
Icon={isLastImage ? <Nfc /> : undefined}
blueVariant={!isLastImage}
/>
</YStack>
);
}

View File

@@ -1,28 +1,47 @@
import React from 'react';
import { Button, Text } from 'tamagui';
import { bgBlue, bgGreen, textBlack } from '../utils/colors';
interface CustomButtonProps {
text: string;
onPress: () => void;
Icon?: React.ReactNode;
bgColor?: string;
h?: string;
isDisabled?: boolean;
disabledOnPress?: () => void;
blueVariant?: boolean;
text: string;
onPress: () => void;
Icon?: React.ReactNode;
bgColor?: string;
h?: string;
isDisabled?: boolean;
disabledOnPress?: () => void;
blueVariant?: boolean;
}
const CustomButton: React.FC<CustomButtonProps> = ({ text, onPress, Icon, bgColor, h, isDisabled, disabledOnPress, blueVariant }) => {
return (
<Button bg={bgColor ? bgColor : blueVariant ? bgBlue : bgGreen} h={blueVariant ? "$8" : "$5"} borderRadius="$10" onPress={isDisabled ? disabledOnPress : onPress}>
{Icon && <Button.Icon>{Icon}</Button.Icon>}
<Text textAlign='center' fontSize={blueVariant ? "$6" : "$5"} fontWeight="bold" color={textBlack}>
{text}
</Text>
</Button>
);
const CustomButton: React.FC<CustomButtonProps> = ({
text,
onPress,
Icon,
bgColor,
h,
isDisabled,
disabledOnPress,
blueVariant,
}) => {
return (
<Button
bg={bgColor ? bgColor : blueVariant ? bgBlue : bgGreen}
h={blueVariant ? '$8' : '$5'}
borderRadius="$10"
onPress={isDisabled ? disabledOnPress : onPress}
>
{Icon && <Button.Icon>{Icon}</Button.Icon>}
<Text
textAlign="center"
fontSize={blueVariant ? '$6' : '$5'}
fontWeight="bold"
color={textBlack}
>
{text}
</Text>
</Button>
);
};
export default CustomButton;
export default CustomButton;

View File

@@ -1,20 +1,33 @@
import { XStack } from "tamagui";
import { textBlack } from "../utils/colors";
import { XStack } from 'tamagui';
import { textBlack } from '../utils/colors';
interface StepOneStepTwoProps {
variable: string;
step1: string;
step2: string;
variable: string;
step1: string;
step2: string;
}
const StepOneStepTwo = ({ variable, step1, step2 }: StepOneStepTwoProps) => {
const isVisible = variable === step1 || variable === step2;
const isVisible = variable === step1 || variable === step2;
return (
<XStack px="$6" gap="$3" style={{ opacity: isVisible ? 1 : 0 }}>
<XStack h="$0.25" f={1} bg={textBlack} borderRadius={100} style={{ opacity: variable === step1 ? 1 : 0.2 }} />
<XStack h="$0.25" f={1} bg={textBlack} borderRadius={100} style={{ opacity: variable === step2 ? 1 : 0.2 }} />
</XStack>
)
}
return (
<XStack px="$6" gap="$3" style={{ opacity: isVisible ? 1 : 0 }}>
<XStack
h="$0.25"
f={1}
bg={textBlack}
borderRadius={100}
style={{ opacity: variable === step1 ? 1 : 0.2 }}
/>
<XStack
h="$0.25"
f={1}
bg={textBlack}
borderRadius={100}
style={{ opacity: variable === step2 ? 1 : 0.2 }}
/>
</XStack>
);
};
export default StepOneStepTwo;

View File

@@ -1,16 +1,19 @@
import { Toast, useToastState } from '@tamagui/toast';
import { YStack } from '@tamagui/stacks';
import { Toast, useToastState } from '@tamagui/toast';
import {
blueColorLight,
greenColorLight,
redColorLight,
blueColorLight,
textColor1,
} from '../utils/colors';
export const ToastMessage = () => {
const toast = useToastState();
if (!toast || toast.isHandledNatively) return null;
if (!toast || toast.isHandledNatively) {
return null;
}
return (
<Toast
@@ -18,8 +21,8 @@ export const ToastMessage = () => {
toast.customData?.type === 'success'
? greenColorLight
: toast.customData?.type === 'error'
? redColorLight
: blueColorLight
? redColorLight
: blueColorLight
}
animation="100ms"
enterStyle={{ y: -20, opacity: 0 }}
@@ -30,7 +33,9 @@ export const ToastMessage = () => {
duration={3000}
>
<YStack ai="center" jc="center">
<Toast.Title fow="bold" color={'white'}>{toast?.title}</Toast.Title>
<Toast.Title fow="bold" color={'white'}>
{toast?.title}
</Toast.Title>
<Toast.Description color={textColor1}>
{toast.message}
</Toast.Description>

View File

@@ -1,34 +1,46 @@
import React from 'react';
import { Text, YStack, Image } from 'tamagui';
import { XStack } from 'tamagui';
import CustomButton from '../components/CustomButton';
import { QrCode } from '@tamagui/lucide-icons';
import { textBlack } from '../utils/colors';
import useUserStore from '../stores/userStore';
import { scanQRCode } from '../utils/qrCode';
import { Image, Text, XStack, YStack } from 'tamagui';
import CustomButton from '../components/CustomButton';
import OPENPASSPORT_LOGO from '../images/openpassport.png';
import useUserStore from '../stores/userStore';
import { textBlack } from '../utils/colors';
import { scanQRCode } from '../utils/qrCode';
interface AppScreenProps {
setSheetAppListOpen: (value: boolean) => void;
setSheetRegisterIsOpen: (value: boolean) => void;
}
const AppScreen: React.FC<AppScreenProps> = ({ setSheetAppListOpen, setSheetRegisterIsOpen }) => {
const {
registered,
} = useUserStore();
const AppScreen: React.FC<AppScreenProps> = ({ setSheetRegisterIsOpen }) => {
const { registered } = useUserStore();
return (
<YStack f={1} >
<YStack f={1}>
<XStack f={1} minHeight="$1" />
<Image alignSelf='center' src={OPENPASSPORT_LOGO} width={400} height={150} />
<Text mt="$2.5" textAlign='center' fontSize="$9" fontWeight='bold' color={textBlack}>OpenPassport</Text>
<Image
alignSelf="center"
src={OPENPASSPORT_LOGO}
width={400}
height={150}
/>
<Text
mt="$2.5"
textAlign="center"
fontSize="$9"
fontWeight="bold"
color={textBlack}
>
OpenPassport
</Text>
<XStack f={1} minHeight="$1" />
<Text textAlign='center' mb="$2" fontSize="$3" color={textBlack}>To use OpenPassport, scan the QR code displayed by an app.</Text>
<Text textAlign="center" mb="$2" fontSize="$3" color={textBlack}>
To use OpenPassport, scan the QR code displayed by an app.
</Text>
<YStack gap="$2.5">
<CustomButton
text="Scan QR Code"
@@ -44,6 +56,6 @@ const AppScreen: React.FC<AppScreenProps> = ({ setSheetAppListOpen, setSheetRegi
</YStack>
</YStack>
);
}
};
export default AppScreen;
export default AppScreen;

View File

@@ -1,31 +1,64 @@
import React from 'react';
import { YStack, Text, Image } from 'tamagui';
import { Camera, SquarePen } from '@tamagui/lucide-icons';
import { bgGreen, textBlack } from '../utils/colors';
import { startCameraScan } from '../utils/cameraScanner';
import { Image, Text, YStack } from 'tamagui';
import CustomButton from '../components/CustomButton';
import PASSPORT_DRAWING from '../images/passport_drawing.png'
import PASSPORT_DRAWING from '../images/passport_drawing.png';
import { startCameraScan } from '../utils/cameraScanner';
import { bgGreen, textBlack } from '../utils/colors';
interface CameraScreenProps {
setSheetIsOpen: (value: boolean) => void
setSheetIsOpen: (value: boolean) => void;
}
const CameraScreen: React.FC<CameraScreenProps> = ({ setSheetIsOpen }) => {
return (
<YStack f={1}>
<YStack f={1} my="$6" jc="space-evenly" ai="center">
<Text textAlign='center' fontSize="$9" color={textBlack}><Text style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>Scan</Text> or type your passport ID</Text>
<Text textAlign='center' mt="$4" fontSize="$6" color={textBlack}>Open your passport on the <Text style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>main page</Text> to scan it.</Text>
<Text textAlign="center" fontSize="$9" color={textBlack}>
<Text
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
Scan
</Text>{' '}
or type your passport ID
</Text>
<Text textAlign="center" mt="$4" fontSize="$6" color={textBlack}>
Open your passport on the{' '}
<Text
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
main page
</Text>{' '}
to scan it.
</Text>
<Image src={PASSPORT_DRAWING} style={{ width: 200, height: 250 }} />
</YStack>
<Text textAlign='center' mb="$2" fontSize="$4" color={textBlack}>The application is not taking a picture, only reading some fields.</Text>
<YStack gap="$2.5" >
<CustomButton text="Open Camera" onPress={startCameraScan} Icon={<Camera color={textBlack} size={24} />} />
<CustomButton bgColor='#ffff' text="Manual Input" onPress={() => setSheetIsOpen(true)} Icon={<SquarePen color={textBlack} size={24} />} />
<Text textAlign="center" mb="$2" fontSize="$4" color={textBlack}>
The application is not taking a picture, only reading some fields.
</Text>
<YStack gap="$2.5">
<CustomButton
text="Open Camera"
onPress={startCameraScan}
Icon={<Camera color={textBlack} size={24} />}
/>
<CustomButton
bgColor="#ffff"
text="Manual Input"
onPress={() => setSheetIsOpen(true)}
Icon={<SquarePen color={textBlack} size={24} />}
/>
</YStack>
</YStack >
</YStack>
);
};
export default CameraScreen;
export default CameraScreen;

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,25 @@
import React, { useState, useCallback, useMemo, useRef } from 'react';
import { YStack, XStack, Text, Select, Adapt, Sheet, Fieldset, Button, Spinner, Switch } from 'tamagui';
import { Check, ChevronDown, ChevronUp, Cpu, Minus, Plus, X } from '@tamagui/lucide-icons';
import { bgColor, bgGreen, bgGreen2, blueColor, borderColor, greenColorLight, redColorDark, textBlack } from '../utils/colors';
import useUserStore from '../stores/userStore';
import useNavigationStore from '../stores/navigationStore';
import CustomButton from '../components/CustomButton';
import { genMockPassportData } from '../../../common/src/utils/genMockPassportData';
import { countryCodes } from '../../../common/src/constants/constants';
import getCountryISO2 from "country-iso-3-to-2";
import React, { useCallback, useState } from 'react';
import { ChevronDown, Cpu, Minus, Plus } from '@tamagui/lucide-icons';
import {
Button,
Fieldset,
Spinner,
Switch,
Text,
XStack,
YStack,
} from 'tamagui';
import { flag } from 'country-emoji';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import getCountryISO2 from 'country-iso-3-to-2';
import { countryCodes } from '../../../common/src/constants/constants';
import { genMockPassportData } from '../../../common/src/utils/genMockPassportData';
import CustomButton from '../components/CustomButton';
import useNavigationStore from '../stores/navigationStore';
import useUserStore from '../stores/userStore';
import { borderColor, textBlack } from '../utils/colors';
interface MockDataScreenProps {
onCountryPress: () => void;
@@ -31,62 +41,88 @@ const MockDataScreen: React.FC<MockDataScreenProps> = ({
const castDate = (yearsOffset: number) => {
const date = new Date();
date.setFullYear(date.getFullYear() + yearsOffset);
return (date.toISOString().slice(2, 4) + date.toISOString().slice(5, 7) + date.toISOString().slice(8, 10)).toString();
return (
date.toISOString().slice(2, 4) +
date.toISOString().slice(5, 7) +
date.toISOString().slice(8, 10)
).toString();
};
const { toast } = useNavigationStore();
const signatureAlgorithmToStrictSignatureAlgorithm = {
"rsa sha256": "rsa_sha256_65537_2048",
"rsa sha1": "rsa_sha1_65537_2048",
"rsapss sha256": "rsapss_sha256_65537_2048"
'rsa sha256': 'rsa_sha256_65537_2048',
'rsa sha1': 'rsa_sha1_65537_2048',
'rsapss sha256': 'rsapss_sha256_65537_2048',
} as const;
const handleGenerate = useCallback(async () => {
setIsGenerating(true);
const randomPassportNumber = Math.random().toString(36).substring(2, 11).replace(/[^a-z0-9]/gi, '').toUpperCase();
await new Promise(resolve => setTimeout(() => {
let mockPassportData;
if (isInOfacList) {
mockPassportData = genMockPassportData(
signatureAlgorithmToStrictSignatureAlgorithm[selectedAlgorithm as keyof typeof signatureAlgorithmToStrictSignatureAlgorithm],
selectedCountry as keyof typeof countryCodes,
castDate(-age),
castDate(expiryYears),
randomPassportNumber,
'HENAO MONTOYA', // this name is the OFAC list
'ARCANGEL DE JESUS'
);
} else {
mockPassportData = genMockPassportData(
signatureAlgorithmToStrictSignatureAlgorithm[selectedAlgorithm as keyof typeof signatureAlgorithmToStrictSignatureAlgorithm],
selectedCountry as keyof typeof countryCodes,
castDate(-age),
castDate(expiryYears),
randomPassportNumber,
);
}
useUserStore.getState().registerPassportData(mockPassportData);
useUserStore.getState().setRegistered(true);
resolve(null);
}, 0));
const randomPassportNumber = Math.random()
.toString(36)
.substring(2, 11)
.replace(/[^a-z0-9]/gi, '')
.toUpperCase();
await new Promise(resolve =>
setTimeout(() => {
let mockPassportData;
if (isInOfacList) {
mockPassportData = genMockPassportData(
signatureAlgorithmToStrictSignatureAlgorithm[
selectedAlgorithm as keyof typeof signatureAlgorithmToStrictSignatureAlgorithm
],
selectedCountry as keyof typeof countryCodes,
castDate(-age),
castDate(expiryYears),
randomPassportNumber,
'HENAO MONTOYA', // this name is the OFAC list
'ARCANGEL DE JESUS',
);
} else {
mockPassportData = genMockPassportData(
signatureAlgorithmToStrictSignatureAlgorithm[
selectedAlgorithm as keyof typeof signatureAlgorithmToStrictSignatureAlgorithm
],
selectedCountry as keyof typeof countryCodes,
castDate(-age),
castDate(expiryYears),
randomPassportNumber,
);
}
useUserStore.getState().registerPassportData(mockPassportData);
useUserStore.getState().setRegistered(true);
resolve(null);
}, 0),
);
toast.show("🤖", {
message: "Passport generated",
toast.show('🤖', {
message: 'Passport generated',
customData: {
type: "success",
type: 'success',
},
});
await new Promise(resolve => setTimeout(resolve, 1000));
useNavigationStore.getState().setSelectedTab("next");
useNavigationStore.getState().setSelectedTab('next');
}, [selectedAlgorithm, selectedCountry, age, expiryYears, isInOfacList]);
return (
<YStack f={1} gap="$4" >
<Text my="$9" textAlign="center" fontSize="$9" color={textBlack}>Generate passport data</Text>
<YStack f={1} gap="$4">
<Text my="$9" textAlign="center" fontSize="$9" color={textBlack}>
Generate passport data
</Text>
<XStack ai="center">
<Text f={1} fontSize="$5">Encryption</Text>
<Button onPress={onAlgorithmPress} p="$2" px="$3" bg="white" borderColor={borderColor} borderWidth={1} borderRadius="$4" >
<Text f={1} fontSize="$5">
Encryption
</Text>
<Button
onPress={onAlgorithmPress}
p="$2"
px="$3"
bg="white"
borderColor={borderColor}
borderWidth={1}
borderRadius="$4"
>
<XStack ai="center" gap="$2">
<Text fontSize="$4">{selectedAlgorithm}</Text>
<ChevronDown size={20} />
@@ -94,73 +130,154 @@ const MockDataScreen: React.FC<MockDataScreenProps> = ({
</Button>
</XStack>
<XStack ai="center">
<Text f={1} fontSize="$5">Nationality</Text>
<Button onPress={onCountryPress} p="$2" px="$3" bg="white" borderColor={borderColor} borderWidth={1} borderRadius="$4">
<Text f={1} fontSize="$5">
Nationality
</Text>
<Button
onPress={onCountryPress}
p="$2"
px="$3"
bg="white"
borderColor={borderColor}
borderWidth={1}
borderRadius="$4"
>
<XStack ai="center" gap="$2">
<Text fontSize="$4">{countryCodes[selectedCountry as keyof typeof countryCodes]} {flag(getCountryISO2(selectedCountry))}</Text>
<Text fontSize="$4">
{countryCodes[selectedCountry as keyof typeof countryCodes]}{' '}
{flag(getCountryISO2(selectedCountry))}
</Text>
<ChevronDown size={20} />
</XStack>
</Button>
</XStack>
<Fieldset mt="$2" gap="$2" horizontal>
<Text color={textBlack} width={160} justifyContent="flex-end" fontSize="$5">
<Text
color={textBlack}
width={160}
justifyContent="flex-end"
fontSize="$5"
>
Age (🎂)
</Text>
<XStack f={1} />
<Button h="$3.5" w="$3.5" bg="white" jc="center" borderColor={borderColor} borderWidth={1} borderRadius="$10" onPress={() => setAge(age - 1)} disabled={age <= 0}>
<Button
h="$3.5"
w="$3.5"
bg="white"
jc="center"
borderColor={borderColor}
borderWidth={1}
borderRadius="$10"
onPress={() => setAge(age - 1)}
disabled={age <= 0}
>
<Minus />
</Button>
<Text textAlign='center' w="$6" color={textBlack} fontSize="$5">
<Text textAlign="center" w="$6" color={textBlack} fontSize="$5">
{age} yo
</Text>
<Button h="$3.5" w="$3.5" bg="white" jc="center" borderColor={borderColor} borderWidth={1} borderRadius="$10" onPress={() => setAge(age + 1)}>
<Button
h="$3.5"
w="$3.5"
bg="white"
jc="center"
borderColor={borderColor}
borderWidth={1}
borderRadius="$10"
onPress={() => setAge(age + 1)}
>
<Plus />
</Button>
</Fieldset>
<Fieldset gap="$2" horizontal>
<Text color={textBlack} width={160} justifyContent="flex-end" fontSize="$5">
<Text
color={textBlack}
width={160}
justifyContent="flex-end"
fontSize="$5"
>
Passport expires in
</Text>
<XStack f={1} />
<Button h="$3.5" w="$3.5" bg="white" jc="center" borderColor={borderColor} borderWidth={1} borderRadius="$10" onPress={() => setExpiryYears(expiryYears - 1)} disabled={expiryYears <= 0}>
<Button
h="$3.5"
w="$3.5"
bg="white"
jc="center"
borderColor={borderColor}
borderWidth={1}
borderRadius="$10"
onPress={() => setExpiryYears(expiryYears - 1)}
disabled={expiryYears <= 0}
>
<Minus />
</Button>
<Text textAlign='center' w="$6" color={textBlack} fontSize="$5">
<Text textAlign="center" w="$6" color={textBlack} fontSize="$5">
{expiryYears} years
</Text>
<Button h="$3.5" w="$3.5" bg="white" jc="center" borderColor={borderColor} borderWidth={1} borderRadius="$10" onPress={() => setExpiryYears(expiryYears + 1)}>
<Button
h="$3.5"
w="$3.5"
bg="white"
jc="center"
borderColor={borderColor}
borderWidth={1}
borderRadius="$10"
onPress={() => setExpiryYears(expiryYears + 1)}
>
<Plus />
</Button>
</Fieldset>
<YStack >
<YStack>
<Fieldset mt="$2" gap="$2" horizontal>
<Text color={textBlack} width={160} justifyContent="flex-end" fontSize="$5">
<Text
color={textBlack}
width={160}
justifyContent="flex-end"
fontSize="$5"
>
Is in OFAC list
</Text>
<XStack f={1} />
<Switch size="$3.5" checked={isInOfacList} onCheckedChange={() => setIsInOfacList(!isInOfacList)} bg={isInOfacList ? "$green7Light" : "$gray4"}>
<Switch
size="$3.5"
checked={isInOfacList}
onCheckedChange={() => setIsInOfacList(!isInOfacList)}
bg={isInOfacList ? '$green7Light' : '$gray4'}
>
<Switch.Thumb animation="quick" bc="white" />
</Switch>
</Fieldset>
<Text mt="$2" color="$red10" justifyContent="flex-end" fontSize="$3" style={{ opacity: isInOfacList ? 1 : 0 }}>
OFAC list is a list of people who are suspected of being involved in terrorism or other illegal activities.
<Text
mt="$2"
color="$red10"
justifyContent="flex-end"
fontSize="$3"
style={{ opacity: isInOfacList ? 1 : 0 }}
>
OFAC list is a list of people who are suspected of being involved in
terrorism or other illegal activities.
</Text>
</YStack>
<YStack f={1} />
<YStack >
<YStack>
<Text mb="$2" textAlign="center" fontSize="$4" color={textBlack}>
These passport data are only for testing purposes.
</Text>
<CustomButton onPress={handleGenerate} text="Generate passport data" Icon={isGenerating ? <Spinner /> : <Cpu color={textBlack} />} isDisabled={isGenerating} />
<CustomButton
onPress={handleGenerate}
text="Generate passport data"
Icon={isGenerating ? <Spinner /> : <Cpu color={textBlack} />}
isDisabled={isGenerating}
/>
</YStack>
</YStack>
);

View File

@@ -1,98 +1,108 @@
import React from 'react';
import { YStack, XStack, Text, Image, useWindowDimensions, Fieldset } from 'tamagui';
import { ArrowRight, Info } from '@tamagui/lucide-icons';
import { getFirstName, maskString } from '../utils/utils';
import { ArrowRight } from '@tamagui/lucide-icons';
import { Fieldset, Image, Text, useWindowDimensions, YStack } from 'tamagui';
import { attributeToPosition } from '../../../common/src/constants/constants';
import USER_PROFILE from '../images/user_profile.png'
import { bgGreen, borderColor, componentBgColor, textBlack, textColor1, textColor2 } from '../utils/colors';
import { formatAttribute } from '../utils/utils';
import useUserStore from '../stores/userStore';
import useNavigationStore from '../stores/navigationStore';
import CustomButton from '../components/CustomButton';
import USER_PROFILE from '../images/user_profile.png';
import useNavigationStore from '../stores/navigationStore';
import useUserStore from '../stores/userStore';
import { bgGreen, textBlack } from '../utils/colors';
import { formatAttribute, getFirstName, maskString } from '../utils/utils';
const NextScreen: React.FC = () => {
const { height } = useWindowDimensions();
const handleNext = () => {
setRegistered(true);
setSelectedTab("app");
}
const {
hideData,
setSelectedTab
setSelectedTab('app');
};
const { hideData, setSelectedTab } = useNavigationStore();
} = useNavigationStore()
const {
passportData,
setRegistered
} = useUserStore();
const { passportData, setRegistered } = useUserStore();
const disclosureOptions: any = {
gender: "optional",
nationality: "optional",
expiry_date: "optional",
date_of_birth: "optional",
gender: 'optional',
nationality: 'optional',
expiry_date: 'optional',
date_of_birth: 'optional',
};
return (
<YStack f={1}>
<YStack alignSelf='center' my="$3">
{hideData
? <Image
<YStack alignSelf="center" my="$3">
{hideData ? (
<Image
w={height > 750 ? 150 : 100}
h={height > 750 ? 190 : 80}
borderRadius={height > 800 ? "$7" : "$6"}
borderRadius={height > 800 ? '$7' : '$6'}
source={{
uri: USER_PROFILE,
}}
/>
: <Image
) : (
<Image
w={height > 750 ? 190 : 130}
h={height > 750 ? 190 : 130}
borderRadius={height > 750 ? "$7" : "$6"}
borderRadius={height > 750 ? '$7' : '$6'}
source={{
uri: passportData.mockUser ? USER_PROFILE : passportData.photoBase64 ?? USER_PROFILE,
uri: passportData.mockUser
? USER_PROFILE
: passportData.photoBase64 ?? USER_PROFILE,
}}
/>
}
)}
</YStack>
<Text color={textBlack} fontSize="$9" mt="$8" >
Hi{" "}
<Text color={textBlack} fontSize="$9" style={{
textDecorationLine: "underline", textDecorationColor: bgGreen
}}>{
hideData
? maskString(getFirstName(passportData.mrz))
: getFirstName(passportData.mrz)
}</Text>
<Text color={textBlack} fontSize="$9" mt="$8">
Hi{' '}
<Text
color={textBlack}
fontSize="$9"
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
{hideData
? maskString(getFirstName(passportData.mrz))
: getFirstName(passportData.mrz)}
</Text>
</Text>
<YStack gap="$2.5" mt="$6">
{Object.keys(disclosureOptions).map((key) => {
{Object.keys(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 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 (
<Fieldset horizontal key={key} gap="$3" alignItems='center'>
<Text color={textBlack} w="$14" justifyContent="flex-end" fontSize="$6" style={{
opacity: 0.7
}}>
{keyFormatted}:
</Text>
<Fieldset horizontal key={key} gap="$3" alignItems="center">
<Text
color={textBlack}
w="$14"
justifyContent="flex-end"
fontSize="$6"
style={{
opacity: 0.7,
}}
>
{hideData ? maskString(mrzAttributeFormatted) : mrzAttributeFormatted}
{keyFormatted}:
</Text>
<Text color={textBlack} fontSize="$6">
{hideData
? maskString(mrzAttributeFormatted)
: mrzAttributeFormatted}
</Text>
</Fieldset>
);
})}
@@ -100,12 +110,15 @@ const NextScreen: React.FC = () => {
<YStack f={1} />
<YStack f={1} />
<CustomButton onPress={handleNext} text="Next" Icon={<ArrowRight color={textBlack} />} />
</YStack >
<CustomButton
onPress={handleNext}
text="Next"
Icon={<ArrowRight color={textBlack} />}
/>
</YStack>
);
};
export default NextScreen;
export default NextScreen;

View File

@@ -1,15 +1,18 @@
import React, { useState } from 'react';
import { YStack, XStack, ScrollView } from 'tamagui';
import { Carousel } from '../components/Carousel';
import US_PASSPORT from '../images/us_passport.jpeg'
import US_PASSPORT_LASTPAGE from '../images/passport_lastpage_graybg.jpeg'
import US_PASSPORT_LASTPAGE_IOS from '../images/passport_lastpage_iphone.jpeg'
import US_PASSPORT_LASTPAGE_ANDROID from '../images/passport_lastpage_android.jpeg'
import PHONE_SCANBUTTON from "../images/phone_scanbutton.jpeg"
import Dialog from "react-native-dialog";
import { Linking, Platform } from 'react-native';
import Dialog from 'react-native-dialog';
import NfcManager from 'react-native-nfc-manager';
import { Platform, Linking } from 'react-native';
import { ScrollView, XStack, YStack } from 'tamagui';
import { Carousel } from '../components/Carousel';
import US_PASSPORT_LASTPAGE_ANDROID from '../images/passport_lastpage_android.jpeg';
import US_PASSPORT_LASTPAGE from '../images/passport_lastpage_graybg.jpeg';
import US_PASSPORT_LASTPAGE_IOS from '../images/passport_lastpage_iphone.jpeg';
import PHONE_SCANBUTTON from '../images/phone_scanbutton.jpeg';
import US_PASSPORT from '../images/us_passport.jpeg';
interface NfcScreenProps {
handleNFCScan: () => void;
@@ -19,7 +22,14 @@ const NfcScreen: React.FC<NfcScreenProps> = ({ handleNFCScan }) => {
const [dialogVisible, setDialogVisible] = useState(false);
const [dialogMessage, setDialogMessage] = useState('');
const [isNfcSupported, setIsNfcSupported] = useState(true);
const carouselImages = [US_PASSPORT, US_PASSPORT_LASTPAGE, Platform.OS === 'ios' ? US_PASSPORT_LASTPAGE_IOS : US_PASSPORT_LASTPAGE_ANDROID, PHONE_SCANBUTTON,];
const carouselImages = [
US_PASSPORT,
US_PASSPORT_LASTPAGE,
Platform.OS === 'ios'
? US_PASSPORT_LASTPAGE_IOS
: US_PASSPORT_LASTPAGE_ANDROID,
PHONE_SCANBUTTON,
];
const openNfcSettings = () => {
if (Platform.OS === 'ios') {
@@ -35,14 +45,18 @@ const NfcScreen: React.FC<NfcScreenProps> = ({ handleNFCScan }) => {
if (isSupported) {
const isEnabled = await NfcManager.isEnabled();
if (!isEnabled) {
setDialogMessage('NFC is not enabled. Would you like to enable it in settings?');
setDialogMessage(
'NFC is not enabled. Would you like to enable it in settings?',
);
setDialogVisible(true);
setIsNfcSupported(true);
return false;
}
return true;
} else {
setDialogMessage("Sorry, your device doesn't seem to have an NFC reader.");
setDialogMessage(
"Sorry, your device doesn't seem to have an NFC reader.",
);
setDialogVisible(true);
setIsNfcSupported(false);
return false;
@@ -58,7 +72,7 @@ const NfcScreen: React.FC<NfcScreenProps> = ({ handleNFCScan }) => {
return (
<ScrollView flex={1} contentContainerStyle={{ flexGrow: 1 }}>
<YStack f={1} >
<YStack f={1}>
<Carousel
images={carouselImages}
height={300}
@@ -66,9 +80,7 @@ const NfcScreen: React.FC<NfcScreenProps> = ({ handleNFCScan }) => {
/>
<Dialog.Container visible={dialogVisible}>
<Dialog.Title>NFC Status</Dialog.Title>
<Dialog.Description>
{dialogMessage}
</Dialog.Description>
<Dialog.Description>{dialogMessage}</Dialog.Description>
{isNfcSupported ? (
<XStack>
<XStack f={1} />
@@ -83,4 +95,4 @@ const NfcScreen: React.FC<NfcScreenProps> = ({ handleNFCScan }) => {
);
};
export default NfcScreen;
export default NfcScreen;

View File

@@ -1,56 +1,84 @@
import React, { useState, useEffect } from 'react';
import { YStack, XStack, Text, Spinner, Progress } from 'tamagui';
import { CheckCircle } from '@tamagui/lucide-icons';
import { DEVELOPMENT_MODE, max_cert_bytes, } from '../../../common/src/constants/constants';
import { bgGreen, greenColorLight, separatorColor, textBlack } from '../utils/colors';
import useUserStore from '../stores/userStore';
import useNavigationStore from '../stores/navigationStore';
import { DisclosureOptions, OpenPassportApp } from '../../../common/src/utils/appType';
import CustomButton from '../components/CustomButton';
import { generateProof } from '../utils/prover';
import React, { useEffect, useState } from 'react';
import io, { Socket } from 'socket.io-client';
import { getCircuitNameOld, parseCertificateSimple } from '../../../common/src/utils/certificate_parsing/parseCertificateSimple';
import { CircuitName } from '../utils/zkeyDownload';
import { generateCircuitInputsInApp } from '../utils/generateInputsInApp';
import { Progress, Spinner, Text, XStack, YStack } from 'tamagui';
import {
DEVELOPMENT_MODE,
max_cert_bytes,
} from '../../../common/src/constants/constants';
import {
DisclosureOptions,
OpenPassportApp,
} from '../../../common/src/utils/appType';
import {
getCircuitNameOld,
parseCertificateSimple,
} from '../../../common/src/utils/certificate_parsing/parseCertificateSimple';
import {
generateCircuitInputsDSC,
getCSCAFromSKI,
sendCSCARequest,
} from '../../../common/src/utils/csca';
import { buildAttestation } from '../../../common/src/utils/openPassportAttestation';
import { generateCircuitInputsDSC, getCSCAFromSKI } from '../../../common/src/utils/csca';
import { sendCSCARequest } from '../../../common/src/utils/csca';
import { parsePassportData } from '../../../common/src/utils/parsePassportData';
import CustomButton from '../components/CustomButton';
import useNavigationStore from '../stores/navigationStore';
import useUserStore from '../stores/userStore';
import {
bgGreen,
greenColorLight,
separatorColor,
textBlack,
} from '../utils/colors';
import { generateCircuitInputsInApp } from '../utils/generateInputsInApp';
import { generateProof } from '../utils/prover';
import { CircuitName } from '../utils/zkeyDownload';
interface ProveScreenProps {
setSheetRegisterIsOpen: (value: boolean) => void;
}
const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) => {
const ProveScreen: React.FC<ProveScreenProps> = ({
setSheetRegisterIsOpen,
}) => {
const [generatingProof, setGeneratingProof] = useState(false);
const selectedApp = useNavigationStore(state => state.selectedApp) as OpenPassportApp;
const disclosureOptions = selectedApp.mode === 'register' ? {} : (selectedApp.args as any).disclosureOptions || {};
const {
toast,
setSelectedTab,
isZkeyDownloading,
zkeyDownloadedPercentage
} = useNavigationStore()
const selectedApp = useNavigationStore(
state => state.selectedApp,
) as OpenPassportApp;
const disclosureOptions =
selectedApp.mode === 'register'
? {}
: (selectedApp.args as any).disclosureOptions || {};
const { toast, setSelectedTab, isZkeyDownloading, zkeyDownloadedPercentage } =
useNavigationStore();
const {
setProofVerificationResult,
registered,
passportData,
} = useUserStore()
const { setProofVerificationResult, registered, passportData } =
useUserStore();
if (!passportData) {
return <Text mt="$10" fontSize="$9" color={textBlack} textAlign='center' >No passport data</Text>;
return (
<Text mt="$10" fontSize="$9" color={textBlack} textAlign="center">
No passport data
</Text>
);
}
const [socket, setSocket] = useState<Socket | null>(null);
const [isConnecting, setIsConnecting] = useState(false);
const { signatureAlgorithm, authorityKeyIdentifier } = parseCertificateSimple(passportData.dsc);
const { signatureAlgorithm, authorityKeyIdentifier } = parseCertificateSimple(
passportData.dsc,
);
const parsedPassportData = parsePassportData(passportData);
const { secret, dscSecret } = useUserStore.getState();
const circuitName = getCircuitNameOld(selectedApp.mode, signatureAlgorithm, parsedPassportData.signedAttrHashFunction);
const circuitName = getCircuitNameOld(
selectedApp.mode,
signatureAlgorithm,
parsedPassportData.signedAttrHashFunction,
);
const waitForSocketConnection = (socket: Socket): Promise<void> => {
return new Promise((resolve) => {
return new Promise(resolve => {
if (socket.connected) {
resolve();
} else {
@@ -68,7 +96,7 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
newSocket = io(selectedApp.websocketUrl, {
path: '/websocket',
transports: ['websocket'],
query: { sessionId: selectedApp.sessionId, clientType: 'mobile' }
query: { sessionId: selectedApp.sessionId, clientType: 'mobile' },
});
newSocket.on('connect', () => {
@@ -79,50 +107,49 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
console.log('Disconnected from WebSocket server');
});
newSocket.on('connect_error', (error) => {
newSocket.on('connect_error', error => {
console.error('Connection error:', error);
toast.show("Error", {
message: "Failed to connect to WebSocket server",
toast.show('Error', {
message: 'Failed to connect to WebSocket server',
customData: {
type: "error",
type: 'error',
},
});
});
newSocket.on('proof_verification_result', (result) => {
newSocket.on('proof_verification_result', result => {
setProofVerificationResult(JSON.parse(result));
console.log("result", result);
console.log('result', result);
if (JSON.parse(result).valid) {
toast.show("✅", {
message: "Identity verified",
toast.show('✅', {
message: 'Identity verified',
customData: {
type: "success",
type: 'success',
},
});
setTimeout(() => {
setSelectedTab("valid");
setSelectedTab('valid');
}, 700);
} else {
toast.show("❌", {
message: "Verification failed",
toast.show('❌', {
message: 'Verification failed',
customData: {
type: "info",
type: 'info',
},
});
setTimeout(() => {
setSelectedTab("wrong");
setSelectedTab('wrong');
}, 700);
}
});
setSocket(newSocket);
} catch (error) {
console.error('Error setting up WebSocket:', error);
toast.show("❌", {
message: "Failed to set up connection",
toast.show('❌', {
message: 'Failed to set up connection',
customData: {
type: "error",
type: 'error',
},
});
}
@@ -146,9 +173,14 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
await waitForSocketConnection(socket);
setIsConnecting(false);
socket.emit('proof_generation_start', { sessionId: selectedApp.sessionId });
socket.emit('proof_generation_start', {
sessionId: selectedApp.sessionId,
});
const inputs = await generateCircuitInputsInApp(passportData, selectedApp);
const inputs = await generateCircuitInputsInApp(
passportData,
selectedApp,
);
let attestation;
let proof;
let dscProof;
@@ -156,18 +188,22 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
switch (selectedApp.mode) {
case 'prove_onchain':
case 'register':
const cscaInputs = generateCircuitInputsDSC(dscSecret as string, passportData.dsc, max_cert_bytes, selectedApp.devMode);
const cscaInputs = generateCircuitInputsDSC(
dscSecret as string,
passportData.dsc,
max_cert_bytes,
selectedApp.devMode,
);
[dscProof, proof] = await Promise.all([
sendCSCARequest(
cscaInputs
),
generateProof(
circuitName,
inputs,
)
sendCSCARequest(cscaInputs),
generateProof(circuitName, inputs),
]);
const cscaPem = getCSCAFromSKI(authorityKeyIdentifier, DEVELOPMENT_MODE);
const { signatureAlgorithm: signatureAlgorithmDsc } = parseCertificateSimple(cscaPem);
const cscaPem = getCSCAFromSKI(
authorityKeyIdentifier,
DEVELOPMENT_MODE,
);
const { signatureAlgorithm: signatureAlgorithmDsc } =
parseCertificateSimple(cscaPem);
attestation = buildAttestation({
mode: selectedApp.mode,
proof: proof.proof,
@@ -182,10 +218,7 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
});
break;
default:
proof = await generateProof(
circuitName,
inputs,
)
proof = await generateProof(circuitName, inputs);
attestation = buildAttestation({
userIdType: selectedApp.userIdType,
mode: selectedApp.mode,
@@ -197,19 +230,23 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
});
break;
}
console.log("\x1b[90mattestation\x1b[0m", attestation);
socket.emit('proof_generated', { sessionId: selectedApp.sessionId, proof: attestation });
console.log('\x1b[90mattestation\x1b[0m', attestation);
socket.emit('proof_generated', {
sessionId: selectedApp.sessionId,
proof: attestation,
});
} catch (error) {
toast.show("Error", {
toast.show('Error', {
message: String(error),
customData: {
type: "error",
type: 'error',
},
});
console.error('Error in handleProve:', error);
if (socket) {
socket.emit('proof_generation_failed', { sessionId: selectedApp.sessionId });
socket.emit('proof_generation_failed', {
sessionId: selectedApp.sessionId,
});
}
} finally {
setGeneratingProof(false);
@@ -217,25 +254,25 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
}
};
const disclosureFieldsToText = (key: keyof DisclosureOptions, option: any) => {
const disclosureFieldsToText = (
key: keyof DisclosureOptions,
option: any,
) => {
if (key === 'ofac') {
return (option == true)
? `My name is not present in the OFAC list.`
: '';
}
else if (option.enabled) {
return option == true ? 'My name is not present in the OFAC list.' : '';
} else if (option.enabled) {
switch (key) {
case 'minimumAge':
return `I am older than ${option.value} years old.`;
case 'nationality':
return option.value === 'Any'
? `The issuer country of my passport.`
? 'The issuer country of my passport.'
: `I have a valid passport from ${option.value}.`;
case 'excludedCountries':
return option.value.length > 0
? `I am not part of the following countries: ${option.value
.join(', ')}.`
? `I am not part of the following countries: ${option.value.join(
', ',
)}.`
: '';
default:
return '';
@@ -245,7 +282,7 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
};
const hasEnabledDisclosureOptions = Object.values(disclosureOptions).some(
(option: any) => option.enabled
(option: any) => option.enabled,
);
return (
@@ -253,22 +290,45 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
{hasEnabledDisclosureOptions ? (
<YStack mt="$4">
<Text fontSize="$9">
<Text fow="bold" style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>
<Text
fow="bold"
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
{selectedApp.appName}
</Text>{' '}
is requesting you to prove the following information.
</Text>
<Text mt="$3" fontSize="$8" color={textBlack} style={{ opacity: 0.9 }}>
<Text
mt="$3"
fontSize="$8"
color={textBlack}
style={{ opacity: 0.9 }}
>
No{' '}
<Text style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>
<Text
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
other
</Text>{' '}
information than the one selected below will be shared with {selectedApp.appName}.
information than the one selected below will be shared with{' '}
{selectedApp.appName}.
</Text>
</YStack>
) : (
<Text fontSize="$9">
<Text fow="bold" style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>
<Text
fow="bold"
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
{selectedApp.appName}
</Text>{' '}
is requesting you to prove you own a valid passport.
@@ -276,51 +336,84 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
)}
<YStack mt="$6">
{Object.entries(disclosureOptions).map(([key, option]: [string, any]) => {
const text = disclosureFieldsToText(key as keyof DisclosureOptions, option);
return text ? (
<XStack key={key} gap="$3" mb="$3" ml="$3">
<CheckCircle size={16} mt="$1.5" />
<Text fontSize="$7" color={textBlack} w="85%">
{text}
</Text>
</XStack>
) : null;
})}
{Object.entries(disclosureOptions).map(
([key, option]: [string, any]) => {
const text = disclosureFieldsToText(
key as keyof DisclosureOptions,
option,
);
return text ? (
<XStack key={key} gap="$3" mb="$3" ml="$3">
<CheckCircle size={16} mt="$1.5" />
<Text fontSize="$7" color={textBlack} w="85%">
{text}
</Text>
</XStack>
) : null;
},
)}
</YStack>
<XStack f={1} />
{isZkeyDownloading[circuitName as CircuitName] && <YStack alignItems='center' gap="$2" mb="$3" mx="$8">
<Text style={{ fontStyle: 'italic' }}>downloading files...</Text>
<Progress
key={circuitName}
size="$1"
value={zkeyDownloadedPercentage}
>
<Progress.Indicator
animation="bouncy"
backgroundColor={greenColorLight}
/>
</Progress>
</YStack>}
{isZkeyDownloading[circuitName as CircuitName] && (
<YStack alignItems="center" gap="$2" mb="$3" mx="$8">
<Text style={{ fontStyle: 'italic' }}>downloading files...</Text>
<Progress
key={circuitName}
size="$1"
value={zkeyDownloadedPercentage}
>
<Progress.Indicator
animation="bouncy"
backgroundColor={greenColorLight}
/>
</Progress>
</YStack>
)}
<CustomButton
Icon={isZkeyDownloading[circuitName as CircuitName] ? <Spinner /> : isConnecting ? <Spinner /> : generatingProof ? <Spinner /> : <CheckCircle />}
isDisabled={isZkeyDownloading[circuitName as CircuitName] || isConnecting || generatingProof}
text={isZkeyDownloading[circuitName as CircuitName] ? "Downloading files..." : isConnecting ? "Connecting..." : generatingProof ? "Generating Proof..." : "Verify"}
Icon={
isZkeyDownloading[circuitName as CircuitName] ? (
<Spinner />
) : isConnecting ? (
<Spinner />
) : generatingProof ? (
<Spinner />
) : (
<CheckCircle />
)
}
isDisabled={
isZkeyDownloading[circuitName as CircuitName] ||
isConnecting ||
generatingProof
}
text={
isZkeyDownloading[circuitName as CircuitName]
? 'Downloading files...'
: isConnecting
? 'Connecting...'
: generatingProof
? 'Generating Proof...'
: 'Verify'
}
onPress={registered ? handleProve : () => setSheetRegisterIsOpen(true)}
bgColor={isConnecting || generatingProof ? separatorColor : bgGreen}
disabledOnPress={() => toast.show('⏳', {
message: isZkeyDownloading[circuitName as CircuitName] ? "⏳ Downloading files..." : isConnecting ? "Connecting to server..." : "Proof is generating",
customData: {
type: "info",
},
})}
disabledOnPress={() =>
toast.show('⏳', {
message: isZkeyDownloading[circuitName as CircuitName]
? '⏳ Downloading files...'
: isConnecting
? 'Connecting to server...'
: 'Proof is generating',
customData: {
type: 'info',
},
})
}
/>
</YStack >
</YStack>
);
};

View File

@@ -1,28 +1,30 @@
import React, { useEffect } from 'react';
import useUserStore from '../stores/userStore';
import { Spinner, Text, XStack, YStack } from 'tamagui';
import useNavigationStore from '../stores/navigationStore';
import { YStack, Text, Spinner, XStack } from 'tamagui';
import useUserStore from '../stores/userStore';
import { textBlack } from '../utils/colors';
const SplashScreen = () => {
const { userLoaded, passportData } = useUserStore();
const { setSelectedTab } = useNavigationStore();
useEffect(() => {
if (userLoaded) {
if (passportData && passportData.dg2Hash && !passportData.mockUser) {
setSelectedTab('app');
} else {
setSelectedTab('start');
}
}
}, [userLoaded]);
return (
<YStack ai="center" f={1} gap="$8" mt="$18" mb="$8">
<Text fontSize="$9">OpenPassport</Text>
<XStack f={1} />
<Spinner color={textBlack} />
</YStack>
);
const { userLoaded, passportData } = useUserStore();
const { setSelectedTab } = useNavigationStore();
useEffect(() => {
if (userLoaded) {
if (passportData && passportData.dg2Hash && !passportData.mockUser) {
setSelectedTab('app');
} else {
setSelectedTab('start');
}
}
}, [userLoaded]); // eslint-disable-line
return (
<YStack ai="center" f={1} gap="$8" mt="$18" mb="$8">
<Text fontSize="$9">OpenPassport</Text>
<XStack f={1} />
<Spinner color={textBlack} />
</YStack>
);
};
export default SplashScreen;

View File

@@ -1,43 +1,47 @@
import React from 'react';
import { View } from 'react-native';
import { YStack, Text, XStack, Image } from 'tamagui';
import { ArrowRight, ShieldCheck } from '@tamagui/lucide-icons';
import { bgGreen, bgWhite, textBlack } from '../utils/colors';
import OPENPASSPORT_LOGO from '../images/openpassport.png';
import { ArrowRight } from '@tamagui/lucide-icons';
import { Image, Text, YStack } from 'tamagui';
import CustomButton from '../components/CustomButton';
import OPENPASSPORT_LOGO from '../images/openpassport.png';
import useNavigationStore from '../stores/navigationStore';
import { textBlack } from '../utils/colors';
const StartScreen: React.FC = () => {
const { setSelectedTab } = useNavigationStore();
const {
setSelectedTab
} = useNavigationStore();
return (
<YStack f={1}>
<YStack f={1} mt="$6" mb="$2.5" gap="$0" ai="center" jc="space-between">
<Text fontSize={38} color={textBlack} textAlign="center">
Welcome to OpenPassport.
</Text>
<Image src={OPENPASSPORT_LOGO} width={400} height={300} />
<Text textAlign="center" fontSize="$4" color={textBlack}>
No information will be shared without your explicit consent.
</Text>
</YStack>
return (
<YStack f={1} >
<YStack f={1} mt="$6" mb="$2.5" gap="$0" ai="center" jc="space-between" >
<Text fontSize={38} color={textBlack} textAlign='center'>Welcome to OpenPassport.</Text>
<Image src={OPENPASSPORT_LOGO} width={400} height={300} />
<Text textAlign='center' fontSize="$4" color={textBlack}>No information will be shared without your explicit consent.</Text>
</YStack>
<YStack gap="$2.5">
<CustomButton Icon={<ArrowRight />} text="Use my passport" onPress={() => {
setSelectedTab("scan");
}} />
<CustomButton bgColor="white" Icon={<ArrowRight />} text="Use a mock passport" onPress={() => {
setSelectedTab("mock");
}} />
</YStack>
</YStack >
);
<YStack gap="$2.5">
<CustomButton
Icon={<ArrowRight />}
text="Use my passport"
onPress={() => {
setSelectedTab('scan');
}}
/>
<CustomButton
bgColor="white"
Icon={<ArrowRight />}
text="Use a mock passport"
onPress={() => {
setSelectedTab('mock');
}}
/>
</YStack>
</YStack>
);
};
export default StartScreen;

View File

@@ -1,76 +1,140 @@
import React from 'react';
import { YStack, Text, XStack, Separator, ScrollView } from 'tamagui';
import useUserStore from '../stores/userStore';
import { textBlack, separatorColor } from '../utils/colors';
import { ScrollView, Separator, Text, XStack, YStack } from 'tamagui';
import { parsePassportData } from '../../../common/src/utils/parsePassportData';
import useUserStore from '../stores/userStore';
import { separatorColor, textBlack } from '../utils/colors';
const UserInfo: React.FC = () => {
const { passportData } = useUserStore();
const passportMetaData = passportData ? parsePassportData(passportData) : null;
const { passportData } = useUserStore();
const passportMetaData = passportData
? parsePassportData(passportData)
: null;
const InfoRow = ({ label, value }: { label: string; value: string | number }) => (
<XStack py="$2" justifyContent="space-between">
<Text color={textBlack} fontSize="$5">{label}</Text>
<Text color={textBlack} fontSize="$5">{value}</Text>
</XStack>
);
const InfoRow = ({
label,
value,
}: {
label: string;
value: string | number;
}) => (
<XStack py="$2" justifyContent="space-between">
<Text color={textBlack} fontSize="$5">
{label}
</Text>
<Text color={textBlack} fontSize="$5">
{value}
</Text>
</XStack>
);
return (
<ScrollView>
<YStack f={1} p="$0" gap="$2" jc="flex-start" py="$2" >
<Text fontSize="$8" color={textBlack} mb="$4">Passport Data Info</Text>
<Separator borderColor={separatorColor} />
return (
<ScrollView>
<YStack f={1} p="$0" gap="$2" jc="flex-start" py="$2">
<Text fontSize="$8" color={textBlack} mb="$4">
Passport Data Info
</Text>
<Separator borderColor={separatorColor} />
<InfoRow label="Data Groups" value={passportMetaData?.dataGroups || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="Data Groups"
value={passportMetaData?.dataGroups || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="DG1 Hash Function" value={passportMetaData?.dg1HashFunction || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="DG1 Hash Function"
value={passportMetaData?.dg1HashFunction || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="DG1 Hash Offset" value={passportMetaData?.dg1HashOffset || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="DG1 Hash Offset"
value={passportMetaData?.dg1HashOffset || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="eContent Size" value={passportMetaData?.eContentSize || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="eContent Size"
value={passportMetaData?.eContentSize || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="eContent Hash Function" value={passportMetaData?.eContentHashFunction || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="eContent Hash Function"
value={passportMetaData?.eContentHashFunction || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="eContent Hash Offset" value={passportMetaData?.eContentHashOffset || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="eContent Hash Offset"
value={passportMetaData?.eContentHashOffset || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="Signed Attributes Size" value={passportMetaData?.signedAttrSize || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="Signed Attributes Size"
value={passportMetaData?.signedAttrSize || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="Signed Attributes Hash Function" value={passportMetaData?.signedAttrHashFunction || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="Signed Attributes Hash Function"
value={passportMetaData?.signedAttrHashFunction || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="Signature Algorithm" value={passportMetaData?.signatureAlgorithm || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="Signature Algorithm"
value={passportMetaData?.signatureAlgorithm || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="Signature Algorithm Details" value={passportMetaData?.curveOrExponent || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="Signature Algorithm Details"
value={passportMetaData?.curveOrExponent || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="Signature Algorithm Bits" value={passportMetaData?.signatureAlgorithmBits || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="Signature Algorithm Bits"
value={passportMetaData?.signatureAlgorithmBits || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="CSCA Found" value={passportMetaData?.cscaFound ? 'Yes' : 'No'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="CSCA Found"
value={passportMetaData?.cscaFound ? 'Yes' : 'No'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="CSCA Hash Function" value={passportMetaData?.cscaHashFunction || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="CSCA Hash Function"
value={passportMetaData?.cscaHashFunction || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="CSCA Signature Algorithm" value={passportMetaData?.cscaSignature || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="CSCA Signature Algorithm"
value={passportMetaData?.cscaSignature || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="CSCA Signature Algorithm Details" value={passportMetaData?.cscaCurveOrExponent || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="CSCA Signature Algorithm Details"
value={passportMetaData?.cscaCurveOrExponent || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow label="CSCA Signature Algorithm Bits" value={passportMetaData?.cscaSignatureAlgorithmBits || 'None'} />
<Separator borderColor={separatorColor} />
</YStack>
</ScrollView>
);
<InfoRow
label="CSCA Signature Algorithm Bits"
value={passportMetaData?.cscaSignatureAlgorithmBits || 'None'}
/>
<Separator borderColor={separatorColor} />
</YStack>
</ScrollView>
);
};
export default UserInfo;

View File

@@ -1,23 +1,39 @@
import React from 'react';
import { YStack, Text, XStack } from 'tamagui';
import { bgGreen, textBlack } from '../utils/colors';
import CustomButton from '../components/CustomButton';
import { scanQRCode } from '../utils/qrCode';
import { QrCode } from '@tamagui/lucide-icons';
import { QrCode } from '@tamagui/lucide-icons';
import { Text, XStack, YStack } from 'tamagui';
import CustomButton from '../components/CustomButton';
import { bgGreen, textBlack } from '../utils/colors';
import { scanQRCode } from '../utils/qrCode';
const SuccessScreen: React.FC = () => {
return (
<YStack f={1} >
<YStack f={1}>
<YStack f={1} mt="$8">
<Text ml="$1" fontSize="$10" color={textBlack}><Text style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>Success</Text>, the proof has been verified</Text>
<Text ml="$1" fontSize="$10" color={textBlack}>
<Text
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
Success
</Text>
, the proof has been verified
</Text>
<XStack f={1} />
</YStack>
<CustomButton Icon={<QrCode size={18} color={textBlack} />} text="Scan another QR code" onPress={() => { scanQRCode() }} />
<CustomButton
Icon={<QrCode size={18} color={textBlack} />}
text="Scan another QR code"
onPress={() => {
scanQRCode();
}}
/>
</YStack>
);
};
export default SuccessScreen;
export default SuccessScreen;

View File

@@ -1,28 +1,53 @@
import React from 'react';
import { YStack, Text, XStack } from 'tamagui';
import { bgGreen, textBlack } from '../utils/colors';
import useUserStore from '../stores/userStore';
import CustomButton from '../components/CustomButton';
import { QrCode } from '@tamagui/lucide-icons';
import React from 'react';
import { Text, XStack, YStack } from 'tamagui';
import CustomButton from '../components/CustomButton';
import useUserStore from '../stores/userStore';
import { bgGreen, textBlack } from '../utils/colors';
import { scanQRCode } from '../utils/qrCode';
const WrongProofScreen: React.FC = () => {
const { proofVerificationResult } = useUserStore();
const formatFieldName = (field: string) => {
return field.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
return field
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};
const fieldsToCheck = [
'scope', 'merkle_root_commitment', 'merkle_root_csca', 'attestation_id', 'current_date', 'issuing_state',
'name', 'passport_number', 'nationality', 'date_of_birth', 'gender',
'expiry_date', 'older_than', 'owner_of', 'blinded_dsc_commitment', 'proof',
'dscProof', 'dsc', 'pubKey', 'ofac', 'forbidden_countries_list'
'scope',
'merkle_root_commitment',
'merkle_root_csca',
'attestation_id',
'current_date',
'issuing_state',
'name',
'passport_number',
'nationality',
'date_of_birth',
'gender',
'expiry_date',
'older_than',
'owner_of',
'blinded_dsc_commitment',
'proof',
'dscProof',
'dsc',
'pubKey',
'ofac',
'forbidden_countries_list',
];
const failedConditions = [];
for (const field of fieldsToCheck) {
console.log(`Checking field ${field}: ${JSON.stringify((proofVerificationResult as any)[field])}`);
console.log(
`Checking field ${field}: ${JSON.stringify(
(proofVerificationResult as any)[field],
)}`,
);
if ((proofVerificationResult as any)[field] === false) {
failedConditions.push(formatFieldName(field));
}
@@ -31,10 +56,18 @@ const WrongProofScreen: React.FC = () => {
console.log('Failed conditions:', JSON.stringify(failedConditions));
return (
<YStack f={1} >
<YStack f={1} mt="$4" >
<YStack f={1}>
<YStack f={1} mt="$4">
<Text ml="$1" fontSize={34} color={textBlack}>
<Text style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>Oops</Text>, the proof is not valid.
<Text
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
Oops
</Text>
, the proof is not valid.
</Text>
{(proofVerificationResult as any).error ? (
<Text ml="$2" mt="$3" fontSize="$8" color={textBlack}>
@@ -43,25 +76,55 @@ const WrongProofScreen: React.FC = () => {
) : (
<>
<Text ml="$2" mt="$3" fontSize="$8" color={textBlack}>
Some of the <Text >conditions</Text> have not been satisfied:
Some of the <Text>conditions</Text> have not been satisfied:
</Text>
<YStack ml="$4" mt="$5">
{failedConditions.map((condition, index) => (
<Text key={index} fontSize="$7" color={textBlack} >
· <Text key={index} style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>{condition}</Text>
<Text key={index} fontSize="$7" color={textBlack}>
·{' '}
<Text
key={index}
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
{condition}
</Text>
</Text>
))}
</YStack>
</>
)}
<Text ml="$2" mt="$8" fontSize="$7" color={textBlack} style={{ opacity: 0.7 }}>
<Text style={{ textDecorationLine: 'underline', textDecorationColor: bgGreen }}>Check again</Text> your eligibility, if you are sure to be eligible to this verification please contact OpenPassport support.
<Text
ml="$2"
mt="$8"
fontSize="$7"
color={textBlack}
style={{ opacity: 0.7 }}
>
<Text
style={{
textDecorationLine: 'underline',
textDecorationColor: bgGreen,
}}
>
Check again
</Text>{' '}
your eligibility, if you are sure to be eligible to this verification
please contact OpenPassport support.
</Text>
<XStack f={1} />
<CustomButton Icon={<QrCode size={18} color={textBlack} />} text="Scan another QR code" onPress={() => { scanQRCode() }} />
<CustomButton
Icon={<QrCode size={18} color={textBlack} />}
text="Scan another QR code"
onPress={() => {
scanQRCode();
}}
/>
</YStack>
</YStack >
</YStack>
);
};
export default WrongProofScreen;
export default WrongProofScreen;

View File

@@ -1,30 +1,34 @@
import { create } from 'zustand'
import { IsZkeyDownloading, ShowWarningModalProps } from '../utils/zkeyDownload';
import { useToastController } from '@tamagui/toast';
import { create } from 'zustand';
import { OpenPassportApp } from '../../../common/src/utils/appType';
import {
IsZkeyDownloading,
ShowWarningModalProps,
} from '../utils/zkeyDownload';
interface NavigationState {
isZkeyDownloading: IsZkeyDownloading
showWarningModal: ShowWarningModalProps
hideData: boolean
toast: ReturnType<typeof useToastController>
selectedTab: string
setSelectedTab: (tab: string) => void
selectedApp: OpenPassportApp | null
setSelectedApp: (app: OpenPassportApp | null) => void
showRegistrationErrorSheet: boolean
registrationErrorMessage: string
isZkeyDownloading: IsZkeyDownloading;
showWarningModal: ShowWarningModalProps;
hideData: boolean;
toast: ReturnType<typeof useToastController>;
selectedTab: string;
setSelectedTab: (tab: string) => void;
selectedApp: OpenPassportApp | null;
setSelectedApp: (app: OpenPassportApp | null) => void;
showRegistrationErrorSheet: boolean;
registrationErrorMessage: string;
setToast: (toast: ReturnType<typeof useToastController>) => void;
update: (patch: any) => void
nfcSheetIsOpen: boolean
setNfcSheetIsOpen: (isOpen: boolean) => void
zkeyDownloadedPercentage: number
setZkeyDownloadedPercentage: (percentage: number) => void
update: (patch: any) => void;
nfcSheetIsOpen: boolean;
setNfcSheetIsOpen: (isOpen: boolean) => void;
zkeyDownloadedPercentage: number;
setZkeyDownloadedPercentage: (percentage: number) => void;
}
const useNavigationStore = create<NavigationState>((set, get) => ({
zkeyDownloadedPercentage: 100,
setZkeyDownloadedPercentage: (percentage: number) => set({ zkeyDownloadedPercentage: percentage }),
setZkeyDownloadedPercentage: (percentage: number) =>
set({ zkeyDownloadedPercentage: percentage }),
isZkeyDownloading: {
prove_rsa_65537_sha1: false,
prove_rsa_65537_sha256: false,
@@ -33,32 +37,32 @@ const useNavigationStore = create<NavigationState>((set, get) => ({
},
showWarningModal: {
show: false,
circuit: "",
circuit: '',
size: 0,
},
hideData: false,
showRegistrationErrorSheet: false,
registrationErrorMessage: "",
registrationErrorMessage: '',
toast: null as unknown as ReturnType<typeof useToastController>,
selectedTab: "scan",
selectedTab: 'scan',
selectedApp: null,
setToast: (toast) => set({ toast }),
setSelectedApp: (app) => set({ selectedApp: app }),
setToast: toast => set({ toast }),
setSelectedApp: app => set({ selectedApp: app }),
setSelectedTab: (tab) => set({ selectedTab: tab }),
setSelectedTab: tab => set({ selectedTab: tab }),
update: (patch) => {
update: patch => {
set({
...get(),
...patch,
});
},
nfcSheetIsOpen: false,
setNfcSheetIsOpen: (isOpen) => set({ nfcSheetIsOpen: isOpen }),
}))
setNfcSheetIsOpen: isOpen => set({ nfcSheetIsOpen: isOpen }),
}));
export default useNavigationStore
export default useNavigationStore;

View File

@@ -1,50 +1,51 @@
import { create } from 'zustand'
import {
DEFAULT_PNUMBER,
DEFAULT_DOB,
DEFAULT_DOE,
} from '@env';
import { PassportData, Proof } from '../../../common/src/utils/types';
import * as Keychain from 'react-native-keychain';
import { loadPassportData, loadSecret, loadSecretOrCreateIt, storePassportData } from '../utils/keychain';
import { DEFAULT_DOB, DEFAULT_DOE, DEFAULT_PNUMBER } from '@env';
import { resetGenericPassword } from 'react-native-keychain';
import { create } from 'zustand';
import { generateDscSecret } from '../../../common/src/utils/csca';
import { PassportData, Proof } from '../../../common/src/utils/types';
import {
loadPassportData,
loadSecretOrCreateIt,
storePassportData,
} from '../utils/keychain';
interface UserState {
passportNumber: string
dateOfBirth: string
dateOfExpiry: string
countryCode: string
registered: boolean
passportData: PassportData | null
secret: string
cscaProof: Proof | null
localProof: Proof | null
dscSecret: string | null
userLoaded: boolean
initUserStore: () => void
registerPassportData: (passportData: PassportData) => Promise<void>
clearPassportDataFromStorage: () => void
clearSecretFromStorage: () => void
clearProofsFromStorage: () => void
update: (patch: any) => void
deleteMrzFields: () => void
setRegistered: (registered: boolean) => void
setDscSecret: (dscSecret: string) => void
setUserLoaded: (userLoaded: boolean) => void
proofVerificationResult: string,
setProofVerificationResult: (proofVerificationResult: string) => void
passportNumber: string;
dateOfBirth: string;
dateOfExpiry: string;
countryCode: string;
registered: boolean;
passportData: PassportData | null;
secret: string;
cscaProof: Proof | null;
localProof: Proof | null;
dscSecret: string | null;
userLoaded: boolean;
initUserStore: () => void;
registerPassportData: (passportData: PassportData) => Promise<void>;
clearPassportDataFromStorage: () => void;
clearSecretFromStorage: () => void;
clearProofsFromStorage: () => void;
update: (patch: any) => void;
deleteMrzFields: () => void;
setRegistered: (registered: boolean) => void;
setDscSecret: (dscSecret: string) => void;
setUserLoaded: (userLoaded: boolean) => void;
proofVerificationResult: string;
setProofVerificationResult: (proofVerificationResult: string) => void;
}
const useUserStore = create<UserState>((set, get) => ({
userLoaded: false,
passportNumber: DEFAULT_PNUMBER ?? "",
dateOfBirth: DEFAULT_DOB ?? "",
dateOfExpiry: DEFAULT_DOE ?? "",
countryCode: "",
passportNumber: DEFAULT_PNUMBER ?? '',
dateOfBirth: DEFAULT_DOB ?? '',
dateOfExpiry: DEFAULT_DOE ?? '',
countryCode: '',
dscSecret: null,
registered: false,
passportData: null,
secret: "",
secret: '',
cscaProof: null,
localProof: null,
setRegistered: (registered: boolean) => {
@@ -56,7 +57,7 @@ const useUserStore = create<UserState>((set, get) => ({
setUserLoaded: (userLoaded: boolean) => {
set({ userLoaded });
},
proofVerificationResult: "null",
proofVerificationResult: 'null',
setProofVerificationResult: (proofVerificationResult: string) => {
set({ proofVerificationResult });
},
@@ -75,7 +76,7 @@ const useUserStore = create<UserState>((set, get) => ({
const passportDataString = await loadPassportData();
if (!passportDataString) {
console.log("No passport data found, starting onboarding flow")
console.log('No passport data found, starting onboarding flow');
set({
userLoaded: true,
});
@@ -83,11 +84,13 @@ const useUserStore = create<UserState>((set, get) => ({
}
// const isAlreadyRegistered = await isCommitmentRegistered(secret, JSON.parse(passportData));
const isAlreadyRegistered = true
const passportData: PassportData = JSON.parse(passportDataString)
const isAlreadyRegistered = true;
const passportData: PassportData = JSON.parse(passportDataString);
if (!isAlreadyRegistered) {
console.log("not registered but passport data found, skipping to nextScreen")
console.log(
'not registered but passport data found, skipping to nextScreen',
);
set({
passportData: passportData,
userLoaded: true,
@@ -96,7 +99,9 @@ const useUserStore = create<UserState>((set, get) => ({
return;
}
console.log("registered and passport data found, skipping to app selection screen")
console.log(
'registered and passport data found, skipping to app selection screen',
);
set({
passportData: passportData,
registered: true,
@@ -107,19 +112,21 @@ const useUserStore = create<UserState>((set, get) => ({
// 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) => {
registerPassportData: async passportData => {
const alreadyStoredPassportData = await loadPassportData();
if (alreadyStoredPassportData) {
console.log("a passportData is already stored, replacing it with the new one")
console.log(
'a passportData is already stored, replacing it with the new one',
);
}
await storePassportData(passportData)
await storePassportData(passportData);
set({ passportData });
},
clearPassportDataFromStorage: async () => {
await Keychain.resetGenericPassword({ service: "passportData" });
await resetGenericPassword({ service: 'passportData' });
},
clearProofsFromStorage: async () => {
@@ -128,21 +135,22 @@ const useUserStore = create<UserState>((set, get) => ({
},
clearSecretFromStorage: async () => {
await Keychain.resetGenericPassword({ service: "secret" });
await resetGenericPassword({ service: 'secret' });
},
update: (patch) => {
update: patch => {
set({
...get(),
...patch,
});
},
deleteMrzFields: () => set({
passportNumber: "",
dateOfBirth: "",
dateOfExpiry: "",
}),
}))
deleteMrzFields: () =>
set({
passportNumber: '',
dateOfBirth: '',
dateOfExpiry: '',
}),
}));
export default useUserStore
export default useUserStore;

View File

@@ -1,4 +1,4 @@
declare module 'country-iso-3-to-2' {
function getCountryISO2(iso3: string): string;
export = getCountryISO2;
}
function getCountryISO2(iso3: string): string;
export = getCountryISO2;
}

View File

@@ -1,8 +1,9 @@
import { NativeModules, Platform } from 'react-native';
import { formatDateToYYMMDD, extractMRZInfo } from './utils';
import * as amplitude from '@amplitude/analytics-react-native';
import useUserStore from '../stores/userStore';
import { NativeModules, Platform } from 'react-native';
import useNavigationStore from '../stores/navigationStore';
import useUserStore from '../stores/userStore';
import { extractMRZInfo, formatDateToYYMMDD } from './utils';
export const startCameraScan = async () => {
const { toast, setSelectedTab } = useNavigationStore.getState();
@@ -10,22 +11,24 @@ export const startCameraScan = async () => {
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}`);
console.log('Scan result:', result);
console.log(
`Document Number: ${result.documentNumber}, Expiry Date: ${result.expiryDate}, Birth Date: ${result.birthDate}`,
);
useUserStore.setState({
passportNumber: result.documentNumber,
dateOfBirth: formatDateToYYMMDD(result.birthDate),
dateOfExpiry: formatDateToYYMMDD(result.expiryDate),
})
});
setSelectedTab("nfc");
toast.show("✔︎", {
setSelectedTab('nfc');
toast.show('✔︎', {
message: 'Scan successful',
customData: {
type: "success",
type: 'success',
},
})
});
} catch (e) {
console.error(e);
amplitude.track('camera_scan_error', { error: e });
@@ -34,21 +37,22 @@ export const startCameraScan = async () => {
NativeModules.CameraActivityModule.startCameraActivity()
.then((mrzInfo: string) => {
try {
const { documentNumber, birthDate, expiryDate } = extractMRZInfo(mrzInfo);
const { documentNumber, birthDate, expiryDate } =
extractMRZInfo(mrzInfo);
useUserStore.setState({
passportNumber: documentNumber,
dateOfBirth: birthDate,
dateOfExpiry: expiryDate,
})
});
setSelectedTab("nfc");
toast.show("✔︎", {
setSelectedTab('nfc');
toast.show('✔︎', {
message: 'Scan successful',
customData: {
type: "success",
type: 'success',
},
})
});
} catch (error: any) {
console.error('Invalid MRZ format:', error.message);
amplitude.track('invalid_mrz_format', { error: error.message });
@@ -59,4 +63,4 @@ export const startCameraScan = async () => {
amplitude.track('camera_scan_error', { error: error.message });
});
}
};
};

View File

@@ -1,21 +1,21 @@
export const borderColor = "#343434";
export const bgColor = "#161616";
export const componentBgColor = "#1c1c1c";
export const componentBgColor2 = "#232323";
export const blueColor = "#3185FC";
export const textColor1 = "#ededed";
export const textColor2 = "#a0a0a0";
export const blueColorDark = "#11233e"
export const blueColorLight = "#0090ff"
export const greenColorDark = "#10291e"
export const greenColorLight = "#30a46b"
export const redColorDark = "#3c181a"
export const redColorLight = "#e5484d"
export const yellowColorDark = "#2d2200"
export const yellowColorLight = "#f5d90a"
export const bgWhite = "#F5F5F5"
export const textBlack = "#333333"
export const bgGreen2 = "#94FBAB"
export const bgGreen = "#AEECEF"
export const bgBlue = "#69DFFF"
export const separatorColor = "#E0E0E0"
export const borderColor = '#343434';
export const bgColor = '#161616';
export const componentBgColor = '#1c1c1c';
export const componentBgColor2 = '#232323';
export const blueColor = '#3185FC';
export const textColor1 = '#ededed';
export const textColor2 = '#a0a0a0';
export const blueColorDark = '#11233e';
export const blueColorLight = '#0090ff';
export const greenColorDark = '#10291e';
export const greenColorLight = '#30a46b';
export const redColorDark = '#3c181a';
export const redColorLight = '#e5484d';
export const yellowColorDark = '#2d2200';
export const yellowColorLight = '#f5d90a';
export const bgWhite = '#F5F5F5';
export const textBlack = '#333333';
export const bgGreen2 = '#94FBAB';
export const bgGreen = '#AEECEF';
export const bgBlue = '#69DFFF';
export const separatorColor = '#E0E0E0';

View File

@@ -1,58 +1,61 @@
import axios from 'axios';
import { contribute_publicKey } from '../../../common/src/constants/constants';
import forge from 'node-forge';
import { contribute_publicKey } from '../../../common/src/constants/constants';
export async function contribute(passportData: any): Promise<void> {
console.log('Contributing...');
const textToEncrypt = JSON.stringify(passportData);
console.log('Text to Encrypt:', textToEncrypt);
console.log("Contributing...")
const textToEncrypt = JSON.stringify(passportData);
console.log("Text to Encrypt:", textToEncrypt);
try {
const aesKey = forge.random.getBytesSync(32);
const iv = forge.random.getBytesSync(16);
try {
const aesKey = forge.random.getBytesSync(32);
const iv = forge.random.getBytesSync(16);
console.log('Generated AES Key (base64):', forge.util.encode64(aesKey));
console.log('Generated IV (base64):', forge.util.encode64(iv));
console.log("Generated AES Key (base64):", forge.util.encode64(aesKey));
console.log("Generated IV (base64):", forge.util.encode64(iv));
const cipher = forge.cipher.createCipher('AES-CBC', aesKey);
cipher.start({ iv: iv });
cipher.update(forge.util.createBuffer(textToEncrypt, 'utf8'));
cipher.finish();
const encryptedData = cipher.output.getBytes();
const cipher = forge.cipher.createCipher('AES-CBC', aesKey);
cipher.start({ iv: iv });
cipher.update(forge.util.createBuffer(textToEncrypt, 'utf8'));
cipher.finish();
const encryptedData = cipher.output.getBytes();
console.log('Encrypted Data (base64):', forge.util.encode64(encryptedData));
console.log("Encrypted Data (base64):", forge.util.encode64(encryptedData));
const publicKey = forge.pki.publicKeyFromPem(contribute_publicKey);
const encryptedAesKey = publicKey.encrypt(aesKey, 'RSA-OAEP', {
md: forge.md.sha256.create(),
mgf1: {
md: forge.md.sha256.create(),
},
});
const publicKey = forge.pki.publicKeyFromPem(contribute_publicKey);
const encryptedAesKey = publicKey.encrypt(aesKey, 'RSA-OAEP', {
md: forge.md.sha256.create(),
mgf1: {
md: forge.md.sha256.create()
}
});
const aesKeyBase64 = forge.util.encode64(encryptedAesKey);
const ivBase64 = forge.util.encode64(iv);
const encryptedDataBase64 = forge.util.encode64(encryptedData);
const aesKeyBase64 = forge.util.encode64(encryptedAesKey);
const ivBase64 = forge.util.encode64(iv);
const encryptedDataBase64 = forge.util.encode64(encryptedData);
console.log('Encrypted AES Key (base64):', aesKeyBase64);
console.log('Encrypted Data (base64):', encryptedDataBase64);
console.log("Encrypted AES Key (base64):", aesKeyBase64);
console.log("Encrypted Data (base64):", encryptedDataBase64);
const data = {
aesKey: aesKeyBase64,
iv: ivBase64,
encryptedData: encryptedDataBase64,
};
const data = {
aesKey: aesKeyBase64,
iv: ivBase64,
encryptedData: encryptedDataBase64
};
console.log('Data to be sent:', JSON.stringify(data));
console.log("Data to be sent:", JSON.stringify(data));
const response = await axios.post('https://contribute.openpassport.app', {
nullifier: forge.md.sha256.create().update(passportData.encryptedDigest.toString()).digest().toHex(),
data: data
});
console.log("Server Response:", response.data);
} catch (error) {
console.error("Encryption Error:", error);
}
const response = await axios.post('https://contribute.openpassport.app', {
nullifier: forge.md.sha256
.create()
.update(passportData.encryptedDigest.toString())
.digest()
.toHex(),
data: data,
});
console.log('Server Response:', response.data);
} catch (error) {
console.error('Encryption Error:', error);
}
}

View File

@@ -1,31 +1,38 @@
import { castCSCAProof } from "../../../common/src/utils/types";
import useUserStore from "../stores/userStore";
import { ModalProofSteps } from "./utils";
import { castCSCAProof } from '../../../common/src/utils/types';
import useUserStore from '../stores/userStore';
import { ModalProofSteps } from './utils';
export const sendCSCARequest = async (inputs_csca: any, modalServerUrl: string, setModalProofStep: (modalProofStep: number) => void) => {
try {
console.log("inputs_csca before requesting modal server - cscaRequest.ts");
fetch(modalServerUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(inputs_csca)
}).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}).then(data => {
useUserStore.getState().cscaProof = castCSCAProof(data);
setModalProofStep(ModalProofSteps.MODAL_SERVER_SUCCESS);
console.log('Response from server:', data);
}).catch(error => {
console.error('Error during request:', error);
setModalProofStep(ModalProofSteps.MODAL_SERVER_ERROR);
});
} catch (error) {
export const sendCSCARequest = async (
inputs_csca: any,
modalServerUrl: string,
setModalProofStep: (modalProofStep: number) => void,
) => {
try {
console.log('inputs_csca before requesting modal server - cscaRequest.ts');
fetch(modalServerUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(inputs_csca),
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
useUserStore.getState().cscaProof = castCSCAProof(data);
setModalProofStep(ModalProofSteps.MODAL_SERVER_SUCCESS);
console.log('Response from server:', data);
})
.catch(error => {
console.error('Error during request:', error);
setModalProofStep(ModalProofSteps.MODAL_SERVER_ERROR);
}
};
});
} catch (error) {
console.error('Error during request:', error);
setModalProofStep(ModalProofSteps.MODAL_SERVER_ERROR);
}
};

View File

@@ -1,77 +1,118 @@
import { ArgumentsDisclose, ArgumentsProveOffChain, DisclosureOptions, OpenPassportApp } from '../../../common/src/utils/appType';
import { PassportData } from '../../../common/src/utils/types';
import { generateCircuitInputsDisclose, generateCircuitInputsProve } from '../../../common/src/utils/generateInputs';
import { circuitToSelectorMode, DEFAULT_MAJORITY, getCountryCode, PASSPORT_ATTESTATION_ID } from '../../../common/src/constants/constants';
import { revealBitmapFromAttributes } from '../../../common/src/utils/revealBitmap';
import useUserStore from '../stores/userStore';
import namejson from '../../../common/ofacdata/outputs/nameSMT.json';
import { SMT } from '@openpassport/zk-kit-smt';
import { poseidon2 } from 'poseidon-lite';
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
import namejson from '../../../common/ofacdata/outputs/nameSMT.json';
import {
circuitToSelectorMode,
DEFAULT_MAJORITY,
getCountryCode,
PASSPORT_ATTESTATION_ID,
} from '../../../common/src/constants/constants';
import {
ArgumentsDisclose,
ArgumentsProveOffChain,
DisclosureOptions,
OpenPassportApp,
} from '../../../common/src/utils/appType';
import {
generateCircuitInputsDisclose,
generateCircuitInputsProve,
} from '../../../common/src/utils/generateInputs';
import { fetchTreeFromUrl } from '../../../common/src/utils/pubkeyTree';
import { revealBitmapFromAttributes } from '../../../common/src/utils/revealBitmap';
import { PassportData } from '../../../common/src/utils/types';
import useUserStore from '../stores/userStore';
export const generateCircuitInputsInApp = async (
passportData: PassportData,
app: OpenPassportApp
passportData: PassportData,
app: OpenPassportApp,
): Promise<any> => {
const { secret, dscSecret } = useUserStore.getState();
const selector_mode = circuitToSelectorMode[app.mode as keyof typeof circuitToSelectorMode];
let smt = new SMT(poseidon2, true);
smt.import(namejson);
switch (app.mode) {
case "prove_offchain":
case "prove_onchain":
const disclosureOptions: DisclosureOptions = (app.args as ArgumentsProveOffChain).disclosureOptions;
const selector_dg1 = revealBitmapFromAttributes(disclosureOptions);
const selector_older_than = disclosureOptions.minimumAge.enabled ? 1 : 0;
const selector_ofac = disclosureOptions.ofac ? 1 : 0;
const forbidden_countries_list = disclosureOptions.excludedCountries.value.map(country => getCountryCode(country));
const inputs = generateCircuitInputsProve(
selector_mode,
secret,
dscSecret as string,
passportData,
app.scope,
selector_dg1,
selector_older_than,
disclosureOptions.minimumAge.value ?? DEFAULT_MAJORITY,
smt,
selector_ofac,
forbidden_countries_list,
app.userId,
app.userIdType
);
return inputs;
case "register":
const selector_dg1_zero = new Array(88).fill(0);
const selector_older_than_zero = 0;
const selector_ofac_zero = 0;
return generateCircuitInputsProve(
selector_mode,
secret,
dscSecret as string,
passportData,
app.scope,
selector_dg1_zero,
selector_older_than_zero,
DEFAULT_MAJORITY,
smt,
selector_ofac_zero,
[],
app.userId,
app.userIdType
);
break;
case "vc_and_disclose":
const commitmentMerkleTreeUrl = (app as any).args.commitmentMerkleTreeUrl;
const tree = await fetchTreeFromUrl(commitmentMerkleTreeUrl);
const { secret, dscSecret } = useUserStore.getState();
const selector_mode =
circuitToSelectorMode[app.mode as keyof typeof circuitToSelectorMode];
let smt = new SMT(poseidon2, true);
smt.import(namejson);
switch (app.mode) {
case 'prove_offchain':
case 'prove_onchain':
const disclosureOptions: DisclosureOptions = (
app.args as ArgumentsProveOffChain
).disclosureOptions;
const selector_dg1 = revealBitmapFromAttributes(disclosureOptions);
const selector_older_than = disclosureOptions.minimumAge.enabled ? 1 : 0;
const selector_ofac = disclosureOptions.ofac ? 1 : 0;
const forbidden_countries_list =
disclosureOptions.excludedCountries.value.map(country =>
getCountryCode(country),
);
const inputs = generateCircuitInputsProve(
selector_mode,
secret,
dscSecret as string,
passportData,
app.scope,
selector_dg1,
selector_older_than,
disclosureOptions.minimumAge.value ?? DEFAULT_MAJORITY,
smt,
selector_ofac,
forbidden_countries_list,
app.userId,
app.userIdType,
);
return inputs;
case 'register':
const selector_dg1_zero = new Array(88).fill(0);
const selector_older_than_zero = 0;
const selector_ofac_zero = 0;
return generateCircuitInputsProve(
selector_mode,
secret,
dscSecret as string,
passportData,
app.scope,
selector_dg1_zero,
selector_older_than_zero,
DEFAULT_MAJORITY,
smt,
selector_ofac_zero,
[],
app.userId,
app.userIdType,
);
case 'vc_and_disclose':
const commitmentMerkleTreeUrl = (app as any).args.commitmentMerkleTreeUrl;
const tree = await fetchTreeFromUrl(commitmentMerkleTreeUrl);
const disclosureOptionsDisclose: DisclosureOptions = (app.args as ArgumentsDisclose).disclosureOptions;
const selector_dg1_disclose = revealBitmapFromAttributes(disclosureOptionsDisclose);
const selector_older_than_disclose = disclosureOptionsDisclose.minimumAge.enabled ? 1 : 0;
const selector_ofac_disclose = disclosureOptionsDisclose.ofac ? 1 : 0;
const forbidden_countries_list_disclose = disclosureOptionsDisclose.excludedCountries.value.map(country => getCountryCode(country))
return generateCircuitInputsDisclose(secret, PASSPORT_ATTESTATION_ID, passportData, app.scope, selector_dg1_disclose, selector_older_than_disclose, tree, disclosureOptionsDisclose.minimumAge.value ?? DEFAULT_MAJORITY, smt, selector_ofac_disclose, forbidden_countries_list_disclose, app.userId)
}
}
const disclosureOptionsDisclose: DisclosureOptions = (
app.args as ArgumentsDisclose
).disclosureOptions;
const selector_dg1_disclose = revealBitmapFromAttributes(
disclosureOptionsDisclose,
);
const selector_older_than_disclose = disclosureOptionsDisclose.minimumAge
.enabled
? 1
: 0;
const selector_ofac_disclose = disclosureOptionsDisclose.ofac ? 1 : 0;
const forbidden_countries_list_disclose =
disclosureOptionsDisclose.excludedCountries.value.map(country =>
getCountryCode(country),
);
return generateCircuitInputsDisclose(
secret,
PASSPORT_ATTESTATION_ID,
passportData,
app.scope,
selector_dg1_disclose,
selector_older_than_disclose,
tree,
disclosureOptionsDisclose.minimumAge.value ?? DEFAULT_MAJORITY,
smt,
selector_ofac_disclose,
forbidden_countries_list_disclose,
app.userId,
);
}
};

View File

@@ -1,29 +1,37 @@
import * as Keychain from 'react-native-keychain';
import { ethers } from 'ethers';
import * as Keychain from 'react-native-keychain';
import { PassportData } from '../../../common/src/utils/types';
export async function loadSecretOrCreateIt() {
const secret = await loadSecret()
const secret = await loadSecret();
if (secret) {
return secret
return secret;
}
console.log("No secret found, creating one")
console.log('No secret found, creating one');
const randomWallet = ethers.Wallet.createRandom();
const newSecret = randomWallet.privateKey;
await Keychain.setGenericPassword("secret", newSecret, { service: "secret" });
return newSecret
await Keychain.setGenericPassword('secret', newSecret, { service: 'secret' });
return newSecret;
}
export async function loadSecret() {
const secretCreds = await Keychain.getGenericPassword({ service: "secret" })
return secretCreds === false ? false : secretCreds.password
const secretCreds = await Keychain.getGenericPassword({ service: 'secret' });
return secretCreds === false ? false : secretCreds.password;
}
export async function loadPassportData() {
const passportDataCreds = await Keychain.getGenericPassword({ service: "passportData" });
return passportDataCreds === false ? false : passportDataCreds.password
const passportDataCreds = await Keychain.getGenericPassword({
service: 'passportData',
});
return passportDataCreds === false ? false : passportDataCreds.password;
}
export async function storePassportData(passportData: PassportData) {
await Keychain.setGenericPassword("passportData", JSON.stringify(passportData), { service: "passportData" });
}
await Keychain.setGenericPassword(
'passportData',
JSON.stringify(passportData),
{ service: 'passportData' },
);
}

View File

@@ -1,38 +1,32 @@
import { NativeModules, Platform } from 'react-native';
// @ts-ignore
import PassportReader from 'react-native-passport-reader';
import { checkInputs } from '../utils/utils';
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 { Buffer } from 'buffer';
import PassportReader from 'react-native-passport-reader';
import { PassportData } from '../../../common/src/utils/types';
import useNavigationStore from '../stores/navigationStore';
import useUserStore from '../stores/userStore';
import { checkInputs } from '../utils/utils';
import { parsePassportData } from './parsePassportData';
export const scan = async (setModalProofStep: (modalProofStep: number) => void) => {
const {
passportNumber,
dateOfBirth,
dateOfExpiry,
} = useUserStore.getState()
export const scan = async (
setModalProofStep: (modalProofStep: number) => void,
) => {
const { passportNumber, dateOfBirth, dateOfExpiry } = useUserStore.getState();
const { toast } = useNavigationStore.getState();
const check = checkInputs(
passportNumber,
dateOfBirth,
dateOfExpiry
);
const check = checkInputs(passportNumber, dateOfBirth, dateOfExpiry);
if (!check.success) {
amplitude.track('inputs_invalid', { error: check.message });
toast.show("Unvailable", {
toast.show('Unvailable', {
message: check.message,
customData: {
type: "info",
type: 'info',
},
})
});
return;
}
@@ -45,12 +39,10 @@ export const scan = async (setModalProofStep: (modalProofStep: number) => void)
}
};
const scanAndroid = async (setModalProofStep: (modalProofStep: number) => void) => {
const {
passportNumber,
dateOfBirth,
dateOfExpiry,
} = useUserStore.getState()
const scanAndroid = async (
setModalProofStep: (modalProofStep: number) => void,
) => {
const { passportNumber, dateOfBirth, dateOfExpiry } = useUserStore.getState();
const { toast, setNfcSheetIsOpen } = useNavigationStore.getState();
setNfcSheetIsOpen(true);
@@ -58,7 +50,7 @@ const scanAndroid = async (setModalProofStep: (modalProofStep: number) => void)
const response = await PassportReader.scan({
documentNumber: passportNumber,
dateOfBirth: dateOfBirth,
dateOfExpiry: dateOfExpiry
dateOfExpiry: dateOfExpiry,
});
console.log('scanned');
setNfcSheetIsOpen(false);
@@ -68,32 +60,29 @@ const scanAndroid = async (setModalProofStep: (modalProofStep: number) => void)
console.log('error during scan:', e);
setNfcSheetIsOpen(false);
amplitude.track('nfc_scan_unsuccessful', { error: e.message });
if (e.message.includes("InvalidMRZKey")) {
if (e.message.includes('InvalidMRZKey')) {
toast.show('Error', {
message: "Go to previous screen and rescan your passport with the camera",
message:
'Go to previous screen and rescan your passport with the camera',
customData: {
type: "error",
type: 'error',
},
timeout: 5000,
})
useNavigationStore.getState().setSelectedTab("scan");
});
useNavigationStore.getState().setSelectedTab('scan');
} else {
toast.show('Error', {
message: e.message,
customData: {
type: "error",
type: 'error',
},
})
});
}
}
};
const scanIOS = async (setModalProofStep: (modalProofStep: number) => void) => {
const {
passportNumber,
dateOfBirth,
dateOfExpiry
} = useUserStore.getState()
const { passportNumber, dateOfBirth, dateOfExpiry } = useUserStore.getState();
const { toast } = useNavigationStore.getState();
console.log('passportNumber', passportNumber);
@@ -104,7 +93,7 @@ const scanIOS = async (setModalProofStep: (modalProofStep: number) => void) => {
const response = await NativeModules.PassportReader.scanPassport(
passportNumber,
dateOfBirth,
dateOfExpiry
dateOfExpiry,
);
console.log('scanned');
handleResponseIOS(response, setModalProofStep);
@@ -120,63 +109,81 @@ const scanIOS = async (setModalProofStep: (modalProofStep: number) => void) => {
// },
// })
// }
if (e.message.includes("InvalidMRZKey")) {
if (e.message.includes('InvalidMRZKey')) {
toast.show('Error', {
message: "Go to previous screen and rescan your passport with the camera",
message:
'Go to previous screen and rescan your passport with the camera',
customData: {
type: "error",
type: 'error',
},
timeout: 5000,
})
useNavigationStore.getState().setSelectedTab("scan");
});
useNavigationStore.getState().setSelectedTab('scan');
} else {
toast.show('Error', {
message: e.message,
customData: {
type: "error",
type: 'error',
},
})
});
}
}
};
const handleResponseIOS = async (
response: any,
setModalProofStep: (modalProofStep: number) => void
setModalProofStep: (modalProofStep: number) => void,
) => {
const { toast } = useNavigationStore.getState();
const parsed = JSON.parse(response);
const dgHashesObj = JSON.parse(parsed?.dataGroupHashes)
const dg1HashString = dgHashesObj?.DG1?.sodHash
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'))
const dg2HashString = dgHashesObj?.DG2?.sodHash
const dg2Hash = Array.from(Buffer.from(dg2HashString, 'hex'))
const dgHashesObj = JSON.parse(parsed?.dataGroupHashes);
const dg1HashString = dgHashesObj?.DG1?.sodHash;
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'));
const dg2HashString = dgHashesObj?.DG2?.sodHash;
const dg2Hash = Array.from(Buffer.from(dg2HashString, 'hex'));
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 mrz = parsed?.passportMRZ;
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('encapsulatedContentDigestAlgorithm', parsed?.encapsulatedContentDigestAlgorithm)
console.log('documentSigningCertificate', parsed?.documentSigningCertificate)
const pem = JSON.parse(parsed?.documentSigningCertificate).PEM.replace(/\n/g, '');
console.log('pem', pem)
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(
'encapsulatedContentDigestAlgorithm',
parsed?.encapsulatedContentDigestAlgorithm,
);
console.log('documentSigningCertificate', parsed?.documentSigningCertificate);
const pem = JSON.parse(parsed?.documentSigningCertificate).PEM.replace(
/\n/g,
'',
);
console.log('pem', pem);
const eContentArray = Array.from(Buffer.from(signedAttributes, 'base64'));
const signedEContentArray = eContentArray.map(byte => byte > 127 ? byte - 256 : byte);
const signedEContentArray = eContentArray.map(byte =>
byte > 127 ? byte - 256 : byte,
);
const concatenatedDataHashesArray = Array.from(Buffer.from(eContentBase64, 'base64'));
const concatenatedDataHashesArraySigned = concatenatedDataHashesArray.map(byte => byte > 127 ? byte - 256 : byte);
const concatenatedDataHashesArray = Array.from(
Buffer.from(eContentBase64, 'base64'),
);
const concatenatedDataHashesArraySigned = concatenatedDataHashesArray.map(
byte => (byte > 127 ? byte - 256 : byte),
);
const encryptedDigestArray = Array.from(Buffer.from(signatureBase64, 'base64')).map(byte => byte > 127 ? byte - 256 : byte);
const encryptedDigestArray = Array.from(
Buffer.from(signatureBase64, 'base64'),
).map(byte => (byte > 127 ? byte - 256 : byte));
// amplitude.track('nfc_response_parsed', {
// dataGroupsPresent: parsed?.dataGroupsPresent,
@@ -199,30 +206,30 @@ const handleResponseIOS = async (
eContent: concatenatedDataHashesArraySigned,
signedAttr: signedEContentArray,
encryptedDigest: encryptedDigestArray,
photoBase64: "data:image/jpeg;base64," + parsed.passportPhoto,
mockUser: false
photoBase64: 'data:image/jpeg;base64,' + parsed.passportPhoto,
mockUser: false,
};
const parsedPassportData = parsePassportData(passportData);
amplitude.track('nfc_response_parsed', parsedPassportData);
try {
useUserStore.getState().registerPassportData(passportData)
useNavigationStore.getState().setSelectedTab("next");
useUserStore.getState().registerPassportData(passportData);
useNavigationStore.getState().setSelectedTab('next');
} catch (e: any) {
console.log('error during parsing:', e);
amplitude.track('error_parsing_nfc_response', { error: e.message });
toast.show('Error', {
message: e.message,
customData: {
type: "error",
type: 'error',
},
})
});
}
};
const handleResponseAndroid = async (
response: any,
setModalProofStep: (modalProofStep: number) => void
setModalProofStep: (modalProofStep: number) => void,
) => {
const { toast } = useNavigationStore.getState();
@@ -238,14 +245,17 @@ const handleResponseAndroid = async (
unicodeVersion,
encapContent,
documentSigningCertificate,
dataGroupHashes
dataGroupHashes,
} = response;
const dgHashesObj = JSON.parse(dataGroupHashes);
const dg1HashString = dgHashesObj["1"];
const dg1HashString = dgHashesObj['1'];
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'));
const dg2Hash = dgHashesObj["2"];
const pem = "-----BEGIN CERTIFICATE-----" + documentSigningCertificate + "-----END CERTIFICATE-----"
const dg2Hash = dgHashesObj['2'];
const pem =
'-----BEGIN CERTIFICATE-----' +
documentSigningCertificate +
'-----END CERTIFICATE-----';
const dgPresents = Object.keys(dgHashesObj)
.map(key => parseInt(key))
@@ -262,26 +272,36 @@ const handleResponseAndroid = async (
signedAttr: JSON.parse(eContent),
encryptedDigest: JSON.parse(encryptedDigest),
photoBase64: photo.base64,
mockUser: false
mockUser: false,
};
console.log('passportData', JSON.stringify({
...passportData,
photoBase64: passportData.photoBase64.substring(0, 100) + '...'
}, null, 2));
console.log(
'passportData',
JSON.stringify(
{
...passportData,
photoBase64: passportData.photoBase64.substring(0, 100) + '...',
},
null,
2,
),
);
console.log('mrz', passportData?.mrz);
console.log('dataGroupHashes', passportData?.eContent);
console.log('eContent', passportData?.eContent);
console.log('encryptedDigest', passportData?.encryptedDigest);
console.log("photoBase64", passportData?.photoBase64.substring(0, 100) + '...')
console.log("digestAlgorithm", digestAlgorithm)
console.log("signerInfoDigestAlgorithm", signerInfoDigestAlgorithm)
console.log("digestEncryptionAlgorithm", digestEncryptionAlgorithm)
console.log("LDSVersion", LDSVersion)
console.log("unicodeVersion", unicodeVersion)
console.log("encapContent", encapContent)
console.log("documentSigningCertificate", documentSigningCertificate)
console.log(
'photoBase64',
passportData?.photoBase64.substring(0, 100) + '...',
);
console.log('digestAlgorithm', digestAlgorithm);
console.log('signerInfoDigestAlgorithm', signerInfoDigestAlgorithm);
console.log('digestEncryptionAlgorithm', digestEncryptionAlgorithm);
console.log('LDSVersion', LDSVersion);
console.log('unicodeVersion', unicodeVersion);
console.log('encapContent', encapContent);
console.log('documentSigningCertificate', documentSigningCertificate);
const parsedPassportData = parsePassportData(passportData);
amplitude.track('nfc_response_parsed', parsedPassportData);
@@ -297,16 +317,16 @@ const handleResponseAndroid = async (
// });
try {
await useUserStore.getState().registerPassportData(passportData)
useNavigationStore.getState().setSelectedTab("next");
await useUserStore.getState().registerPassportData(passportData);
useNavigationStore.getState().setSelectedTab('next');
} catch (e: any) {
console.log('error during parsing:', e);
amplitude.track('error_parsing_nfc_response', { error: e.message });
toast.show('Error', {
message: e.message,
customData: {
type: "error",
type: 'error',
},
})
});
}
};

View File

@@ -1,19 +1,17 @@
import { NativeModules, Platform } from 'react-native';
import { parseProofAndroid } from './utils';
import RNFS from 'react-native-fs';
import * as amplitude from '@amplitude/analytics-react-native';
import { NativeModules, Platform } from 'react-native';
import RNFS from 'react-native-fs';
export const generateProof = async (
circuit: string,
inputs: any,
) => {
import { parseProofAndroid } from './utils';
export const generateProof = async (circuit: string, inputs: any) => {
console.log('launching generateProof function');
console.log('inputs in prover.ts', inputs);
console.log('circuit', circuit);
const zkey_path = `${RNFS.DocumentDirectoryPath}/${circuit}.zkey`
// Example: "/data/user/0/com.proofofpassportapp/files/register_sha256WithRSAEncryption_65537.zkey" on android
const dat_path = `${RNFS.DocumentDirectoryPath}/${circuit}.dat`
const zkey_path = `${RNFS.DocumentDirectoryPath}/${circuit}.zkey`;
const dat_path = `${RNFS.DocumentDirectoryPath}/${circuit}.dat`;
const witness_calculator = circuit;
@@ -29,7 +27,7 @@ export const generateProof = async (
zkey_path,
witness_calculator,
dat_path,
inputs
inputs,
);
// console.log('local proof:', response);
@@ -37,7 +35,7 @@ export const generateProof = async (
if (Platform.OS === 'android') {
const parsedResponse = parseProofAndroid(response);
console.log('parsedResponse', parsedResponse);
return formatProof(parsedResponse)
return formatProof(parsedResponse);
} else {
const parsedResponse = JSON.parse(response);
console.log('parsedResponse', parsedResponse);
@@ -45,7 +43,7 @@ export const generateProof = async (
return formatProof({
proof: parsedResponse.proof,
pub_signals: parsedResponse.inputs,
})
});
}
} catch (err: any) {
console.log('err', err);
@@ -63,24 +61,16 @@ export const generateProof = async (
export const formatProof = (rawProof: any): any => {
return {
proof: {
pi_a: [
rawProof.proof.a[0],
rawProof.proof.a[1],
"1"
],
pi_a: [rawProof.proof.a[0], rawProof.proof.a[1], '1'],
pi_b: [
[rawProof.proof.b[0][0], rawProof.proof.b[0][1]],
[rawProof.proof.b[1][0], rawProof.proof.b[1][1]],
["1", "0"]
['1', '0'],
],
pi_c: [
rawProof.proof.c[0],
rawProof.proof.c[1],
"1"
],
protocol: "groth16",
curve: "bn128"
pi_c: [rawProof.proof.c[0], rawProof.proof.c[1], '1'],
protocol: 'groth16',
curve: 'bn128',
},
publicSignals: (rawProof as any).pub_signals
}
}
publicSignals: (rawProof as any).pub_signals,
};
};

View File

@@ -1,162 +1,192 @@
import { NativeModules, Platform, Linking } from "react-native";
// import { AppType, reconstructAppType } from "../../../common/src/utils/appType";
import msgpack from 'msgpack-lite';
import pako from 'pako';
import { Linking, NativeModules, Platform } from 'react-native';
import { Mode, OpenPassportApp } from '../../../common/src/utils/appType';
import {
getCircuitNameOld,
parseCertificateSimple,
} from '../../../common/src/utils/certificate_parsing/parseCertificateSimple';
import useNavigationStore from '../stores/navigationStore';
import useUserStore from "../stores/userStore";
import { downloadZkey } from "./zkeyDownload";
import msgpack from "msgpack-lite";
import pako from "pako";
import { Mode, OpenPassportApp } from "../../../common/src/utils/appType";
import { getCircuitNameOld, parseCertificateSimple } from "../../../common/src/utils/certificate_parsing/parseCertificateSimple";
import useUserStore from '../stores/userStore';
import { downloadZkey } from './zkeyDownload';
const parseUrlParams = (url: string): Map<string, string> => {
const [, queryString] = url.split('?');
const params = new Map<string, string>();
if (queryString) {
queryString.split('&').forEach(pair => {
const [key, value] = pair.split('=');
params.set(key, decodeURIComponent(value));
});
}
return params;
const [, queryString] = url.split('?');
const params = new Map<string, string>();
if (queryString) {
queryString.split('&').forEach(pair => {
const [key, value] = pair.split('=');
params.set(key, decodeURIComponent(value));
});
}
return params;
};
export const scanQRCode = () => {
const { toast, setSelectedApp, setSelectedTab } = useNavigationStore.getState();
const { toast, setSelectedApp, setSelectedTab } =
useNavigationStore.getState();
Linking.getInitialURL().then((url) => {
if (url) {
handleUniversalLink(url);
} else {
if (Platform.OS === 'ios') {
console.log("Scanning QR code on iOS without Universal Link");
Linking.getInitialURL()
.then(url => {
if (url) {
handleUniversalLink(url);
} else {
if (Platform.OS === 'ios') {
console.log('Scanning QR code on iOS without Universal Link');
const qrScanner = NativeModules.QRScannerBridge;
if (qrScanner && qrScanner.scanQRCode) {
qrScanner.scanQRCode()
.then((result: string) => {
const params = parseUrlParams(result);
const encodedData = params.get('data');
handleQRCodeScan(encodedData as string, toast, setSelectedApp, setSelectedTab);
})
.catch((error: any) => {
console.error('QR Scanner Error:', error);
toast.show('Error', {
message: 'Failed to scan QR code',
type: 'error',
});
});
} else {
console.error('QR Scanner module not found for iOS');
toast.show('Error', {
message: 'QR Scanner not available',
type: 'error',
});
}
} else if (Platform.OS === 'android') {
const qrScanner = NativeModules.QRCodeScanner;
if (qrScanner && qrScanner.scanQRCode) {
qrScanner.scanQRCode()
.then((result: string) => {
const params = parseUrlParams(result);
const encodedData = params.get('data');
handleQRCodeScan(encodedData as string, toast, setSelectedApp, setSelectedTab);
})
.catch((error: any) => {
console.error('QR Scanner Error:', error);
toast.show('Error', {
message: 'Failed to scan QR code',
type: 'error',
});
});
} else {
console.error('QR Scanner module not found for Android');
toast.show('Error', {
message: 'QR Scanner not available',
type: 'error',
});
}
}
const qrScanner = NativeModules.QRScannerBridge;
if (qrScanner && qrScanner.scanQRCode) {
qrScanner
.scanQRCode()
.then((result: string) => {
const params = parseUrlParams(result);
const encodedData = params.get('data');
handleQRCodeScan(
encodedData as string,
toast,
setSelectedApp,
setSelectedTab,
);
})
.catch((error: any) => {
console.error('QR Scanner Error:', error);
toast.show('Error', {
message: 'Failed to scan QR code',
type: 'error',
});
});
} else {
console.error('QR Scanner module not found for iOS');
toast.show('Error', {
message: 'QR Scanner not available',
type: 'error',
});
}
} else if (Platform.OS === 'android') {
const qrScanner = NativeModules.QRCodeScanner;
if (qrScanner && qrScanner.scanQRCode) {
qrScanner
.scanQRCode()
.then((result: string) => {
const params = parseUrlParams(result);
const encodedData = params.get('data');
handleQRCodeScan(
encodedData as string,
toast,
setSelectedApp,
setSelectedTab,
);
})
.catch((error: any) => {
console.error('QR Scanner Error:', error);
toast.show('Error', {
message: 'Failed to scan QR code',
type: 'error',
});
});
} else {
console.error('QR Scanner module not found for Android');
toast.show('Error', {
message: 'QR Scanner not available',
type: 'error',
});
}
}
}).catch(err => {
console.error('An error occurred while getting initial URL', err);
toast.show('Error', {
message: 'Failed to process initial link',
type: 'error',
});
}
})
.catch(err => {
console.error('An error occurred while getting initial URL', err);
toast.show('Error', {
message: 'Failed to process initial link',
type: 'error',
});
});
};
const handleQRCodeScan = (result: string, toast: any, setSelectedApp: any, setSelectedTab: any) => {
try {
const dsc = useUserStore.getState().passportData?.dsc;
if (dsc) {
const handleQRCodeScan = (
result: string,
toast: any,
setSelectedApp: any,
setSelectedTab: any,
) => {
try {
const dsc = useUserStore.getState().passportData?.dsc;
if (dsc) {
const decodedResult = atob(result);
const uint8Array = new Uint8Array(
decodedResult.split('').map(char => char.charCodeAt(0)),
);
const decompressedData = pako.inflate(uint8Array);
const unpackedData = msgpack.decode(decompressedData);
const openPassportApp: OpenPassportApp = unpackedData;
setSelectedApp(openPassportApp);
const decodedResult = atob(result);
const uint8Array = new Uint8Array(decodedResult.split('').map(char => char.charCodeAt(0)));
const decompressedData = pako.inflate(uint8Array);
const unpackedData = msgpack.decode(decompressedData);
const openPassportApp: OpenPassportApp = unpackedData;
setSelectedApp(openPassportApp);
const parsedDsc = parseCertificateSimple(dsc);
const parsedDsc = parseCertificateSimple(dsc);
const circuitName =
openPassportApp.mode === 'vc_and_disclose'
? 'vc_and_disclose'
: getCircuitNameOld(
'prove' as Mode,
parsedDsc.signatureAlgorithm,
parsedDsc.hashAlgorithm,
);
downloadZkey(circuitName as any);
const circuitName = openPassportApp.mode === 'vc_and_disclose'
? 'vc_and_disclose'
: getCircuitNameOld("prove" as Mode, parsedDsc.signatureAlgorithm, parsedDsc.hashAlgorithm);
downloadZkey(circuitName as any);
setSelectedTab("prove");
toast.show('✅', {
message: "QR code scanned",
customData: {
type: "success",
},
});
}
else {
toast.show('Welcome', {
message: 'Please register your passport first',
type: 'info',
});
}
} catch (error) {
console.error('Error parsing QR code result:', error);
toast.show('Try again', {
message: "Error reading QR code: " + (error as Error).message,
customData: {
type: "error",
},
});
setSelectedTab('prove');
toast.show('✅', {
message: 'QR code scanned',
customData: {
type: 'success',
},
});
} else {
toast.show('Welcome', {
message: 'Please register your passport first',
type: 'info',
});
}
} catch (error) {
console.error('Error parsing QR code result:', error);
toast.show('Try again', {
message: 'Error reading QR code: ' + (error as Error).message,
customData: {
type: 'error',
},
});
}
};
const handleUniversalLink = (url: string) => {
const { toast, setSelectedApp, setSelectedTab } = useNavigationStore.getState();
const params = parseUrlParams(url);
const encodedData = params.get('data');
console.log("Encoded data:", encodedData);
if (encodedData) {
handleQRCodeScan(encodedData, toast, setSelectedApp, setSelectedTab);
} else {
console.error('No data found in the Universal Link');
toast.show('Error', {
message: 'Invalid link',
type: 'error',
});
}
const { toast, setSelectedApp, setSelectedTab } =
useNavigationStore.getState();
const params = parseUrlParams(url);
const encodedData = params.get('data');
console.log('Encoded data:', encodedData);
if (encodedData) {
handleQRCodeScan(encodedData, toast, setSelectedApp, setSelectedTab);
} else {
console.error('No data found in the Universal Link');
toast.show('Error', {
message: 'Invalid link',
type: 'error',
});
}
};
export const setupUniversalLinkListener = () => {
Linking.getInitialURL().then((url) => {
if (url) {
handleUniversalLink(url);
}
});
Linking.getInitialURL().then(url => {
if (url) {
handleUniversalLink(url);
}
});
const linkingEventListener = Linking.addEventListener('url', ({ url }) => {
handleUniversalLink(url);
});
const linkingEventListener = Linking.addEventListener('url', ({ url }) => {
handleUniversalLink(url);
});
return () => {
linkingEventListener.remove();
};
return () => {
linkingEventListener.remove();
};
};

View File

@@ -1,17 +1,26 @@
import axios from "axios";
import { COMMITMENT_TREE_TRACKER_URL, PASSPORT_ATTESTATION_ID } from "../../../common/src/constants/constants";
import { LeanIMT, LeanIMTHashFunction } from "@openpassport/zk-kit-lean-imt";
import { poseidon2, poseidon6 } from "poseidon-lite";
import { PassportData } from "../../../common/src/utils/types";
import { generateCommitment, getLeaf } from "../../../common/src/utils/pubkeyTree";
import { formatMrz, packBytes } from "../../../common/src/utils/utils";
import { findIndexInTree } from "../../../common/src/utils/generateInputs";
export async function isCommitmentRegistered(secret: string, passportData: PassportData) {
import { LeanIMT, LeanIMTHashFunction } from '@openpassport/zk-kit-lean-imt';
import axios from 'axios';
import { poseidon2 } from 'poseidon-lite';
import {
COMMITMENT_TREE_TRACKER_URL,
PASSPORT_ATTESTATION_ID,
} from '../../../common/src/constants/constants';
import { findIndexInTree } from '../../../common/src/utils/generateInputs';
import {
generateCommitment,
getLeaf,
} from '../../../common/src/utils/pubkeyTree';
import { PassportData } from '../../../common/src/utils/types';
import { formatMrz, packBytes } from '../../../common/src/utils/utils';
export async function isCommitmentRegistered(
secret: string,
passportData: PassportData,
) {
let response;
console.log(COMMITMENT_TREE_TRACKER_URL)
console.log(COMMITMENT_TREE_TRACKER_URL);
try {
response = await axios.get(COMMITMENT_TREE_TRACKER_URL);
} catch (error) {
@@ -20,21 +29,28 @@ export async function isCommitmentRegistered(secret: string, passportData: Passp
}
console.log('response.data:', response.data);
const hashFunction: LeanIMTHashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
const hashFunction: LeanIMTHashFunction = (a: bigint, b: bigint) =>
poseidon2([a, b]);
const imt = LeanIMT.import(hashFunction, response.data);
const pubkey_leaf = getLeaf(passportData.dsc);
const formattedMrz = formatMrz(passportData.mrz);
const mrz_bytes = packBytes(formattedMrz);
const commitment = generateCommitment(secret, PASSPORT_ATTESTATION_ID, pubkey_leaf, mrz_bytes, passportData.dg2Hash?.map((x) => x.toString()) || []);
const commitment = generateCommitment(
secret,
PASSPORT_ATTESTATION_ID,
pubkey_leaf,
mrz_bytes,
passportData.dg2Hash?.map(x => x.toString()) || [],
);
console.log('commitment', commitment.toString());
try {
findIndexInTree(imt as any, commitment); // this will throw if not found
return true
return true;
} catch (err) {
return false;
}
}
}

View File

@@ -1,28 +1,32 @@
// utils from snarkjs copied here before snarkjs imports node crypto
function unstringifyBigInts(o: any): any {
if (typeof o == "string" && /^[0-9]+$/.test(o)) {
return BigInt(o);
} else if (typeof o == "string" && /^0x[0-9a-fA-F]+$/.test(o)) {
return BigInt(o);
if (typeof o === 'string' && /^[0-9]+$/.test(o)) {
return BigInt(o);
} else if (typeof o === 'string' && /^0x[0-9a-fA-F]+$/.test(o)) {
return BigInt(o);
} else if (Array.isArray(o)) {
return o.map(unstringifyBigInts);
} else if (typeof o == "object") {
if (o === null) return null;
const res: any = {};
const keys = Object.keys(o);
keys.forEach((k) => {
res[k] = unstringifyBigInts(o[k]);
});
return res;
return o.map(unstringifyBigInts);
} else if (typeof o === 'object') {
if (o === null) {
return null;
}
const res: any = {};
const keys = Object.keys(o);
keys.forEach(k => {
res[k] = unstringifyBigInts(o[k]);
});
return res;
} else {
return o;
return o;
}
}
function p256(n: any) {
let nstr = n.toString(16);
while (nstr.length < 64) nstr = "0"+nstr;
while (nstr.length < 64) {
nstr = '0' + nstr;
}
nstr = `"0x${nstr}"`;
return nstr;
}
@@ -31,16 +35,21 @@ export default function groth16ExportSolidityCallData(_proof: any, _pub: any) {
const proof = unstringifyBigInts(_proof);
const pub = unstringifyBigInts(_pub);
let inputs = "";
for (let i=0; i<pub.length; i++) {
if (inputs != "") inputs = inputs + ",";
inputs = inputs + p256(pub[i]);
let inputs = '';
for (let i = 0; i < pub.length; i++) {
if (inputs !== '') {
inputs = inputs + ',';
}
inputs = inputs + p256(pub[i]);
}
const S =`[${p256(proof.a[0])}, ${p256(proof.a[1])}],` +
`[[${p256(proof.b[0][1])}, ${p256(proof.b[0][0])}],[${p256(proof.b[1][1])}, ${p256(proof.b[1][0])}]],` +
const S =
`[${p256(proof.a[0])}, ${p256(proof.a[1])}],` +
`[[${p256(proof.b[0][1])}, ${p256(proof.b[0][0])}],[${p256(
proof.b[1][1],
)}, ${p256(proof.b[1][0])}]],` +
`[${p256(proof.c[0])}, ${p256(proof.c[1])}],` +
`[${inputs}]`;
return S;
}
}

View File

@@ -1,21 +1,34 @@
import { ethers } from "ethers";
import axios from 'axios';
import { ethers } from 'ethers';
import {
CHAIN_NAME,
RELAYER_URL,
RPC_URL,
SignatureAlgorithmIndex,
} from '../../../common/src/constants/constants';
import {
formatCallData_disclose,
formatCallData_dsc,
formatCallData_register,
} from '../../../common/src/utils/formatCallData';
import { Proof } from '../../../common/src/utils/types';
import registerArtefacts from '../../deployments/artifacts/Deploy_Registry#OpenPassportRegister.json';
import sbtArtefacts from '../../deployments/artifacts/Deploy_Registry#SBT.json';
import contractAddresses from '../../deployments/deployed_addresses.json';
import groth16ExportSolidityCallData from './snarkjs';
import contractAddresses from "../../deployments/deployed_addresses.json";
import registerArtefacts from "../../deployments/artifacts/Deploy_Registry#OpenPassportRegister.json";
import sbtArtefacts from "../../deployments/artifacts/Deploy_Registry#SBT.json";
import { CHAIN_NAME, RELAYER_URL, RPC_URL, SignatureAlgorithmIndex } from '../../../common/src/constants/constants';
import { Proof } from "../../../common/src/utils/types";
import { formatCallData_disclose, formatCallData_dsc, formatCallData_register } from "../../../common/src/utils/formatCallData";
export const sendRegisterTransaction = async (
proof: Proof,
cscaProof: Proof,
sigAlgIndex: SignatureAlgorithmIndex
sigAlgIndex: SignatureAlgorithmIndex,
) => {
const provider = new ethers.JsonRpcProvider(RPC_URL);
if (!contractAddresses["Deploy_Registry#OpenPassportRegister"] || !registerArtefacts.abi) {
if (
!contractAddresses['Deploy_Registry#OpenPassportRegister'] ||
!registerArtefacts.abi
) {
console.log('contracts addresses or abi not found');
return;
}
@@ -25,11 +38,14 @@ export const sendRegisterTransaction = async (
const cd = groth16ExportSolidityCallData(proof.proof, proof.pub_signals);
const callData = JSON.parse(`[${cd}]`);
//console.log('callData', callData);
const formattedCallData_register = formatCallData_register(callData)
const formattedCallData_register = formatCallData_register(callData);
console.log('formattedCallData_register', formattedCallData_register);
//console.log("exporting csca proof", cscaProof, cscaProof.proof, cscaProof.pub_signals)
const cd_csca = groth16ExportSolidityCallData(cscaProof.proof, cscaProof.pub_signals);
const cd_csca = groth16ExportSolidityCallData(
cscaProof.proof,
cscaProof.pub_signals,
);
const callData_csca = JSON.parse(`[${cd_csca}]`);
//console.log('callData_csca', callData_csca);
const formattedCallData_csca = formatCallData_dsc(callData_csca);
@@ -37,18 +53,23 @@ export const sendRegisterTransaction = async (
try {
const registerContract = new ethers.Contract(
contractAddresses["Deploy_Registry#OpenPassportRegister"],
contractAddresses['Deploy_Registry#OpenPassportRegister'],
registerArtefacts.abi,
provider
provider,
);
const transactionRequest = await registerContract
.validateProof.populateTransaction(formattedCallData_register, formattedCallData_csca, sigAlgIndex, sigAlgIndex);
const transactionRequest =
await registerContract.validateProof.populateTransaction(
formattedCallData_register,
formattedCallData_csca,
sigAlgIndex,
sigAlgIndex,
);
console.log('transactionRequest', transactionRequest);
const response = await axios.post(RELAYER_URL, {
chain: CHAIN_NAME,
tx_data: transactionRequest
tx_data: transactionRequest,
});
console.log('response status', response.status);
console.log('response data', response.data);
@@ -71,12 +92,10 @@ export const sendRegisterTransaction = async (
}
};
export const mintSBT = async (
proof: Proof,
) => {
export const mintSBT = async (proof: Proof) => {
const provider = new ethers.JsonRpcProvider(RPC_URL);
if (!contractAddresses["Deploy_Registry#SBT"] || !sbtArtefacts.abi) {
if (!contractAddresses['Deploy_Registry#SBT'] || !sbtArtefacts.abi) {
console.log('contracts addresses or abi not found');
return;
}
@@ -86,22 +105,26 @@ export const mintSBT = async (
const parsedCallData_disclose = JSON.parse(`[${cd}]`);
console.log('parsedCallData_disclose', parsedCallData_disclose);
const formattedCallData_disclose = formatCallData_disclose(parsedCallData_disclose);
const formattedCallData_disclose = formatCallData_disclose(
parsedCallData_disclose,
);
try {
const proofOfPassportContract = new ethers.Contract(
contractAddresses["Deploy_Registry#SBT"],
contractAddresses['Deploy_Registry#SBT'],
sbtArtefacts.abi,
provider
provider,
);
const transactionRequest = await proofOfPassportContract
.mint.populateTransaction(formattedCallData_disclose);
const transactionRequest =
await proofOfPassportContract.mint.populateTransaction(
formattedCallData_disclose,
);
console.log('transactionRequest', transactionRequest);
const response = await axios.post(RELAYER_URL, {
chain: CHAIN_NAME,
tx_data: transactionRequest
tx_data: transactionRequest,
});
console.log('response status', response.status);
console.log('response data', response.data);
@@ -122,4 +145,4 @@ export const mintSBT = async (
}
}
}
};
};

View File

@@ -1,8 +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";
import { getCountryCodeFromMrz } from "./parsePassportData";
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) {
@@ -34,11 +33,10 @@ export function formatDateToYYMMDD(inputDate: string) {
return year + month + day;
}
export const ModalProofSteps = {
MODAL_REQUEST_SENT: 1,
MODAL_SERVER_ERROR: 2,
MODAL_SERVER_SUCCESS: 3
MODAL_SERVER_SUCCESS: 3,
};
export function formatAttribute(key: string, attribute: string) {
@@ -48,9 +46,8 @@ export function formatAttribute(key: string, attribute: string) {
const day = attribute.substring(4, 6);
return `${year}-${month}-${day}`; // ISO 8601 format (YYYY-MM-DD)
} else if (key === 'nationality' && attribute in countryCodes) {
return countryCodes[attribute as keyof typeof countryCodes]
}
else if (key === 'date_of_birth') {
return countryCodes[attribute as keyof typeof countryCodes];
} else if (key === 'date_of_birth') {
let year = '19' + attribute.substring(0, 2);
const currentYear = 2024;
const birthYear = parseInt(year);
@@ -65,9 +62,13 @@ export function formatAttribute(key: string, attribute: string) {
}
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=\[(.*?)\]\)/);
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');
if (!match) {
throw new Error('Invalid input format');
}
const [, pi_a, pi_b_1, pi_b_2, pi_c, pub_signals] = match;
@@ -80,41 +81,41 @@ export const parseProofAndroid = (response: string) => {
],
c: pi_c.split(',').map((n: string) => n.trim()),
},
pub_signals: pub_signals.split(',').map((n: string) => n.trim())
pub_signals: pub_signals.split(',').map((n: string) => n.trim()),
} as Proof;
};
export function getFirstName(mrz: string): string {
const names = mrz.split("<<");
const firstName = names[1].split("<")[0].trim();
const names = mrz.split('<<');
const firstName = names[1].split('<')[0].trim();
const capitalized = firstName.charAt(0) + firstName.slice(1).toLowerCase();
return capitalized || "Unknown";
return capitalized || 'Unknown';
}
export function checkInputs(
passportNumber: string,
dateOfBirth: string,
dateOfExpiry: string,
): { success: boolean, message: string } {
): { success: boolean; message: string } {
// if (passportNumber.length !== 9) {
// throw new Error('Passport number must be 9 characters long');
// }
if (dateOfBirth.length !== 6) {
return {
success: false,
message: 'Complete Step 1 first'
message: 'Complete Step 1 first',
};
}
if (dateOfExpiry.length !== 6) {
return {
success: false,
message: 'Date of expiry must be 6 characters long'
message: 'Date of expiry must be 6 characters long',
};
}
return {
success: true,
message: ''
message: '',
};
}
@@ -124,4 +125,4 @@ export const maskString = (input: string): string => {
} else {
return input.charAt(0) + input.charAt(1) + '*'.repeat(input.length - 2);
}
}
};

View File

@@ -6,32 +6,40 @@ import { unzip } from 'react-native-zip-archive';
import useNavigationStore from '../stores/navigationStore';
const zkeyZipUrls = {
prove_rsa_65537_sha256: "https://d8o9bercqupgk.cloudfront.net/staging/prove_rsa_65537_sha256.zkey.zip",
prove_rsa_65537_sha1: "https://d8o9bercqupgk.cloudfront.net/staging/prove_rsa_65537_sha1.zkey.zip",
prove_rsapss_65537_sha256: "https://d8o9bercqupgk.cloudfront.net/staging/prove_rsapss_65537_sha256.zkey.zip",
prove_rsa_65537_sha256:
'https://d8o9bercqupgk.cloudfront.net/staging/prove_rsa_65537_sha256.zkey.zip',
prove_rsa_65537_sha1:
'https://d8o9bercqupgk.cloudfront.net/staging/prove_rsa_65537_sha1.zkey.zip',
prove_rsapss_65537_sha256:
'https://d8o9bercqupgk.cloudfront.net/staging/prove_rsapss_65537_sha256.zkey.zip',
// register_sha256WithRSAEncryption_65537: "https://d8o9bercqupgk.cloudfront.net/staging/register_sha256WithRSAEncryption_65537_csca2.zkey.zip",
// disclose: "https://d8o9bercqupgk.cloudfront.net/staging/disclose3.zkey.zip",
vc_and_disclose: "https://d8o9bercqupgk.cloudfront.net/staging/vc_and_disclose.zkey.zip",
vc_and_disclose:
'https://d8o9bercqupgk.cloudfront.net/staging/vc_and_disclose.zkey.zip',
};
const datZipUrls = {
prove_rsa_65537_sha256: "https://d8o9bercqupgk.cloudfront.net/staging/prove_rsa_65537_sha256.dat.zip",
prove_rsa_65537_sha1: "https://d8o9bercqupgk.cloudfront.net/staging/prove_rsa_65537_sha1.dat.zip",
prove_rsapss_65537_sha256: "https://d8o9bercqupgk.cloudfront.net/staging/prove_rsapss_65537_sha256.dat.zip",
vc_and_disclose: "https://d8o9bercqupgk.cloudfront.net/staging/vc_and_disclose.dat.zip",
prove_rsa_65537_sha256:
'https://d8o9bercqupgk.cloudfront.net/staging/prove_rsa_65537_sha256.dat.zip',
prove_rsa_65537_sha1:
'https://d8o9bercqupgk.cloudfront.net/staging/prove_rsa_65537_sha1.dat.zip',
prove_rsapss_65537_sha256:
'https://d8o9bercqupgk.cloudfront.net/staging/prove_rsapss_65537_sha256.dat.zip',
vc_and_disclose:
'https://d8o9bercqupgk.cloudfront.net/staging/vc_and_disclose.dat.zip',
};
export type CircuitName = keyof typeof zkeyZipUrls;
export type ShowWarningModalProps = {
show: boolean,
circuit: CircuitName | "",
size: number,
}
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
@@ -43,50 +51,51 @@ export type IsZkeyDownloading = {
// 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(
circuit: CircuitName,
) {
const {
isZkeyDownloading,
update
} = useNavigationStore.getState();
export async function downloadZkey(circuit: CircuitName) {
const { isZkeyDownloading, update } = useNavigationStore.getState();
const downloadRequired = await isDownloadRequired(circuit, isZkeyDownloading);
if (!downloadRequired) {
console.log(`zkey and dat for ${circuit} already downloaded`)
console.log(`zkey and dat for ${circuit} already downloaded`);
amplitude.track('zkey_already_downloaded', { circuit: circuit });
return;
}
const networkInfo = await NetInfo.fetch();
console.log('Network type:', networkInfo.type)
console.log('Network type:', networkInfo.type);
if (networkInfo.type === 'wifi') {
fetchZkeyAndDat(circuit);
} else {
const zkeyResponse = await axios.head(zkeyZipUrls[circuit]);
const datResponse = await axios.head(datZipUrls[circuit]);
const expectedSize = parseInt(zkeyResponse.headers['content-length'], 10) + parseInt(datResponse.headers['content-length'], 10);
const expectedSize =
parseInt(zkeyResponse.headers['content-length'], 10) +
parseInt(datResponse.headers['content-length'], 10);
update({
showWarningModal: {
show: true,
circuit: circuit,
size: expectedSize,
}
},
});
}
}
export async function isDownloadRequired(
circuit: CircuitName,
isZkeyDownloading: IsZkeyDownloading
isZkeyDownloading: IsZkeyDownloading,
) {
if (isZkeyDownloading[circuit]) {
return false;
}
const zkeyExists = await RNFS.exists(`${RNFS.DocumentDirectoryPath}/${circuit}.zkey`);
const datExists = await RNFS.exists(`${RNFS.DocumentDirectoryPath}/${circuit}.dat`);
const zkeyExists = await RNFS.exists(
`${RNFS.DocumentDirectoryPath}/${circuit}.zkey`,
);
const datExists = await RNFS.exists(
`${RNFS.DocumentDirectoryPath}/${circuit}.dat`,
);
if (!zkeyExists || !datExists) {
return true;
}
@@ -94,10 +103,22 @@ export async function isDownloadRequired(
let storedZkeyZipSize = 0;
let storedDatZipSize = 0;
try {
storedZkeyZipSize = Number(await RNFS.readFile(`${RNFS.DocumentDirectoryPath}/${circuit}_zkey_zip_size.txt`, 'utf8'));
storedDatZipSize = Number(await RNFS.readFile(`${RNFS.DocumentDirectoryPath}/${circuit}_dat_zip_size.txt`, 'utf8'));
storedZkeyZipSize = Number(
await RNFS.readFile(
`${RNFS.DocumentDirectoryPath}/${circuit}_zkey_zip_size.txt`,
'utf8',
),
);
storedDatZipSize = Number(
await RNFS.readFile(
`${RNFS.DocumentDirectoryPath}/${circuit}_dat_zip_size.txt`,
'utf8',
),
);
} catch (error) {
console.log(`Size files not found for ${circuit}, assuming files are outdated.`);
console.log(
`Size files not found for ${circuit}, assuming files are outdated.`,
);
return true;
}
@@ -106,27 +127,24 @@ export async function isDownloadRequired(
const expectedZkeySize = parseInt(zkeyResponse.headers['content-length'], 10);
const expectedDatSize = parseInt(datResponse.headers['content-length'], 10);
return storedZkeyZipSize !== expectedZkeySize || storedDatZipSize !== expectedDatSize;
return (
storedZkeyZipSize !== expectedZkeySize ||
storedDatZipSize !== expectedDatSize
);
}
export async function fetchZkeyAndDat(
circuit: CircuitName,
) {
console.log(`fetching zkey and dat for ${circuit} ...`)
export async function fetchZkeyAndDat(circuit: CircuitName) {
console.log(`fetching zkey and dat for ${circuit} ...`);
amplitude.track('fetching_zkey_and_dat', { circuit: circuit });
const {
isZkeyDownloading,
toast,
update,
setZkeyDownloadedPercentage
} = useNavigationStore.getState();
const { isZkeyDownloading, toast, update, setZkeyDownloadedPercentage } =
useNavigationStore.getState();
update({
isZkeyDownloading: {
...isZkeyDownloading,
[circuit]: true,
}
},
});
const startTime = Date.now();
@@ -142,22 +160,26 @@ export async function fetchZkeyAndDat(
},
progress: (res: any) => {
if (fileName.endsWith('.zkey.zip')) {
const percentComplete = Math.floor((res.bytesWritten / res.contentLength) * 100);
if (percentComplete % 5 === 0 && percentComplete !== previousPercentComplete) {
const percentComplete = Math.floor(
(res.bytesWritten / res.contentLength) * 100,
);
if (
percentComplete % 5 === 0 &&
percentComplete !== previousPercentComplete
) {
console.log(`${percentComplete}%`);
previousPercentComplete = percentComplete;
setZkeyDownloadedPercentage(percentComplete);
}
}
}
},
};
await RNFS.downloadFile(options).promise;
console.log(`Download of ${fileName} complete`);
RNFS.readDir(RNFS.DocumentDirectoryPath)
.then((result) => {
console.log('Directory contents before unzipping:', result);
})
RNFS.readDir(RNFS.DocumentDirectoryPath).then(result => {
console.log('Directory contents before unzipping:', result);
});
};
try {
@@ -171,10 +193,14 @@ export async function fetchZkeyAndDat(
isZkeyDownloading: {
...isZkeyDownloading,
[circuit]: false,
}
},
});
console.log('zkey and dat download succeeded, took ' + ((Date.now() - startTime) / 1000) + ' seconds');
console.log(
'zkey and dat download succeeded, took ' +
(Date.now() - startTime) / 1000 +
' seconds',
);
await saveFileSize(circuit, 'zkey');
await saveFileSize(circuit, 'dat');
@@ -185,19 +211,18 @@ export async function fetchZkeyAndDat(
const result = await RNFS.readDir(RNFS.DocumentDirectoryPath);
console.log('Directory contents at the end:', result);
} catch (error: any) {
console.error(error);
update({
isZkeyDownloading: {
...isZkeyDownloading,
[circuit]: false,
}
},
});
toast.show('Error', {
message: `Error: ${error.message}`,
customData: {
type: "error",
type: 'error',
},
});
}
@@ -205,7 +230,10 @@ export async function fetchZkeyAndDat(
async function unzipFile(circuit: CircuitName, fileType: 'zkey' | 'dat') {
const unzipPath = `${RNFS.DocumentDirectoryPath}/${circuit}_temp`;
await unzip(`${RNFS.DocumentDirectoryPath}/${circuit}.${fileType}.zip`, unzipPath);
await unzip(
`${RNFS.DocumentDirectoryPath}/${circuit}.${fileType}.zip`,
unzipPath,
);
const files = await RNFS.readDir(unzipPath);
const targetFile = files.find(file => file.name.endsWith(`.${fileType}`));
if (targetFile) {
@@ -217,13 +245,21 @@ async function unzipFile(circuit: CircuitName, fileType: 'zkey' | 'dat') {
await RNFS.moveFile(targetFile.path, destinationPath);
console.log(`File moved to ${circuit}.${fileType}`);
} else {
throw new Error(`${fileType.toUpperCase()} file not found in the unzipped directory`);
throw new Error(
`${fileType.toUpperCase()} file not found in the unzipped directory`,
);
}
await RNFS.unlink(unzipPath);
}
async function saveFileSize(circuit: CircuitName, fileType: 'zkey' | 'dat') {
const zipSize = await RNFS.stat(`${RNFS.DocumentDirectoryPath}/${circuit}.${fileType}.zip`);
await RNFS.writeFile(`${RNFS.DocumentDirectoryPath}/${circuit}_${fileType}_zip_size.txt`, zipSize.size.toString(), 'utf8');
const zipSize = await RNFS.stat(
`${RNFS.DocumentDirectoryPath}/${circuit}.${fileType}.zip`,
);
await RNFS.writeFile(
`${RNFS.DocumentDirectoryPath}/${circuit}_${fileType}_zip_size.txt`,
zipSize.size.toString(),
'utf8',
);
console.log(`${fileType} zip size written to file`);
}
}

View File

@@ -1,15 +1,15 @@
import { config } from '@tamagui/config/v3'
import { createTamagui } from 'tamagui' // or '@tamagui/core'
import { config } from '@tamagui/config/v3';
import { createTamagui } from 'tamagui'; // or '@tamagui/core'
const appConfig = createTamagui(config)
const appConfig = createTamagui(config);
export type AppConfig = typeof appConfig
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 { }
interface TamaguiCustomConfig extends AppConfig {}
}
export default appConfig
export default appConfig;

View File

@@ -1,13 +1,7 @@
{
"extends": "@react-native/typescript-config/tsconfig.json",
"compilerOptions": {
"lib": [
"dom",
"esnext"
]
"lib": ["dom", "esnext"]
},
"typeRoots": [
"./node_modules/@types",
"./src/types"
]
}
"typeRoots": ["./node_modules/@types", "./src/types"]
}