Merge pull request #224 from zk-passport/deeplink

qrCode deeplinking
This commit is contained in:
turnoffthiscomputer
2024-10-22 17:18:21 +02:00
committed by GitHub
23 changed files with 240 additions and 261 deletions

View File

@@ -10,6 +10,7 @@ import { AMPLITUDE_KEY } from '@env';
import * as amplitude from '@amplitude/analytics-react-native';
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 {
@@ -29,13 +30,18 @@ function App(): React.JSX.Element {
useEffect(() => {
setSelectedTab('splash');
}, [setSelectedTab]);
useEffect(() => {
if (AMPLITUDE_KEY) {
amplitude.init(AMPLITUDE_KEY);
}
}, []);
useEffect(() => {
const cleanup = setupUniversalLinkListener();
return cleanup;
}, []);
return (
<YStack f={1} bc={bgWhite} h="100%" w="100%">
<YStack h="100%" w="100%">

View File

@@ -107,6 +107,7 @@
1686F0DB2C500F3800841CDE /* QRScannerBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerBridge.swift; sourceTree = "<group>"; };
1686F0DD2C500F4F00841CDE /* QRScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerViewController.swift; sourceTree = "<group>"; };
1686F0DF2C500FBD00841CDE /* QRScannerBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QRScannerBridge.m; sourceTree = "<group>"; };
169349842CC694DA00166F21 /* OpenPassportDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = OpenPassportDebug.entitlements; path = OpenPassport/OpenPassportDebug.entitlements; sourceTree = "<group>"; };
16E6646D2B8D292500FDD6A0 /* QKMRZScannerViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QKMRZScannerViewRepresentable.swift; sourceTree = "<group>"; };
16E884A42C5BD764003B7125 /* passport.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = passport.json; sourceTree = "<group>"; };
7C737C07B2C3788F9AB02DE4 /* Pods-OpenPassport.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OpenPassport.release.xcconfig"; path = "Target Support Files/Pods-OpenPassport/Pods-OpenPassport.release.xcconfig"; sourceTree = "<group>"; };
@@ -180,6 +181,7 @@
13B07FAE1A68108700A75B9A /* OpenPassport */ = {
isa = PBXGroup;
children = (
169349842CC694DA00166F21 /* OpenPassportDebug.entitlements */,
16E884A42C5BD764003B7125 /* passport.json */,
05EDEDC42B52D25D00AA51AD /* Prover.m */,
05EDEDC52B52D25D00AA51AD /* Prover.swift */,
@@ -491,7 +493,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassport.entitlements;
CODE_SIGN_ENTITLEMENTS = OpenPassport/OpenPassportDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 63;

View File

@@ -1,8 +1,8 @@
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTBridge.h>
#import <React/RCTLinkingManager.h>
@implementation AppDelegate
@@ -30,4 +30,13 @@
#endif
}
@end
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
@end

View File

@@ -70,5 +70,14 @@
<string>A0000002472001</string>
<string>00000000000000</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>proofofpassport</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -11,6 +11,7 @@
<string>appclips:openpassport.app</string>
<string>appclips:staging.openpassport.app</string>
<string>appclips:appclip.openpassport.app</string>
<string>applinks:proofofpassport-merkle-tree.xyz</string>
</array>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-appclip-app-identifiers</key>
<array>
<string>5B29R5LYHQ.com.warroom.proofofpassport.Clip</string>
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>appclips:openpassport.app</string>
<string>appclips:staging.openpassport.app</string>
<string>appclips:appclip.openpassport.app</string>
<string>applinks:proofofpassport-merkle-tree.xyz</string>
</array>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
</array>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>

View File

@@ -25,7 +25,6 @@ import ProveScreen from './ProveScreen';
import NfcScreen from './NfcScreen';
import CameraScreen from './CameraScreen';
import NextScreen from './NextScreen';
import RegisterScreen from './RegisterScreen';
import AppScreen from './AppScreen';
// import constants
import { RPC_URL, SignatureAlgorithmIndex } from '../../../common/src/constants/constants';
@@ -336,14 +335,6 @@ const MainScreen: React.FC = () => {
<Eraser color={textColor2} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textBlack} width={200} justifyContent="flex-end" htmlFor="skip" >
go to register
</Label>
<Button bg="white" jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={() => setSelectedTab('register')}>
<ArrowRight color={textColor2} />
</Button>
</Fieldset>
<Fieldset gap="$4" mt="$1" horizontal>
<Label color={textBlack} width={200} justifyContent="flex-end" htmlFor="skip" >
registered = (!registered)
@@ -689,9 +680,6 @@ const MainScreen: React.FC = () => {
<Tabs.Content value="next" f={1}>
<NextScreen />
</Tabs.Content>
<Tabs.Content value="register" f={1}>
<RegisterScreen />
</Tabs.Content>
<Tabs.Content value="app" f={1}>
<AppScreen
setSheetAppListOpen={setSheetAppListOpen}

View File

@@ -5,11 +5,11 @@ import { countryCodes, DEVELOPMENT_MODE, max_cert_bytes, } from '../../../common
import { bgGreen, bgGreen2, greenColorLight, separatorColor, textBlack } from '../utils/colors';
import useUserStore from '../stores/userStore';
import useNavigationStore from '../stores/navigationStore';
import { ArgumentsDisclose, DisclosureOptions, OpenPassportApp } from '../../../common/src/utils/appType';
import { DisclosureOptions, OpenPassportApp } from '../../../common/src/utils/appType';
import CustomButton from '../components/CustomButton';
import { formatProof, generateProof } from '../utils/prover';
import { generateProof } from '../utils/prover';
import io, { Socket } from 'socket.io-client';
import { getCircuitName, parseCertificate, parseDSC } from '../../../common/src/utils/certificates/handleCertificate';
import { getCircuitName, parseCertificate } from '../../../common/src/utils/certificates/handleCertificate';
import { CircuitName } from '../utils/zkeyDownload';
import { generateCircuitInputsInApp } from '../utils/generateInputsInApp';
import { buildAttestation } from '../../../common/src/utils/openPassportAttestation';
@@ -41,7 +41,7 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
const [isConnecting, setIsConnecting] = useState(false);
const { signatureAlgorithm, hashFunction, authorityKeyIdentifier } = parseCertificate(passportData.dsc);
const { secret, dscSecret } = useUserStore.getState();
const circuitName = getCircuitName("prove", signatureAlgorithm, hashFunction);
const circuitName = getCircuitName(selectedApp.mode, signatureAlgorithm, hashFunction);
const waitForSocketConnection = (socket: Socket): Promise<void> => {
return new Promise((resolve) => {
@@ -88,7 +88,7 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
console.log("result", result);
if (JSON.parse(result).valid) {
toast.show("✅", {
message: "Proof verified",
message: "Identity verified",
customData: {
type: "success",
},
@@ -98,7 +98,7 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
}, 700);
} else {
toast.show("❌", {
message: "Wrong proof",
message: "Verification failed",
customData: {
type: "info",
},
@@ -142,28 +142,16 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
socket.emit('proof_generation_start', { sessionId: selectedApp.sessionId });
console.log("selectedApp.mode", selectedApp.mode);
const inputs = await generateCircuitInputsInApp(passportData, selectedApp);
let attestation;
let proof;
let dscProof;
switch (selectedApp.mode) {
case 'vc_and_disclose':
const inputs_disclose = await generateCircuitInputsInApp(passportData, selectedApp);
const proof_disclose = await generateProof('vc_and_disclose', inputs_disclose);
const formattedProof_disclose = formatProof(proof_disclose);
console.log(formattedProof_disclose);
const attestation_disclose = buildAttestation({
userIdType: selectedApp.userIdType,
mode: selectedApp.mode,
proof: formattedProof_disclose.proof,
publicSignals: formattedProof_disclose.publicSignals,
signatureAlgorithm: signatureAlgorithm,
hashFunction: hashFunction,
});
socket.emit('proof_generated', { sessionId: selectedApp.sessionId, proof: attestation_disclose });
break;
case 'prove_onchain':
case 'register':
const inputs = await generateCircuitInputsInApp(passportData, selectedApp);
const cscaInputs = generateCircuitInputsDSC(dscSecret as string, passportData.dsc, max_cert_bytes, true);
const [modalResponse, proof] = await Promise.all([
const cscaInputs = generateCircuitInputsDSC(dscSecret as string, passportData.dsc, max_cert_bytes, selectedApp.devMode);
[dscProof, proof] = await Promise.all([
sendCSCARequest(
cscaInputs
),
@@ -172,17 +160,12 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
inputs,
)
]);
const dscProof = JSON.parse(JSON.stringify(modalResponse));
const cscaPem = getCSCAFromSKI(authorityKeyIdentifier, DEVELOPMENT_MODE);
if (!cscaPem) {
throw new Error(`CSCA not found, devMode: ${DEVELOPMENT_MODE}, authorityKeyIdentifier: ${authorityKeyIdentifier}`);
}
const { signatureAlgorithm: signatureAlgorithmDsc } = parseCertificate(cscaPem);
const formattedProof = formatProof(proof);
const attestation = buildAttestation({
attestation = buildAttestation({
mode: selectedApp.mode,
proof: formattedProof.proof,
publicSignals: formattedProof.publicSignals,
proof: proof.proof,
publicSignals: proof.publicSignals,
signatureAlgorithm: signatureAlgorithm,
hashFunction: hashFunction,
userIdType: selectedApp.userIdType,
@@ -191,30 +174,25 @@ const ProveScreen: React.FC<ProveScreenProps> = ({ setSheetRegisterIsOpen }) =>
signatureAlgorithmDsc: signatureAlgorithmDsc,
hashFunctionDsc: hashFunction,
});
console.log("\x1b[90mattestation\x1b[0m", attestation);
socket.emit('proof_generated', { sessionId: selectedApp.sessionId, proof: attestation });
break;
case 'prove_offchain':
const inputs_prove = await generateCircuitInputsInApp(passportData, selectedApp);
const proof_prove = await generateProof(
default:
proof = await generateProof(
circuitName,
inputs_prove,
);
const formattedProof_prove = formatProof(proof_prove);
const attestation_prove = buildAttestation({
inputs,
)
attestation = buildAttestation({
userIdType: selectedApp.userIdType,
mode: selectedApp.mode,
proof: formattedProof_prove.proof,
publicSignals: formattedProof_prove.publicSignals,
proof: proof.proof,
publicSignals: proof.publicSignals,
signatureAlgorithm: signatureAlgorithm,
hashFunction: hashFunction,
dsc: passportData.dsc,
});
console.log("\x1b[90mattestation\x1b[0m", attestation_prove);
socket.emit('proof_generated', { sessionId: selectedApp.sessionId, proof: attestation_prove });
break;
}
console.log("\x1b[90mattestation\x1b[0m", attestation);
socket.emit('proof_generated', { sessionId: selectedApp.sessionId, proof: attestation });
} catch (error) {
toast.show("Error", {

View File

@@ -1,78 +0,0 @@
import React, { useState } from 'react';
import { YStack, XStack, Text, Button, Spinner } from 'tamagui';
import { LockKeyhole, UserPlus } from '@tamagui/lucide-icons';
import { bgGreen, borderColor, componentBgColor, componentBgColor2, textBlack } from '../utils/colors';
import { Platform } from 'react-native';
import useUserStore from '../stores/userStore';
import useNavigationStore from '../stores/navigationStore';
import CustomButton from '../components/CustomButton';
const RegisterScreen: React.FC = () => {
const [registering, setRegistering] = useState(false);
const [registerStep, setRegisterStep] = useState<string | null>(null);
const { isZkeyDownloading } = useNavigationStore.getState();
const handleRegister = async () => {
setRegistering(true);
useUserStore.getState().registerCommitment();
setRegisterStep("Generating witness...");
setTimeout(() => {
setRegisterStep("Generating proof...");
setTimeout(() => {
setRegisterStep("DSC verification...");
setTimeout(() => {
setRegisterStep("Registering...");
}, 4000);
}, 4000);
}, 4000);
}
return (
<YStack p="$3" f={1} mb={Platform.OS === 'ios' ? "$5" : "$0"}>
<YStack flex={1} mx="$2" gap="$2">
<Text mt="$7" fontSize="$9" color={textBlack}>Join OpenPassport to start sharing your identity<Text style={{
textDecorationLine: "underline", textDecorationColor: bgGreen
}}> securely. </Text></Text>
<Text mt="$0" color={textBlack} fontSize="$8">Easily verify your nationality, humanity, or age and share<Text style={{
textDecorationLine: "underline", textDecorationColor: bgGreen
}}> only </Text>what you want to reveal.</Text>
<XStack f={1} />
<XStack mt="$5" bg="white" borderRadius={100} mb="$12" py="$2.5" px="$3">
<XStack p="$2" >
<LockKeyhole alignSelf='center' size={24} color={textBlack} />
</XStack>
<Text alignSelf='center' ml="$3" pr="$5" fontSize="$3" color={textBlack}>Registration does not leak any personal information</Text>
</XStack>
<CustomButton
isDisabled={isZkeyDownloading.register_sha256WithRSAEncryption_65537 || registering}
onPress={handleRegister}
text={isZkeyDownloading.register_sha256WithRSAEncryption_65537 ? "Downloading zkey..." : (registerStep || "Register")}
Icon={isZkeyDownloading.register_sha256WithRSAEncryption_65537 || registering ? <Spinner color={textBlack} /> : <UserPlus color={textBlack} />}
/>
{/* <Button
disabled={isZkeyDownloading.register_sha256WithRSAEncryption_65537}
mt="$8"
alignSelf='center'
onPress={handleRegister}
borderWidth={1.3} borderColor={borderColor} borderRadius="$10" bg={isZkeyDownloading.register_sha256WithRSAEncryption_65537 ? "gray" : "#3185FC"}
mb="$6"
w="100%"
>
<XStack gap="$3">
{(registering || isZkeyDownloading.register_sha256WithRSAEncryption_65537) && <Spinner color="white" size="small" />}
<Text color={textBlack} fontSize="$5" >
{isZkeyDownloading.register_sha256WithRSAEncryption_65537 ? "Downloading zkey..." : (registerStep || "Register")}
</Text>
</XStack>
</Button> */}
</YStack >
</YStack >
);
};
export default RegisterScreen;

View File

@@ -37,15 +37,15 @@ export const generateProof = async (
if (Platform.OS === 'android') {
const parsedResponse = parseProofAndroid(response);
console.log('parsedResponse', parsedResponse);
return parsedResponse
return formatProof(parsedResponse)
} else {
const parsedResponse = JSON.parse(response);
console.log('parsedResponse', parsedResponse);
return {
return formatProof({
proof: parsedResponse.proof,
pub_signals: parsedResponse.inputs,
}
})
}
} catch (err: any) {
console.log('err', err);

View File

@@ -1,4 +1,4 @@
import { NativeModules, Platform } from "react-native";
import { NativeModules, Platform, Linking } from "react-native";
// import { AppType, reconstructAppType } from "../../../common/src/utils/appType";
import useNavigationStore from '../stores/navigationStore';
import { getCircuitName, parseDSC } from "../../../common/src/utils/certificates/handleCertificate";
@@ -6,89 +6,147 @@ import useUserStore from "../stores/userStore";
import { downloadZkey } from "./zkeyDownload";
import msgpack from "msgpack-lite";
import pako from "pako";
import { OpenPassportApp } from "../../../common/src/utils/appType";
import { Mode, OpenPassportApp } from "../../../common/src/utils/appType";
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;
};
export const scanQRCode = () => {
const { toast, setSelectedApp, setSelectedTab } = useNavigationStore.getState();
if (Platform.OS === 'ios') {
if (NativeModules.QRScannerBridge && NativeModules.QRScannerBridge.scanQRCode) {
NativeModules.QRScannerBridge.scanQRCode()
.then((result: string) => {
handleQRCodeScan(result, toast, setSelectedApp, setSelectedTab);
})
.catch((error: any) => {
console.error('QR Scanner Error:', error);
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: 'Failed to scan QR code',
message: 'QR Scanner not available',
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') {
if (NativeModules.QRCodeScanner && NativeModules.QRCodeScanner.scanQRCode) {
NativeModules.QRCodeScanner.scanQRCode()
.then((result: string) => {
handleQRCodeScan(result, toast, setSelectedApp, setSelectedTab);
})
.catch((error: any) => {
console.error('QR Scanner Error:', error);
}
} else if (Platform.OS === 'android') {
const qrScanner = NativeModules.QRCodeScanner;
if (qrScanner && qrScanner.scanQRCode) {
qrScanner.scanQRCode()
.then((result: string) => {
handleQRCodeScan(result, 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: 'Failed to scan QR code',
message: 'QR Scanner not available',
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',
});
});
};
const handleQRCodeScan = (result: string, toast: any, setSelectedApp: any, setSelectedTab: any) => {
try {
console.log(result);
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);
console.log(unpackedData);
const openPassportApp: OpenPassportApp = unpackedData;
setSelectedApp(openPassportApp);
const dsc = useUserStore.getState().passportData.dsc;
const sigAlgName = parseDSC(dsc!);
if (openPassportApp.mode == 'vc_and_disclose') {
downloadZkey('vc_and_disclose');
}
else {
const circuitName = getCircuitName("prove", sigAlgName.signatureAlgorithm, sigAlgName.hashFunction);
downloadZkey(circuitName as any);
}
const circuitName = openPassportApp.mode === 'vc_and_disclose'
? 'vc_and_disclose'
: getCircuitName("prove" as Mode, sigAlgName.signatureAlgorithm, sigAlgName.hashFunction);
downloadZkey(circuitName as any);
setSelectedTab("prove");
toast.show('✅', {
message: "QR code scanned",
customData: {
type: "success",
},
})
});
} catch (error) {
console.error('Error parsing QR code result:', error);
toast.show('Try again', {
message: "Error reading QR code",
message: "Error reading QR code: " + (error as Error).message,
customData: {
type: "info",
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',
});
}
};
export const setupUniversalLinkListener = () => {
Linking.getInitialURL().then((url) => {
if (url) {
handleUniversalLink(url);
}
});
const linkingEventListener = Linking.addEventListener('url', ({ url }) => {
handleUniversalLink(url);
});
return () => {
linkingEventListener.remove();
};
};

View File

@@ -50,10 +50,12 @@ export const ECDSA_K_LENGTH_FACTOR = 2;
// possible values because of sha1 constaints: 192,320,384, 448, 576, 640
export const circuitNameFromMode = {
prove: 'prove',
prove_onchain: 'prove',
prove_offchain: 'prove',
register: 'prove',
vc_and_disclose: 'vc_and_disclose',
dsc: 'dsc',
}
export enum SignatureAlgorithmIndex {

View File

@@ -14,6 +14,7 @@ export interface OpenPassportAppPartial {
sessionId: string;
userId: string;
userIdType: UserIdType;
devMode: boolean;
}
export interface OpenPassportApp extends OpenPassportAppPartial {

View File

@@ -5,6 +5,8 @@ import elliptic from 'elliptic';
import { parseRsaPublicKey, parseRsaPssPublicKey, parseECParameters } from './publicKeyDetails';
import { PublicKeyDetailsRSAPSS } from './dataStructure';
import { getNamedCurve } from './curves';
import { circuitNameFromMode } from '../../constants/constants';
import { Mode } from '../appType';
if (typeof global.Buffer === 'undefined') {
global.Buffer = require('buffer').Buffer;
@@ -48,12 +50,16 @@ export function parseCertificate(pem: string) {
}
export const getCircuitName = (circuitType: string, signatureAlgorithm: string, hashFunction: string) => {
if (signatureAlgorithm === 'ecdsa') {
return circuitType + "_" + signatureAlgorithm + "_secp256r1_" + hashFunction;
export const getCircuitName = (circuitMode: Mode, signatureAlgorithm: string, hashFunction: string) => {
const circuit = circuitNameFromMode[circuitMode];
if (circuit == 'vc_and_disclose') {
return 'vc_and_disclose';
}
else if (signatureAlgorithm === 'ecdsa') {
return circuit + "_" + signatureAlgorithm + "_secp256r1_" + hashFunction;
}
else {
return circuitType + "_" + signatureAlgorithm + "_65537_" + hashFunction;
return circuit + "_" + signatureAlgorithm + "_65537_" + hashFunction;
}
}

View File

@@ -1,4 +1,4 @@
import { sha1Pad, sha256Pad } from "./shaPad";
import { shaPad } from "./shaPad";
import * as forge from "node-forge";
import { bytesToBigDecimal, extractRSFromSignature, getNAndK, getNAndKCSCA, hexToDecimal, splitToWords } from "./utils";
import { CSCA_TREE_DEPTH, MODAL_SERVER_ADDRESS } from "../constants/constants";
@@ -9,13 +9,16 @@ import axios from "axios";
import { parseCertificate } from "./certificates/handleCertificate";
import { getLeafCSCA } from "./pubkeyTree";
import { SKI_PEM, SKI_PEM_DEV } from "../constants/skiPem";
export function findStartIndex(modulus: string, messagePadded: Uint8Array): number {
console.log('messagePadded', messagePadded);
const modulusNumArray = [];
for (let i = 0; i < modulus.length; i += 2) {
const hexPair = modulus.slice(i, i + 2);
const number = parseInt(hexPair, 16);
modulusNumArray.push(number);
}
console.log('modulusNumArray', modulusNumArray);
const messagePaddedNumber = [];
for (let i = 0; i < messagePadded.length; i += 1) {
const number = Number(messagePadded[i]);
@@ -26,17 +29,18 @@ export function findStartIndex(modulus: string, messagePadded: Uint8Array): numb
if (modulusNumArray[0] === messagePaddedNumber[i]) {
for (let j = 0; j < modulusNumArray.length; j++) {
if (modulusNumArray[j] !== messagePaddedNumber[i + j]) {
//console.log("NO MODULUS FOUND IN CERTIFICATE");
break;
}
else if (j === modulusNumArray.length - 1) {
//console.log("MODULUS FOUND IN CERTIFICATE");
startIndex = i;
}
}
break;
}
}
if (startIndex === -1) {
throw new Error('DSC Pubkey not found in CSCA certificate');
}
return startIndex;
}
@@ -52,18 +56,7 @@ export function generateCircuitInputsDSC(dscSecret: string, dscCertificate: any,
const { signatureAlgorithm, hashFunction, publicKeyDetails, x, y, modulus, curve, exponent, bits, subjectKeyIdentifier, authorityKeyIdentifier } = parseCertificate(dscCertificate);
let dsc_message_padded;
let dsc_messagePaddedLen;
switch (hashFunction) {
case 'sha1':
[dsc_message_padded, dsc_messagePaddedLen] = sha1Pad(dscTbsCertUint8Array, max_cert_bytes);
break;
case 'sha256':
[dsc_message_padded, dsc_messagePaddedLen] = sha256Pad(dscTbsCertUint8Array, max_cert_bytes);
break;
default:
console.log("Signature algorithm not recognized", signatureAlgorithm);
[dsc_message_padded, dsc_messagePaddedLen] = sha256Pad(dscTbsCertUint8Array, max_cert_bytes);
break;
}
[dsc_message_padded, dsc_messagePaddedLen] = shaPad(dscTbsCertUint8Array, max_cert_bytes);
const { n, k } = getNAndK(signatureAlgorithm);
// Extract the signature from the DSC certificate
@@ -78,6 +71,7 @@ export function generateCircuitInputsDSC(dscSecret: string, dscCertificate: any,
const dsc_messagePaddedLen_formatted = BigInt(dsc_messagePaddedLen).toString()
const cscaPem = getCSCAFromSKI(authorityKeyIdentifier, devMode);
console.log('cscaPem', cscaPem);
const { x: csca_x, y: csca_y, modulus: csca_modulus, signature_algorithm: csca_signature_algorithm } = parseCertificate(cscaPem);
const { n: n_csca, k: k_csca } = getNAndKCSCA(csca_signature_algorithm);
@@ -139,12 +133,13 @@ export function generateCircuitInputsDSC(dscSecret: string, dscCertificate: any,
}
export function getCSCAFromSKI(ski: string, devMode: boolean): string | null {
export function getCSCAFromSKI(ski: string, devMode: boolean): string {
const cscaPemPROD = (SKI_PEM as any)[ski];
const cscaPemDEV = (SKI_PEM_DEV as any)[ski];
const cscaPem = devMode ? cscaPemDEV || cscaPemPROD : cscaPemPROD;
if (!cscaPem) {
console.log('\x1b[31m%s\x1b[0m', `CSCA with SKI ${ski} not found`, 'devMode: ', devMode);
throw new Error(`CSCA not found, authorityKeyIdentifier: ${ski}, areMockPassportsAllowed: ${devMode},`);
}
return cscaPem;
}

View File

@@ -218,12 +218,10 @@ export function generateCircuitInputsProve(
}
const [eContentPadded, eContentLen] = shaPad(
signatureAlgorithm,
new Uint8Array(eContent),
MAX_PADDED_ECONTENT_LEN[signatureAlgorithmFullName]
);
const [signedAttrPadded, signedAttrPaddedLen] = shaPad(
signatureAlgorithm,
new Uint8Array(signedAttr),
MAX_PADDED_SIGNED_ATTR_LEN[signatureAlgorithmFullName]
);

View File

@@ -1,37 +1,7 @@
// Copied from zk-email cuz it uses crypto so can't import it here.
export function shaPad(signatureAlgorithm: string, prehash_prepad_m: Uint8Array, maxShaBytes: number): [Uint8Array, number] {
if (signatureAlgorithm == 'sha1WithRSAEncryption') {
return sha1Pad(prehash_prepad_m, maxShaBytes);
} else {
return sha256Pad(prehash_prepad_m, maxShaBytes);
}
}
// Puts an end selector, a bunch of 0s, then the length, then fill the rest with 0s.
export function sha1Pad(prehash_prepad_m: Uint8Array, maxShaBytes: number): [Uint8Array, number] {
let length_bits = prehash_prepad_m.length * 8; // bytes to bits
let length_in_bytes = int64toBytes(length_bits);
prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(2 ** 7)); // Add the 1 on the end, length 505
while ((prehash_prepad_m.length * 8 + length_in_bytes.length * 8) % 512 !== 0) {
prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(0));
}
prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, length_in_bytes);
assert((prehash_prepad_m.length * 8) % 512 === 0, "Padding did not complete properly!");
let messageLen = prehash_prepad_m.length;
while (prehash_prepad_m.length < maxShaBytes) {
prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int64toBytes(0));
}
assert(
prehash_prepad_m.length === maxShaBytes,
`Padding to max length did not complete properly! Your padded message is ${prehash_prepad_m.length} long but max is ${maxShaBytes}!`
);
return [prehash_prepad_m, messageLen];
}
// Puts an end selector, a bunch of 0s, then the length, then fill the rest with 0s.
export function sha256Pad(prehash_prepad_m: Uint8Array, maxShaBytes: number): [Uint8Array, number] {
export function shaPad(prehash_prepad_m: Uint8Array, maxShaBytes: number): [Uint8Array, number] {
let length_bits = prehash_prepad_m.length * 8; // bytes to bits
let length_in_bytes = int64toBytes(length_bits);
prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(2 ** 7)); // Add the 1 on the end, length 505

View File

@@ -260,8 +260,7 @@ export class AttestationVerifier {
signatureAlgorithm: string,
hashFunction: string
): Promise<void> {
const circuitName = circuitNameFromMode[mode];
const vkey = getVkeyFromArtifacts(circuitName, signatureAlgorithm, hashFunction);
const vkey = getVkeyFromArtifacts(mode, signatureAlgorithm, hashFunction);
const isVerified = await groth16.verify(vkey, publicSignals, proof as any);
this.verifyAttribute('proof', isVerified.toString(), 'true');
}

View File

@@ -95,6 +95,7 @@ export class OpenPassportVerifier extends AttestationVerifier {
sessionId: sessionId,
userId: userId,
userIdType: userIdType,
devMode: this.devMode,
};
let openPassportArguments: ArgumentsProveOffChain | ArgumentsRegister;

View File

@@ -51,6 +51,13 @@ const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
);
}, [sessionId, websocketUrl]);
const generateUniversalLink = () => {
const baseUrl = 'https://proofofpassport-merkle-tree.xyz';
const path = '/open-passport';
const data = openPassportVerifier.getIntent(appName, userId, userIdType, sessionId);
return `${baseUrl}${path}?data=${data}`;
};
const renderProofStatus = () => (
<div style={containerStyle}>
<div style={ledContainerStyle}>
@@ -89,7 +96,7 @@ const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
default:
return (
<QRCodeSVG
value={openPassportVerifier.getIntent(appName, userId, userIdType, sessionId)}
value={generateUniversalLink()}
size={size}
/>
);

View File

@@ -12,8 +12,7 @@ export default function Prove() {
.setCommitmentMerkleTreeUrl(COMMITMENT_TREE_TRACKER_URL)
.excludeCountries('Albania')
.setMinimumAge(20)
.enableOFACCheck()
.setNationality('Germany');
.enableOFACCheck();
return (
<div className="h-screen w-full bg-white flex flex-col items-center justify-center gap-4">
<OpenPassportQRcode
@@ -21,7 +20,7 @@ export default function Prove() {
userId={userId}
userIdType={'uuid'}
openPassportVerifier={openPassportVerifierDisclose}
onSuccess={(attestation) => {}}
onSuccess={(attestation) => { }}
/>
</div>
);

View File

@@ -11,7 +11,7 @@ export default function Prove() {
const openPassportVerifier = new OpenPassportVerifier(
'register',
scope
).setCommitmentMerkleTreeUrl(COMMITMENT_TREE_TRACKER_URL);
).setCommitmentMerkleTreeUrl(COMMITMENT_TREE_TRACKER_URL).allowMockPassports();
return (
<div className="h-screen w-full bg-white flex flex-col items-center justify-center gap-4">
<OpenPassportQRcode

View File

@@ -2,6 +2,7 @@ import { ethers } from 'ethers';
import { getCurrentDateYYMMDD } from '../../common/src/utils/utils';
import {
attributeToPosition,
circuitNameFromMode,
REGISTER_ABI,
REGISTER_CONTRACT_ADDRESS,
} from '../../common/src/constants/constants';
@@ -18,20 +19,24 @@ import {
vkey_vc_and_disclose,
} from '../../common/src/constants/vkey';
import { getCircuitName } from '../../common/src/utils/certificates/handleCertificate';
import { Mode } from 'fs';
export function getCurrentDateFormatted() {
return getCurrentDateYYMMDD().map((datePart) => BigInt(datePart).toString());
}
export function getVkeyFromArtifacts(
circuit: string,
circuitMode: Mode,
signatureAlgorithm: string,
hashFunction: string
) {
const circuitName =
circuit === 'vc_and_disclose'
? circuit
: getCircuitName(circuit, signatureAlgorithm, hashFunction);
const circuit = circuitNameFromMode[circuitMode];
let circuitName = '';
if (circuit === 'vc_and_disclose') {
circuitName = circuit;
} else {
circuitName = getCircuitName(circuit, signatureAlgorithm, hashFunction);
}
// console.log('\x1b[90m%s\x1b[0m', 'circuit name:', circuitName);
switch (circuitName) {
case 'vc_and_disclose':