refactoring App.tsx into multiple files

This commit is contained in:
0xturboblitz
2024-03-23 15:53:30 -07:00
parent 941e4e7ac2
commit a24ea1c3ea
7 changed files with 562 additions and 439 deletions

View File

@@ -2,39 +2,26 @@ import React, { useEffect, useState } from 'react';
import {
NativeModules,
DeviceEventEmitter,
Platform,
} from 'react-native';
import Toast, { BaseToast, ErrorToast, SuccessToast, ToastProps } from 'react-native-toast-message';
// @ts-ignore
import PassportReader from 'react-native-passport-reader';
import { checkInputs } from './utils/utils';
import Toast from 'react-native-toast-message';
import {
DEFAULT_PNUMBER,
DEFAULT_DOB,
DEFAULT_DOE
} from '@env';
import { PassportData } from '../common/src/utils/types';
import { revealBitmapFromMapping } from '../common/src/utils/revealBitmap';
import { toStandardName } from '../common/src/utils/formatNames';
import { generateCircuitInputs } from '../common/src/utils/generateInputs';
import { AWS_ENDPOINT } from '../common/src/constants/constants';
import {
formatProof,
formatInputs
} from '../common/src/utils/utils';
import { samplePassportData } from '../common/src/utils/passportDataStatic';
import "@ethersproject/shims"
import { ethers } from "ethers";
import axios from 'axios';
import groth16ExportSolidityCallData from './utils/snarkjs';
import contractAddresses from "./deployments/addresses.json"
import proofOfPassportArtefact from "./deployments/ProofOfPassport.json";
// import serializedTree from "./deployments/serialized_tree.json";
import MainScreen from './src/screens/MainScreen';
import { extractMRZInfo, formatDateToYYMMDD, Steps } from './src/utils/utils';
import forge from 'node-forge';
import { Steps } from './src/utils/utils';
import { startCameraScan } from './src/utils/cameraScanner';
import { scan } from './src/utils/nfcScanner';
import { mint } from './src/utils/minter';
import { toastConfig } from './src/utils/toastConfig';
import { Buffer } from 'buffer';
import { YStack } from 'tamagui';
import { prove } from './src/utils/prover';
global.Buffer = Buffer;
console.log('DEFAULT_PNUMBER', DEFAULT_PNUMBER);
@@ -63,39 +50,6 @@ function App(): JSX.Element {
older_than: false,
});
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}`);
setPassportNumber(result.documentNumber);
setDateOfBirth(formatDateToYYMMDD(result.birthDate));
setDateOfExpiry(formatDateToYYMMDD(result.expiryDate));
} catch (e) {
console.error(e);
}
}
else {
NativeModules.CameraActivityModule.startCameraActivity()
.then((mrzInfo: string) => {
try {
const { documentNumber, birthDate, expiryDate } = extractMRZInfo(mrzInfo);
setPassportNumber(documentNumber);
setDateOfBirth(birthDate);
setDateOfExpiry(expiryDate);
setStep(Steps.MRZ_SCAN_COMPLETED);
} catch (error: any) {
console.error('Invalid MRZ format:', error.message);
}
})
.catch((error: any) => {
console.error('Camera Activity Error:', error);
});
}
};
const handleDisclosureChange = (field: string) => {
setDisclosure(
{
@@ -126,356 +80,52 @@ function App(): JSX.Element {
console.log('init res', res)
}
async function handleResponseIOS(response: any) {
const parsed = JSON.parse(response);
const eContentBase64 = parsed.eContentBase64; // this is what we call concatenatedDataHashes in android world
const signedAttributes = parsed.signedAttributes; // this is what we call eContent in android world
const signatureAlgorithm = parsed.signatureAlgorithm;
const mrz = parsed.passportMRZ;
const signatureBase64 = parsed.signatureBase64;
console.log('dataGroupsPresent', parsed.dataGroupsPresent)
console.log('placeOfBirth', parsed.placeOfBirth)
console.log('activeAuthenticationPassed', parsed.activeAuthenticationPassed)
console.log('isPACESupported', parsed.isPACESupported)
console.log('isChipAuthenticationSupported', parsed.isChipAuthenticationSupported)
console.log('residenceAddress', parsed.residenceAddress)
console.log('passportPhoto', parsed.passportPhoto.substring(0, 100) + '...')
console.log('parsed.documentSigningCertificate', parsed.documentSigningCertificate)
const pem = JSON.parse(parsed.documentSigningCertificate).PEM.replace(/\\\\n/g, '\n')
console.log('pem', pem)
const cert = forge.pki.certificateFromPem(pem);
const publicKey = cert.publicKey;
const modulus = (publicKey as any).n.toString(10);
const eContentArray = Array.from(Buffer.from(signedAttributes, 'base64'));
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 encryptedDigestArray = Array.from(Buffer.from(signatureBase64, 'base64')).map(byte => byte > 127 ? byte - 256 : byte);
const passportData = {
mrz,
signatureAlgorithm: toStandardName(signatureAlgorithm),
pubKey: {
modulus: modulus,
},
dataGroupHashes: concatenatedDataHashesArraySigned,
eContent: signedEContentArray,
encryptedDigest: encryptedDigestArray,
photoBase64: "data:image/jpeg;base64," + parsed.passportPhoto,
};
console.log('mrz', passportData.mrz);
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
console.log('pubKey', passportData.pubKey);
console.log('dataGroupHashes', [...passportData.dataGroupHashes.slice(0, 10), '...']);
console.log('eContent', [...passportData.eContent.slice(0, 10), '...']);
console.log('encryptedDigest', [...passportData.encryptedDigest.slice(0, 10), '...']);
console.log("photoBase64", passportData.photoBase64.substring(0, 100) + '...')
setPassportData(passportData);
setStep(Steps.NFC_SCAN_COMPLETED);
}
async function handleResponseAndroid(response: any) {
const {
mrz,
signatureAlgorithm,
modulus,
curveName,
publicKeyQ,
eContent,
encryptedDigest,
photo,
digestAlgorithm,
signerInfoDigestAlgorithm,
digestEncryptionAlgorithm,
LDSVersion,
unicodeVersion,
encapContent
} = response;
const passportData: PassportData = {
mrz: mrz.replace(/\n/g, ''),
signatureAlgorithm: toStandardName(signatureAlgorithm),
pubKey: {
modulus: modulus,
curveName: curveName,
publicKeyQ: publicKeyQ,
},
dataGroupHashes: JSON.parse(encapContent),
eContent: JSON.parse(eContent),
encryptedDigest: JSON.parse(encryptedDigest),
photoBase64: photo.base64,
};
console.log('mrz', passportData.mrz);
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
console.log('pubKey', passportData.pubKey);
console.log('dataGroupHashes', passportData.dataGroupHashes);
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)
setPassportData(passportData);
setStep(Steps.NFC_SCAN_COMPLETED);
}
async function scan() {
const check = checkInputs(passportNumber, dateOfBirth, dateOfExpiry)
if (!check.success) {
Toast.show({
type: 'error',
text1: check.message,
})
return
}
console.log('scanning...');
setStep(Steps.NFC_SCANNING);
if (Platform.OS === 'android') {
scanAndroid();
} else {
scanIOS();
}
}
async function scanAndroid() {
try {
const response = await PassportReader.scan({
documentNumber: passportNumber,
dateOfBirth: dateOfBirth,
dateOfExpiry: dateOfExpiry,
});
// console.log('response', response);
console.log('scanned');
handleResponseAndroid(response);
} catch (e: any) {
console.log('error during scan :', e);
setStep(Steps.MRZ_SCAN_COMPLETED);
Toast.show({
type: 'error',
text1: e.message,
})
}
}
async function scanIOS() {
try {
const response = await NativeModules.PassportReader.scanPassport(
passportNumber,
dateOfBirth,
dateOfExpiry
);
console.log('response', response);
console.log('scanned');
handleResponseIOS(response);
} catch (e: any) {
console.log('error during scan :', e);
setStep(Steps.MRZ_SCAN_COMPLETED);
Toast.show({
type: 'error',
text1: e.message,
})
}
}
const handleProve = async (path: string) => {
if (passportData === null) {
console.log('passport data is null');
return;
}
setStep(Steps.GENERATING_PROOF);
setGeneratingProof(true)
await new Promise(resolve => setTimeout(resolve, 10));
const reveal_bitmap = revealBitmapFromMapping(disclosure);
// if (!["sha256WithRSAEncryption"].includes(passportData.signatureAlgorithm)) {
// console.log(`${passportData.signatureAlgorithm} not supported for proof right now.`);
// return;
// }
const inputs = generateCircuitInputs(
passportData,
reveal_bitmap,
address,
{ developmentMode: false }
);
Object.keys(inputs).forEach((key) => {
if (Array.isArray(inputs[key as keyof typeof inputs])) {
console.log(key, inputs[key as keyof typeof inputs].slice(0, 10), '...');
} else {
console.log(key, inputs[key as keyof typeof inputs]);
}
const handleStartCameraScan = () => {
startCameraScan({
setPassportNumber,
setDateOfBirth,
setDateOfExpiry,
setStep,
});
const start = Date.now();
await prove(inputs, path);
const end = Date.now();
console.log('Total proof time from frontend:', end - start);
};
async function prove(inputs: any, path?: string) {
try {
console.log('launching prove function')
console.log('inputs in App.tsx', inputs)
await NativeModules.Prover.runInitAction()
const handleNFCScan = () => {
scan({
passportNumber,
dateOfBirth,
dateOfExpiry,
setPassportData,
setStep,
});
};
const startTime = Date.now();
const handleProve = (path: string) => {
prove({
passportData,
disclosure,
address,
setStep,
setGeneratingProof,
setProofTime,
setProof,
}, path);
};
console.log('running mopro prove action')
const response = await NativeModules.Prover.runProveAction(inputs)
console.log('proof response:', response)
function parseProofAndroid(response: any) {
const match = response.match(/GenerateProofResult\(proof=\[(.*?)\], inputs=\[(.*?)\]\)/);
if (!match) throw new Error('Invalid input format');
return {
proof: match[1].split(',').map((n: any) => (parseInt(n.trim()) + 256) % 256),
inputs: match[2].split(',').map((n: any) => (parseInt(n.trim()) + 256) % 256)
}
}
const parsedResponse = Platform.OS == 'android'
? parseProofAndroid(response)
: JSON.parse(response)
console.log('parsedResponse', parsedResponse)
const endTime = Date.now();
setProofTime(endTime - startTime);
console.log('running mopro verify action')
const res = await NativeModules.Prover.runVerifyAction()
console.log('verify response:', res)
const finalProof = {
proof: JSON.stringify(formatProof(parsedResponse.proof)),
inputs: JSON.stringify(formatInputs(parsedResponse.inputs)),
}
console.log('finalProof:', finalProof)
setProof(finalProof);
setGeneratingProof(false)
setStep(Steps.PROOF_GENERATED);
} catch (err: any) {
console.log('err', err);
}
}
const handleMint = async () => {
setStep(Steps.TX_MINTING);
if (!proof?.proof || !proof?.inputs) {
console.log('proof or inputs is null');
return;
}
if (!contractAddresses.ProofOfPassport || !proofOfPassportArtefact.abi) {
console.log('contracts addresses or abi not found');
return;
}
// Format the proof and publicInputs as calldata for the verifier contract
const p = JSON.parse(proof.proof);
const i = JSON.parse(proof.inputs);
console.log('p', p);
console.log('i', i);
const cd = groth16ExportSolidityCallData(p, i);
const callData = JSON.parse(`[${cd}]`);
console.log('callData', callData);
// format transaction
// for now, we do it all on sepolia
try {
const provider = new ethers.JsonRpcProvider('https://gateway.tenderly.co/public/sepolia');
const proofOfPassportOnSepolia = new ethers.Contract(contractAddresses.ProofOfPassport, proofOfPassportArtefact.abi, provider);
const transactionRequest = await proofOfPassportOnSepolia
.mint.populateTransaction(...callData);
console.log('transactionRequest', transactionRequest);
const response = await axios.post(AWS_ENDPOINT, {
chain: "sepolia",
tx_data: transactionRequest
});
console.log('response status', response.status)
console.log('response data', response.data)
setMintText(`Network: Sepolia. Transaction hash: ${response.data.hash}`)
const receipt = await provider.waitForTransaction(response.data.hash);
console.log('receipt status:', receipt?.status)
if (receipt?.status === 1) {
Toast.show({
type: 'success',
text1: 'SBT minted 🎊',
position: 'top',
bottomOffset: 80,
})
setMintText(`SBT minted. Network: Sepolia. Transaction hash: ${response.data.hash}`)
setStep(Steps.TX_MINTED);
} else {
Toast.show({
type: 'error',
text1: 'Proof of passport minting failed',
position: 'top',
bottomOffset: 80,
})
setMintText(`Error minting SBT. Network: Sepolia. Transaction hash: ${response.data.hash}`)
setStep(Steps.PROOF_GENERATED);
}
} catch (err: any) {
console.log('err', err);
if (err.isAxiosError && err.response) {
const errorMessage = err.response.data.error
console.log('Server error message:', errorMessage);
// parse blockchain error and show it
const match = errorMessage.match(/execution reverted: "([^"]*)"/);
if (match && match[1]) {
console.log('Parsed blockchain error:', match[1]);
Toast.show({
type: 'error',
text1: `Error: ${match[1]}`,
position: 'top',
bottomOffset: 80,
})
} else {
Toast.show({
type: 'error',
text1: `Error: mint failed`,
position: 'top',
bottomOffset: 80,
})
console.log('Failed to parse blockchain error');
}
}
setMintText(`Error minting SBT. Network: Sepolia.`)
}
const handleMint = () => {
mint({
proof,
setStep,
setMintText,
});
};
return (
<YStack f={1} bg="white" h="100%" w="100%">
<YStack h="100%" w="100%">
<MainScreen
onStartCameraScan={startCameraScan}
nfcScan={scan}
onStartCameraScan={handleStartCameraScan}
nfcScan={handleNFCScan}
passportData={passportData}
disclosure={disclosure}
handleDisclosureChange={handleDisclosureChange}
@@ -505,49 +155,3 @@ function App(): JSX.Element {
}
export default App;
export const toastConfig = {
info: (props: ToastProps) => (
<BaseToast
{...props}
contentContainerStyle={{ paddingHorizontal: 15 }}
text1Style={{
fontSize: 15,
fontWeight: "600",
}}
text2Style={{
fontSize: 15,
fontWeight: "500",
}}
/>
),
error: (props: ToastProps) => (
<ErrorToast
{...props}
contentContainerStyle={{ paddingHorizontal: 15 }}
text1Style={{
fontSize: 15,
fontWeight: "600",
}}
text2Style={{
fontSize: 15,
fontWeight: "400",
}}
/>
),
success: (props: ToastProps) => (
<SuccessToast
{...props}
contentContainerStyle={{ paddingHorizontal: 15 }}
text1Style={{
fontSize: 15,
fontWeight: "600",
}}
text2Style={{
fontSize: 15,
fontWeight: "400",
}}
/>
),
};

View File

@@ -40,6 +40,7 @@ const AppCard: React.FC<AppCardProps> = ({
borderRadius="$11"
borderColor={(selected) ? "#3185FC" : ((Platform.OS === 'ios') ? "white" : "transparent")}
borderWidth={(selected) ? 3 : 3}
backgroundColor='white'
shadowColor={selected ? "#3185FC" : "black"}
>
<Card
@@ -47,8 +48,7 @@ const AppCard: React.FC<AppCardProps> = ({
elevation={0}
onTouchStart={selectable ? onTouchStart : showtoast}
>
<XStack w="100%"
>
<XStack w="100%">
<Card.Header w="100%">
<XStack ai="center" py="$1">
<YStack width={250}>

View File

@@ -0,0 +1,45 @@
import { NativeModules, Platform } from 'react-native';
import { formatDateToYYMMDD, extractMRZInfo, Steps } from './utils';
interface CameraScannerProps {
setPassportNumber: (value: string) => void;
setDateOfBirth: (value: string) => void;
setDateOfExpiry: (value: string) => void;
setStep: (value: number) => void;
}
export const startCameraScan = async ({
setPassportNumber,
setDateOfBirth,
setDateOfExpiry,
setStep,
}: CameraScannerProps) => {
if (Platform.OS === 'ios') {
try {
const result = await NativeModules.MRZScannerModule.startScanning();
console.log("Scan result:", result);
console.log(`Document Number: ${result.documentNumber}, Expiry Date: ${result.expiryDate}, Birth Date: ${result.birthDate}`);
setPassportNumber(result.documentNumber);
setDateOfBirth(formatDateToYYMMDD(result.birthDate));
setDateOfExpiry(formatDateToYYMMDD(result.expiryDate));
} catch (e) {
console.error(e);
}
} else {
NativeModules.CameraActivityModule.startCameraActivity()
.then((mrzInfo: string) => {
try {
const { documentNumber, birthDate, expiryDate } = extractMRZInfo(mrzInfo);
setPassportNumber(documentNumber);
setDateOfBirth(birthDate);
setDateOfExpiry(expiryDate);
setStep(Steps.MRZ_SCAN_COMPLETED);
} catch (error: any) {
console.error('Invalid MRZ format:', error.message);
}
})
.catch((error: any) => {
console.error('Camera Activity Error:', error);
});
}
};

104
app/src/utils/minter.ts Normal file
View File

@@ -0,0 +1,104 @@
import { ethers } from "ethers";
import axios from 'axios';
import groth16ExportSolidityCallData from '../../utils/snarkjs';
import contractAddresses from "../../deployments/addresses.json";
import proofOfPassportArtefact from "../../deployments/ProofOfPassport.json";
import { Steps } from './utils';
import Toast from 'react-native-toast-message';
import { AWS_ENDPOINT } from '../../../common/src/constants/constants';
interface MinterProps {
proof: { proof: string; inputs: string } | null;
setStep: (value: number) => void;
setMintText: (value: string) => void;
}
export const mint = async ({ proof, setStep, setMintText }: MinterProps) => {
setStep(Steps.TX_MINTING);
if (!proof?.proof || !proof?.inputs) {
console.log('proof or inputs is null');
return;
}
if (!contractAddresses.ProofOfPassport || !proofOfPassportArtefact.abi) {
console.log('contracts addresses or abi not found');
return;
}
// Format the proof and publicInputs as calldata for the verifier contract
const p = JSON.parse(proof.proof);
const i = JSON.parse(proof.inputs);
console.log('p', p);
console.log('i', i);
const cd = groth16ExportSolidityCallData(p, i);
const callData = JSON.parse(`[${cd}]`);
console.log('callData', callData);
// format transaction
// for now, we do it all on sepolia
try {
const provider = new ethers.JsonRpcProvider('https://gateway.tenderly.co/public/sepolia');
const proofOfPassportOnSepolia = new ethers.Contract(contractAddresses.ProofOfPassport, proofOfPassportArtefact.abi, provider);
const transactionRequest = await proofOfPassportOnSepolia
.mint.populateTransaction(...callData);
console.log('transactionRequest', transactionRequest);
const response = await axios.post(AWS_ENDPOINT, {
chain: "sepolia",
tx_data: transactionRequest
});
console.log('response status', response.status);
console.log('response data', response.data);
setMintText(`Network: Sepolia. Transaction hash: ${response.data.hash}`);
const receipt = await provider.waitForTransaction(response.data.hash);
console.log('receipt status:', receipt?.status);
if (receipt?.status === 1) {
Toast.show({
type: 'success',
text1: 'SBT minted 🎊',
position: 'top',
bottomOffset: 80,
});
setMintText(`SBT minted. Network: Sepolia. Transaction hash: ${response.data.hash}`);
setStep(Steps.TX_MINTED);
} else {
Toast.show({
type: 'error',
text1: 'Proof of passport minting failed',
position: 'top',
bottomOffset: 80,
});
setMintText(`Error minting SBT. Network: Sepolia. Transaction hash: ${response.data.hash}`);
setStep(Steps.PROOF_GENERATED);
}
} catch (err: any) {
console.log('err', err);
if (err.isAxiosError && err.response) {
const errorMessage = err.response.data.error;
console.log('Server error message:', errorMessage);
// parse blockchain error and show it
const match = errorMessage.match(/execution reverted: "([^"]*)"/);
if (match && match[1]) {
console.log('Parsed blockchain error:', match[1]);
Toast.show({
type: 'error',
text1: `Error: ${match[1]}`,
position: 'top',
bottomOffset: 80,
});
} else {
Toast.show({
type: 'error',
text1: `Error: mint failed`,
position: 'top',
bottomOffset: 80,
});
console.log('Failed to parse blockchain error');
}
}
setMintText(`Error minting SBT. Network: Sepolia.`);
}
};

208
app/src/utils/nfcScanner.ts Normal file
View File

@@ -0,0 +1,208 @@
import { NativeModules, Platform } from 'react-native';
// @ts-ignore
import PassportReader from 'react-native-passport-reader';
import { toStandardName } from '../../../common/src/utils/formatNames';
import { checkInputs } from '../../utils/utils';
import { Steps } from './utils';
import Toast from 'react-native-toast-message';
import { PassportData } from '../../../common/src/utils/types';
import forge from 'node-forge';
import { Buffer } from 'buffer';
interface NFCScannerProps {
passportNumber: string;
dateOfBirth: string;
dateOfExpiry: string;
setPassportData: (data: PassportData) => void;
setStep: (value: number) => void;
}
export const scan = async ({
passportNumber,
dateOfBirth,
dateOfExpiry,
setPassportData,
setStep,
}: NFCScannerProps) => {
const check = checkInputs(passportNumber, dateOfBirth, dateOfExpiry);
if (!check.success) {
Toast.show({
type: 'error',
text1: check.message,
});
return;
}
console.log('scanning...');
setStep(Steps.NFC_SCANNING);
if (Platform.OS === 'android') {
scanAndroid(passportNumber, dateOfBirth, dateOfExpiry, setPassportData, setStep);
} else {
scanIOS(passportNumber, dateOfBirth, dateOfExpiry, setPassportData, setStep);
}
};
const scanAndroid = async (
passportNumber: string,
dateOfBirth: string,
dateOfExpiry: string,
setPassportData: (data: PassportData) => void,
setStep: (value: number) => void,
) => {
try {
const response = await PassportReader.scan({
documentNumber: passportNumber,
dateOfBirth: dateOfBirth,
dateOfExpiry: dateOfExpiry,
});
console.log('scanned');
handleResponseAndroid(response, setPassportData, setStep);
} catch (e: any) {
console.log('error during scan:', e);
setStep(Steps.MRZ_SCAN_COMPLETED);
Toast.show({
type: 'error',
text1: e.message,
});
}
};
const scanIOS = async (
passportNumber: string,
dateOfBirth: string,
dateOfExpiry: string,
setPassportData: (data: PassportData) => void,
setStep: (value: number) => void,
) => {
try {
const response = await NativeModules.PassportReader.scanPassport(
passportNumber,
dateOfBirth,
dateOfExpiry,
);
console.log('scanned');
handleResponseIOS(response, setPassportData, setStep);
} catch (e: any) {
console.log('error during scan:', e);
setStep(Steps.MRZ_SCAN_COMPLETED);
Toast.show({
type: 'error',
text1: e.message,
});
}
};
const handleResponseIOS = async (
response: any,
setPassportData: (data: PassportData) => void,
setStep: (value: number) => void,
) => {
const parsed = JSON.parse(response);
const eContentBase64 = parsed.eContentBase64; // this is what we call concatenatedDataHashes in android world
const signedAttributes = parsed.signedAttributes; // this is what we call eContent in android world
const signatureAlgorithm = parsed.signatureAlgorithm;
const mrz = parsed.passportMRZ;
const signatureBase64 = parsed.signatureBase64;
console.log('dataGroupsPresent', parsed.dataGroupsPresent)
console.log('placeOfBirth', parsed.placeOfBirth)
console.log('activeAuthenticationPassed', parsed.activeAuthenticationPassed)
console.log('isPACESupported', parsed.isPACESupported)
console.log('isChipAuthenticationSupported', parsed.isChipAuthenticationSupported)
console.log('residenceAddress', parsed.residenceAddress)
console.log('passportPhoto', parsed.passportPhoto.substring(0, 100) + '...')
console.log('parsed.documentSigningCertificate', parsed.documentSigningCertificate)
const pem = JSON.parse(parsed.documentSigningCertificate).PEM.replace(/\\\\n/g, '\n')
console.log('pem', pem)
const cert = forge.pki.certificateFromPem(pem);
const publicKey = cert.publicKey;
const modulus = (publicKey as any).n.toString(10);
const eContentArray = Array.from(Buffer.from(signedAttributes, 'base64'));
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 encryptedDigestArray = Array.from(Buffer.from(signatureBase64, 'base64')).map(byte => byte > 127 ? byte - 256 : byte);
const passportData = {
mrz,
signatureAlgorithm: toStandardName(signatureAlgorithm),
pubKey: {
modulus: modulus,
},
dataGroupHashes: concatenatedDataHashesArraySigned,
eContent: signedEContentArray,
encryptedDigest: encryptedDigestArray,
photoBase64: "data:image/jpeg;base64," + parsed.passportPhoto,
};
console.log('mrz', passportData.mrz);
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
console.log('pubKey', passportData.pubKey);
console.log('dataGroupHashes', [...passportData.dataGroupHashes.slice(0, 10), '...']);
console.log('eContent', [...passportData.eContent.slice(0, 10), '...']);
console.log('encryptedDigest', [...passportData.encryptedDigest.slice(0, 10), '...']);
console.log("photoBase64", passportData.photoBase64.substring(0, 100) + '...')
setPassportData(passportData);
setStep(Steps.NFC_SCAN_COMPLETED);
};
const handleResponseAndroid = async (
response: any,
setPassportData: (data: PassportData) => void,
setStep: (value: number) => void,
) => {
const {
mrz,
signatureAlgorithm,
modulus,
curveName,
publicKeyQ,
eContent,
encryptedDigest,
photo,
digestAlgorithm,
signerInfoDigestAlgorithm,
digestEncryptionAlgorithm,
LDSVersion,
unicodeVersion,
encapContent
} = response;
const passportData: PassportData = {
mrz: mrz.replace(/\n/g, ''),
signatureAlgorithm: toStandardName(signatureAlgorithm),
pubKey: {
modulus: modulus,
curveName: curveName,
publicKeyQ: publicKeyQ,
},
dataGroupHashes: JSON.parse(encapContent),
eContent: JSON.parse(eContent),
encryptedDigest: JSON.parse(encryptedDigest),
photoBase64: photo.base64,
};
console.log('mrz', passportData.mrz);
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
console.log('pubKey', passportData.pubKey);
console.log('dataGroupHashes', passportData.dataGroupHashes);
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)
setPassportData(passportData);
setStep(Steps.NFC_SCAN_COMPLETED);
};

116
app/src/utils/prover.ts Normal file
View File

@@ -0,0 +1,116 @@
import { NativeModules, Platform } from 'react-native';
import { revealBitmapFromMapping } from '../../../common/src/utils/revealBitmap';
import { generateCircuitInputs } from '../../../common/src/utils/generateInputs';
import { formatProof, formatInputs } from '../../../common/src/utils/utils';
import { Steps } from './utils';
import { PassportData } from '../../../common/src/utils/types';
interface ProverProps {
passportData: PassportData | null;
disclosure: any;
address: string;
setStep: (value: number) => void;
setGeneratingProof: (value: boolean) => void;
setProofTime: (value: number) => void;
setProof: (value: { proof: string; inputs: string } | null) => void;
}
export const prove = async ({
passportData,
disclosure,
address,
setStep,
setGeneratingProof,
setProofTime,
setProof,
}: ProverProps, path?: string) => {
if (passportData === null) {
console.log('passport data is null');
return;
}
setStep(Steps.GENERATING_PROOF);
setGeneratingProof(true);
await new Promise(resolve => setTimeout(resolve, 10));
const reveal_bitmap = revealBitmapFromMapping(disclosure);
// if (!["sha256WithRSAEncryption"].includes(passportData.signatureAlgorithm)) {
// console.log(`${passportData.signatureAlgorithm} not supported for proof right now.`);
// return;
// }
const inputs = generateCircuitInputs(
passportData,
reveal_bitmap,
address,
{ developmentMode: false }
);
Object.keys(inputs).forEach((key) => {
if (Array.isArray(inputs[key as keyof typeof inputs])) {
console.log(key, inputs[key as keyof typeof inputs].slice(0, 10), '...');
} else {
console.log(key, inputs[key as keyof typeof inputs]);
}
});
const start = Date.now();
await generateProof(inputs, setProofTime, setProof, setGeneratingProof, setStep, path);
const end = Date.now();
console.log('Total proof time from frontend:', end - start);
};
const generateProof = async (
inputs: any,
setProofTime: (value: number) => void,
setProof: (value: { proof: string; inputs: string } | null) => void,
setGeneratingProof: (value: boolean) => void,
setStep: (value: number) => void,
path?: string,
) => {
try {
console.log('launching generateProof function');
console.log('inputs in App.tsx', inputs);
await NativeModules.Prover.runInitAction();
const startTime = Date.now();
console.log('running mopro prove action');
const response = await NativeModules.Prover.runProveAction(inputs);
console.log('proof response:', response);
const parsedResponse = Platform.OS === 'android'
? parseProofAndroid(response)
: JSON.parse(response);
console.log('parsedResponse', parsedResponse);
const endTime = Date.now();
setProofTime(endTime - startTime);
console.log('running mopro verify action');
const res = await NativeModules.Prover.runVerifyAction();
console.log('verify response:', res);
const finalProof = {
proof: JSON.stringify(formatProof(parsedResponse.proof)),
inputs: JSON.stringify(formatInputs(parsedResponse.inputs)),
};
console.log('finalProof:', finalProof);
setProof(finalProof);
setGeneratingProof(false);
setStep(Steps.PROOF_GENERATED);
} catch (err: any) {
console.log('err', err);
}
};
const parseProofAndroid = (response: any) => {
const match = response.match(/GenerateProofResult\(proof=\[(.*?)\], inputs=\[(.*?)\]\)/);
if (!match) throw new Error('Invalid input format');
return {
proof: match[1].split(',').map((n: any) => (parseInt(n.trim()) + 256) % 256),
inputs: match[2].split(',').map((n: any) => (parseInt(n.trim()) + 256) % 256)
};
};

View File

@@ -0,0 +1,46 @@
import { BaseToast, ErrorToast, SuccessToast, ToastProps } from 'react-native-toast-message';
export const toastConfig = {
info: (props: ToastProps) => (
<BaseToast
{...props}
contentContainerStyle={{ paddingHorizontal: 15 }}
text1Style={{
fontSize: 15,
fontWeight: "600",
}}
text2Style={{
fontSize: 15,
fontWeight: "500",
}}
/>
),
error: (props: ToastProps) => (
<ErrorToast
{...props}
contentContainerStyle={{ paddingHorizontal: 15 }}
text1Style={{
fontSize: 15,
fontWeight: "600",
}}
text2Style={{
fontSize: 15,
fontWeight: "400",
}}
/>
),
success: (props: ToastProps) => (
<SuccessToast
{...props}
contentContainerStyle={{ paddingHorizontal: 15 }}
text1Style={{
fontSize: 15,
fontWeight: "600",
}}
text2Style={{
fontSize: 15,
fontWeight: "400",
}}
/>
),
};