mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 14:48:06 -05:00
moves validateDocument functions into the common package. (#977)
* moves validateDocument functions into the common package. * fix build issues and lint * handle bad connections better in nullifiier * add an abort controler to nullifer fetcher, ignore fals positives * import types separately * take it as an arg
This commit is contained in:
4
.gitleaksignore
Normal file
4
.gitleaksignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:54
|
||||||
|
1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:55
|
||||||
|
1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:73
|
||||||
|
1b461a626e0a4a93d4e1c727e7aed8c955aa728c:common/src/utils/passports/validate.test.ts:generic-api-key:74
|
||||||
@@ -144,7 +144,7 @@ module.exports = {
|
|||||||
// General JavaScript Rules
|
// General JavaScript Rules
|
||||||
// Warn on common issues but don't block development
|
// Warn on common issues but don't block development
|
||||||
|
|
||||||
'no-console': 'warn',
|
'no-console': 'off',
|
||||||
'no-empty-pattern': 'off',
|
'no-empty-pattern': 'off',
|
||||||
'prefer-const': 'warn',
|
'prefer-const': 'warn',
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ const extraNodeModules = {
|
|||||||
commonPath,
|
commonPath,
|
||||||
'dist/esm/src/utils/passports/format.js',
|
'dist/esm/src/utils/passports/format.js',
|
||||||
),
|
),
|
||||||
|
'@selfxyz/common/utils/passports/validate': path.resolve(
|
||||||
|
commonPath,
|
||||||
|
'dist/esm/src/utils/passports/validate.js',
|
||||||
|
),
|
||||||
'@selfxyz/common/utils/passportMock': path.resolve(
|
'@selfxyz/common/utils/passportMock': path.resolve(
|
||||||
commonPath,
|
commonPath,
|
||||||
'dist/esm/src/utils/passports/mock.js',
|
'dist/esm/src/utils/passports/mock.js',
|
||||||
|
|||||||
@@ -59,12 +59,8 @@ import type {
|
|||||||
DocumentMetadata,
|
DocumentMetadata,
|
||||||
PassportData,
|
PassportData,
|
||||||
} from '@selfxyz/common/utils/types';
|
} from '@selfxyz/common/utils/types';
|
||||||
import {
|
import type { DocumentsAdapter, SelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||||
DocumentsAdapter,
|
import { getAllDocuments, useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||||
getAllDocuments,
|
|
||||||
SelfClient,
|
|
||||||
useSelfClient,
|
|
||||||
} from '@selfxyz/mobile-sdk-alpha';
|
|
||||||
|
|
||||||
import { unsafe_getPrivateKey, useAuth } from '@/providers/authProvider';
|
import { unsafe_getPrivateKey, useAuth } from '@/providers/authProvider';
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import React, { useCallback, useState } from 'react';
|
|||||||
import { Separator, View, XStack, YStack } from 'tamagui';
|
import { Separator, View, XStack, YStack } from 'tamagui';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
|
||||||
|
import { isUserRegisteredWithAlternativeCSCA } from '@selfxyz/common/utils/passports/validate';
|
||||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||||
import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||||
|
|
||||||
@@ -23,10 +24,10 @@ import {
|
|||||||
loadPassportDataAndSecret,
|
loadPassportDataAndSecret,
|
||||||
reStorePassportDataWithRightCSCA,
|
reStorePassportDataWithRightCSCA,
|
||||||
} from '@/providers/passportDataProvider';
|
} from '@/providers/passportDataProvider';
|
||||||
|
import { useProtocolStore } from '@/stores/protocolStore';
|
||||||
import { useSettingStore } from '@/stores/settingStore';
|
import { useSettingStore } from '@/stores/settingStore';
|
||||||
import { STORAGE_NAME, useBackupMnemonic } from '@/utils/cloudBackup';
|
import { STORAGE_NAME, useBackupMnemonic } from '@/utils/cloudBackup';
|
||||||
import { black, slate500, slate600, white } from '@/utils/colors';
|
import { black, slate500, slate600, white } from '@/utils/colors';
|
||||||
import { isUserRegisteredWithAlternativeCSCA } from '@/utils/proving/validateDocument';
|
|
||||||
|
|
||||||
const AccountRecoveryChoiceScreen: React.FC = () => {
|
const AccountRecoveryChoiceScreen: React.FC = () => {
|
||||||
const { trackEvent } = useSelfClient();
|
const { trackEvent } = useSelfClient();
|
||||||
@@ -60,6 +61,14 @@ const AccountRecoveryChoiceScreen: React.FC = () => {
|
|||||||
const { isRegistered, csca } = await isUserRegisteredWithAlternativeCSCA(
|
const { isRegistered, csca } = await isUserRegisteredWithAlternativeCSCA(
|
||||||
passportData,
|
passportData,
|
||||||
secret,
|
secret,
|
||||||
|
{
|
||||||
|
getCommitmentTree(docCategory) {
|
||||||
|
return useProtocolStore.getState()[docCategory].commitment_tree;
|
||||||
|
},
|
||||||
|
getAltCSCA(docCategory) {
|
||||||
|
return useProtocolStore.getState()[docCategory].alternative_csca;
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (!isRegistered) {
|
if (!isRegistered) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Text, TextArea, View, XStack, YStack } from 'tamagui';
|
|||||||
import Clipboard from '@react-native-clipboard/clipboard';
|
import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
|
||||||
|
import { isUserRegisteredWithAlternativeCSCA } from '@selfxyz/common/utils/passports/validate';
|
||||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||||
import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
import { BackupEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
loadPassportDataAndSecret,
|
loadPassportDataAndSecret,
|
||||||
reStorePassportDataWithRightCSCA,
|
reStorePassportDataWithRightCSCA,
|
||||||
} from '@/providers/passportDataProvider';
|
} from '@/providers/passportDataProvider';
|
||||||
|
import { useProtocolStore } from '@/stores/protocolStore';
|
||||||
import {
|
import {
|
||||||
black,
|
black,
|
||||||
slate300,
|
slate300,
|
||||||
@@ -28,7 +30,6 @@ import {
|
|||||||
slate700,
|
slate700,
|
||||||
white,
|
white,
|
||||||
} from '@/utils/colors';
|
} from '@/utils/colors';
|
||||||
import { isUserRegisteredWithAlternativeCSCA } from '@/utils/proving/validateDocument';
|
|
||||||
|
|
||||||
const RecoverWithPhraseScreen: React.FC = () => {
|
const RecoverWithPhraseScreen: React.FC = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
@@ -65,6 +66,14 @@ const RecoverWithPhraseScreen: React.FC = () => {
|
|||||||
const { isRegistered, csca } = await isUserRegisteredWithAlternativeCSCA(
|
const { isRegistered, csca } = await isUserRegisteredWithAlternativeCSCA(
|
||||||
passportData,
|
passportData,
|
||||||
secret as string,
|
secret as string,
|
||||||
|
{
|
||||||
|
getCommitmentTree(docCategory) {
|
||||||
|
return useProtocolStore.getState()[docCategory].commitment_tree;
|
||||||
|
},
|
||||||
|
getAltCSCA(docCategory) {
|
||||||
|
return useProtocolStore.getState()[docCategory].alternative_csca;
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (!isRegistered) {
|
if (!isRegistered) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
IDENTITY_TREE_URL_STAGING,
|
IDENTITY_TREE_URL_STAGING,
|
||||||
IDENTITY_TREE_URL_STAGING_ID_CARD,
|
IDENTITY_TREE_URL_STAGING_ID_CARD,
|
||||||
} from '@selfxyz/common/constants';
|
} from '@selfxyz/common/constants';
|
||||||
import type { OfacTree } from '@selfxyz/common/utils/types';
|
import type { DeployedCircuits, OfacTree } from '@selfxyz/common/utils/types';
|
||||||
|
|
||||||
import { fetchOfacTrees } from '@/utils/ofac';
|
import { fetchOfacTrees } from '@/utils/ofac';
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ interface ProtocolState {
|
|||||||
commitment_tree: any;
|
commitment_tree: any;
|
||||||
dsc_tree: any;
|
dsc_tree: any;
|
||||||
csca_tree: string[][] | null;
|
csca_tree: string[][] | null;
|
||||||
deployed_circuits: any;
|
deployed_circuits: DeployedCircuits | null;
|
||||||
circuits_dns_mapping: any;
|
circuits_dns_mapping: any;
|
||||||
alternative_csca: Record<string, string>;
|
alternative_csca: Record<string, string>;
|
||||||
ofac_trees: OfacTree | null;
|
ofac_trees: OfacTree | null;
|
||||||
@@ -49,7 +49,7 @@ interface ProtocolState {
|
|||||||
commitment_tree: any;
|
commitment_tree: any;
|
||||||
dsc_tree: any;
|
dsc_tree: any;
|
||||||
csca_tree: string[][] | null;
|
csca_tree: string[][] | null;
|
||||||
deployed_circuits: any;
|
deployed_circuits: DeployedCircuits | null;
|
||||||
circuits_dns_mapping: any;
|
circuits_dns_mapping: any;
|
||||||
alternative_csca: Record<string, string>;
|
alternative_csca: Record<string, string>;
|
||||||
ofac_trees: OfacTree | null;
|
ofac_trees: OfacTree | null;
|
||||||
|
|||||||
@@ -16,7 +16,4 @@ export {
|
|||||||
// From loadingScreenStateText - used in loading screen
|
// From loadingScreenStateText - used in loading screen
|
||||||
export { getLoadingScreenText } from '@/utils/proving/loadingScreenStateText';
|
export { getLoadingScreenText } from '@/utils/proving/loadingScreenStateText';
|
||||||
|
|
||||||
// From validateDocument - used in recovery and splash screens
|
|
||||||
export { isUserRegisteredWithAlternativeCSCA } from '@/utils/proving/validateDocument';
|
|
||||||
|
|
||||||
export { useProvingStore } from '@/utils/proving/provingMachine';
|
export { useProvingStore } from '@/utils/proving/provingMachine';
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ import {
|
|||||||
getSolidityPackedUserContextData,
|
getSolidityPackedUserContextData,
|
||||||
} from '@selfxyz/common/utils';
|
} from '@selfxyz/common/utils';
|
||||||
import { getPublicKey, verifyAttestation } from '@selfxyz/common/utils/attest';
|
import { getPublicKey, verifyAttestation } from '@selfxyz/common/utils/attest';
|
||||||
|
import {
|
||||||
|
checkDocumentSupported,
|
||||||
|
checkIfPassportDscIsInTree,
|
||||||
|
isDocumentNullified,
|
||||||
|
isUserRegistered,
|
||||||
|
isUserRegisteredWithAlternativeCSCA,
|
||||||
|
} from '@selfxyz/common/utils/passports/validate';
|
||||||
import {
|
import {
|
||||||
clientKey,
|
clientKey,
|
||||||
clientPublicKeyHex,
|
clientPublicKeyHex,
|
||||||
@@ -50,13 +57,6 @@ import {
|
|||||||
generateTEEInputsDSC,
|
generateTEEInputsDSC,
|
||||||
generateTEEInputsRegister,
|
generateTEEInputsRegister,
|
||||||
} from '@/utils/proving/provingInputs';
|
} from '@/utils/proving/provingInputs';
|
||||||
import {
|
|
||||||
checkIfPassportDscIsInTree,
|
|
||||||
checkPassportSupported,
|
|
||||||
isDocumentNullified,
|
|
||||||
isUserRegistered,
|
|
||||||
isUserRegisteredWithAlternativeCSCA,
|
|
||||||
} from '@/utils/proving/validateDocument';
|
|
||||||
|
|
||||||
const { trackEvent } = analytics();
|
const { trackEvent } = analytics();
|
||||||
|
|
||||||
@@ -711,7 +711,10 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
|||||||
if (!passportData) {
|
if (!passportData) {
|
||||||
throw new Error('PassportData is not available');
|
throw new Error('PassportData is not available');
|
||||||
}
|
}
|
||||||
const isSupported = await checkPassportSupported(passportData);
|
const isSupported = await checkDocumentSupported(passportData, {
|
||||||
|
getDeployedCircuits: (documentCategory: DocumentCategory) =>
|
||||||
|
useProtocolStore.getState()[documentCategory].deployed_circuits!,
|
||||||
|
});
|
||||||
if (isSupported.status !== 'passport_supported') {
|
if (isSupported.status !== 'passport_supported') {
|
||||||
console.error(
|
console.error(
|
||||||
'Passport not supported:',
|
'Passport not supported:',
|
||||||
@@ -726,13 +729,15 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
|||||||
actor!.send({ type: 'PASSPORT_NOT_SUPPORTED' });
|
actor!.send({ type: 'PASSPORT_NOT_SUPPORTED' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const getCommitmentTree = (documentCategory: DocumentCategory) =>
|
||||||
|
useProtocolStore.getState()[documentCategory].commitment_tree;
|
||||||
/// disclosure
|
/// disclosure
|
||||||
if (circuitType === 'disclose') {
|
if (circuitType === 'disclose') {
|
||||||
// check if the user is registered using the csca from the passport data.
|
// check if the user is registered using the csca from the passport data.
|
||||||
const isRegisteredWithLocalCSCA = await isUserRegistered(
|
const isRegisteredWithLocalCSCA = await isUserRegistered(
|
||||||
passportData,
|
passportData,
|
||||||
secret as string,
|
secret as string,
|
||||||
|
getCommitmentTree,
|
||||||
);
|
);
|
||||||
if (isRegisteredWithLocalCSCA) {
|
if (isRegisteredWithLocalCSCA) {
|
||||||
trackEvent(ProofEvents.VALIDATION_SUCCESS);
|
trackEvent(ProofEvents.VALIDATION_SUCCESS);
|
||||||
@@ -750,6 +755,11 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
|||||||
await isUserRegisteredWithAlternativeCSCA(
|
await isUserRegisteredWithAlternativeCSCA(
|
||||||
passportData,
|
passportData,
|
||||||
secret as string,
|
secret as string,
|
||||||
|
{
|
||||||
|
getCommitmentTree,
|
||||||
|
getAltCSCA: docType =>
|
||||||
|
useProtocolStore.getState()[docType].alternative_csca,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (isRegistered) {
|
if (isRegistered) {
|
||||||
reStorePassportDataWithRightCSCA(passportData, csca as string);
|
reStorePassportDataWithRightCSCA(passportData, csca as string);
|
||||||
|
|||||||
@@ -2,26 +2,8 @@
|
|||||||
// SPDX-License-Identifier: BUSL-1.1
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||||
|
|
||||||
import { poseidon2, poseidon5 } from 'poseidon-lite';
|
import type { PassportData } from '@selfxyz/common/types';
|
||||||
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
import { isUserRegistered } from '@selfxyz/common/utils/passports/validate';
|
||||||
|
|
||||||
import {
|
|
||||||
API_URL,
|
|
||||||
API_URL_STAGING,
|
|
||||||
ID_CARD_ATTESTATION_ID,
|
|
||||||
PASSPORT_ATTESTATION_ID,
|
|
||||||
} from '@selfxyz/common/constants';
|
|
||||||
import type { DocumentCategory, PassportData } from '@selfxyz/common/types';
|
|
||||||
import { parseCertificateSimple } from '@selfxyz/common/utils/certificates/parseSimple';
|
|
||||||
import { getCircuitNameFromPassportData } from '@selfxyz/common/utils/circuitNames';
|
|
||||||
import { packBytesAndPoseidon } from '@selfxyz/common/utils/hash/poseidon';
|
|
||||||
import { hash } from '@selfxyz/common/utils/hash/sha';
|
|
||||||
import { formatMrz } from '@selfxyz/common/utils/passportFormat';
|
|
||||||
import {
|
|
||||||
generateCommitment,
|
|
||||||
generateNullifier,
|
|
||||||
} from '@selfxyz/common/utils/passports';
|
|
||||||
import { getLeafDscTree } from '@selfxyz/common/utils/trees';
|
|
||||||
import type { PassportValidationCallbacks } from '@selfxyz/mobile-sdk-alpha';
|
import type { PassportValidationCallbacks } from '@selfxyz/mobile-sdk-alpha';
|
||||||
import { isPassportDataValid } from '@selfxyz/mobile-sdk-alpha';
|
import { isPassportDataValid } from '@selfxyz/mobile-sdk-alpha';
|
||||||
import { DocumentEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
import { DocumentEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||||
@@ -39,13 +21,6 @@ import analytics from '@/utils/analytics';
|
|||||||
|
|
||||||
const { trackEvent } = analytics();
|
const { trackEvent } = analytics();
|
||||||
|
|
||||||
export type PassportSupportStatus =
|
|
||||||
| 'passport_metadata_missing'
|
|
||||||
| 'csca_not_found'
|
|
||||||
| 'registration_circuit_not_supported'
|
|
||||||
| 'dsc_circuit_not_supported'
|
|
||||||
| 'passport_supported';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function checks and updates registration states for all documents and updates the `isRegistered`.
|
* This function checks and updates registration states for all documents and updates the `isRegistered`.
|
||||||
*/
|
*/
|
||||||
@@ -152,7 +127,11 @@ export async function checkAndUpdateRegistrationStates(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { secret } = JSON.parse(passportDataAndSecret);
|
const { secret } = JSON.parse(passportDataAndSecret);
|
||||||
const isRegistered = await isUserRegistered(migratedPassportData, secret);
|
const isRegistered = await isUserRegistered(
|
||||||
|
migratedPassportData,
|
||||||
|
secret,
|
||||||
|
docType => useProtocolStore.getState()[docType].commitment_tree,
|
||||||
|
);
|
||||||
|
|
||||||
// Update the registration state in the document metadata
|
// Update the registration state in the document metadata
|
||||||
await updateDocumentRegistrationState(documentId, isRegistered);
|
await updateDocumentRegistrationState(documentId, isRegistered);
|
||||||
@@ -183,225 +162,7 @@ export async function checkAndUpdateRegistrationStates(): Promise<void> {
|
|||||||
if (__DEV__) console.log('Registration state check and update completed');
|
if (__DEV__) console.log('Registration state check and update completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkIfPassportDscIsInTree(
|
// UNUSED?
|
||||||
passportData: PassportData,
|
|
||||||
dscTree: string,
|
|
||||||
): Promise<boolean> {
|
|
||||||
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
|
|
||||||
const tree = LeanIMT.import(hashFunction, dscTree);
|
|
||||||
const leaf = getLeafDscTree(
|
|
||||||
passportData.dsc_parsed!,
|
|
||||||
passportData.csca_parsed!,
|
|
||||||
);
|
|
||||||
const index = tree.indexOf(BigInt(leaf));
|
|
||||||
if (index === -1) {
|
|
||||||
console.warn('DSC not found in the tree');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkPassportSupported(
|
|
||||||
passportData: PassportData,
|
|
||||||
): Promise<{
|
|
||||||
status: PassportSupportStatus;
|
|
||||||
details: string;
|
|
||||||
}> {
|
|
||||||
const passportMetadata = passportData.passportMetadata;
|
|
||||||
const document: DocumentCategory = passportData.documentCategory;
|
|
||||||
if (!passportMetadata) {
|
|
||||||
console.warn('Passport metadata is null');
|
|
||||||
return { status: 'passport_metadata_missing', details: passportData.dsc };
|
|
||||||
}
|
|
||||||
if (!passportMetadata.cscaFound) {
|
|
||||||
console.warn('CSCA not found');
|
|
||||||
return { status: 'csca_not_found', details: passportData.dsc };
|
|
||||||
}
|
|
||||||
const circuitNameRegister = getCircuitNameFromPassportData(
|
|
||||||
passportData,
|
|
||||||
'register',
|
|
||||||
);
|
|
||||||
const deployedCircuits =
|
|
||||||
useProtocolStore.getState()[document].deployed_circuits; // change this to the document type
|
|
||||||
if (
|
|
||||||
!circuitNameRegister ||
|
|
||||||
!(
|
|
||||||
deployedCircuits.REGISTER.includes(circuitNameRegister) ||
|
|
||||||
deployedCircuits.REGISTER_ID.includes(circuitNameRegister)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
status: 'registration_circuit_not_supported',
|
|
||||||
details: circuitNameRegister,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const circuitNameDsc = getCircuitNameFromPassportData(passportData, 'dsc');
|
|
||||||
if (
|
|
||||||
!circuitNameDsc ||
|
|
||||||
!(
|
|
||||||
deployedCircuits.DSC.includes(circuitNameDsc) ||
|
|
||||||
deployedCircuits.DSC_ID.includes(circuitNameDsc)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
console.warn('DSC circuit not supported:', circuitNameDsc);
|
|
||||||
return { status: 'dsc_circuit_not_supported', details: circuitNameDsc };
|
|
||||||
}
|
|
||||||
return { status: 'passport_supported', details: 'null' };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateCommitmentInApp(
|
|
||||||
secret: string,
|
|
||||||
attestation_id: string,
|
|
||||||
passportData: PassportData,
|
|
||||||
alternativeCSCA: Record<string, string>,
|
|
||||||
) {
|
|
||||||
const dg1_packed_hash = packBytesAndPoseidon(formatMrz(passportData.mrz));
|
|
||||||
const eContent_packed_hash = packBytesAndPoseidon(
|
|
||||||
(
|
|
||||||
hash(
|
|
||||||
passportData.passportMetadata!.eContentHashFunction,
|
|
||||||
Array.from(passportData.eContent),
|
|
||||||
'bytes',
|
|
||||||
) as number[]
|
|
||||||
)
|
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
.map(byte => byte & 0xff),
|
|
||||||
);
|
|
||||||
|
|
||||||
const csca_list: string[] = [];
|
|
||||||
const commitment_list: string[] = [];
|
|
||||||
|
|
||||||
for (const [cscaKey, cscaValue] of Object.entries(alternativeCSCA)) {
|
|
||||||
try {
|
|
||||||
const formattedCsca = formatCSCAPem(cscaValue);
|
|
||||||
const cscaParsed = parseCertificateSimple(formattedCsca);
|
|
||||||
|
|
||||||
const commitment = poseidon5([
|
|
||||||
secret,
|
|
||||||
attestation_id,
|
|
||||||
dg1_packed_hash,
|
|
||||||
eContent_packed_hash,
|
|
||||||
getLeafDscTree(passportData.dsc_parsed!, cscaParsed),
|
|
||||||
]).toString();
|
|
||||||
|
|
||||||
csca_list.push(formatCSCAPem(cscaValue));
|
|
||||||
commitment_list.push(commitment);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
`Failed to parse CSCA certificate for key ${cscaKey}:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commitment_list.length === 0) {
|
|
||||||
console.error('No valid CSCA certificates found in alternativeCSCA');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { commitment_list, csca_list };
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatCSCAPem(cscaPem: string): string {
|
|
||||||
let cleanedPem = cscaPem.trim();
|
|
||||||
|
|
||||||
if (!cleanedPem.includes('-----BEGIN CERTIFICATE-----')) {
|
|
||||||
cleanedPem = cleanedPem.replace(/[^A-Za-z0-9+/=]/g, '');
|
|
||||||
try {
|
|
||||||
Buffer.from(cleanedPem, 'base64');
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Invalid base64 certificate data: ${error}`);
|
|
||||||
}
|
|
||||||
cleanedPem = `-----BEGIN CERTIFICATE-----\n${cleanedPem}\n-----END CERTIFICATE-----`;
|
|
||||||
}
|
|
||||||
return cleanedPem;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isDocumentNullified(passportData: PassportData) {
|
|
||||||
const nullifier = generateNullifier(passportData);
|
|
||||||
const nullifierHex = `0x${BigInt(nullifier).toString(16)}`;
|
|
||||||
const attestationId =
|
|
||||||
passportData.documentCategory === 'passport'
|
|
||||||
? '0x0000000000000000000000000000000000000000000000000000000000000001'
|
|
||||||
: '0x0000000000000000000000000000000000000000000000000000000000000002';
|
|
||||||
console.log('checking for nullifier', nullifierHex, attestationId);
|
|
||||||
const baseUrl = passportData.mock === false ? API_URL : API_URL_STAGING;
|
|
||||||
const response = await fetch(
|
|
||||||
`${baseUrl}/is-nullifier-onchain-with-attestation-id`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
nullifier: nullifierHex,
|
|
||||||
attestation_id: attestationId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('isDocumentNullified', data);
|
|
||||||
return data.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isUserRegistered(
|
|
||||||
passportData: PassportData,
|
|
||||||
secret: string,
|
|
||||||
) {
|
|
||||||
if (!passportData) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const attestationId =
|
|
||||||
passportData.documentCategory === 'passport'
|
|
||||||
? PASSPORT_ATTESTATION_ID
|
|
||||||
: ID_CARD_ATTESTATION_ID;
|
|
||||||
const commitment = generateCommitment(secret, attestationId, passportData);
|
|
||||||
const document: DocumentCategory = passportData.documentCategory;
|
|
||||||
const serializedTree = useProtocolStore.getState()[document].commitment_tree;
|
|
||||||
const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serializedTree);
|
|
||||||
const index = tree.indexOf(BigInt(commitment));
|
|
||||||
return index !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isUserRegisteredWithAlternativeCSCA(
|
|
||||||
passportData: PassportData,
|
|
||||||
secret: string,
|
|
||||||
): Promise<{ isRegistered: boolean; csca: string | null }> {
|
|
||||||
if (!passportData) {
|
|
||||||
console.error('Passport data is null');
|
|
||||||
return { isRegistered: false, csca: null };
|
|
||||||
}
|
|
||||||
const document: DocumentCategory = passportData.documentCategory;
|
|
||||||
const alternativeCSCA =
|
|
||||||
useProtocolStore.getState()[document].alternative_csca;
|
|
||||||
const { commitment_list, csca_list } = generateCommitmentInApp(
|
|
||||||
secret,
|
|
||||||
document === 'passport' ? PASSPORT_ATTESTATION_ID : ID_CARD_ATTESTATION_ID,
|
|
||||||
passportData,
|
|
||||||
alternativeCSCA,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (commitment_list.length === 0) {
|
|
||||||
console.error(
|
|
||||||
'No valid CSCA certificates could be parsed from alternativeCSCA',
|
|
||||||
);
|
|
||||||
return { isRegistered: false, csca: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
const serializedTree = useProtocolStore.getState()[document].commitment_tree;
|
|
||||||
const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serializedTree);
|
|
||||||
for (let i = 0; i < commitment_list.length; i++) {
|
|
||||||
const commitment = commitment_list[i];
|
|
||||||
const index = tree.indexOf(BigInt(commitment));
|
|
||||||
if (index !== -1) {
|
|
||||||
return { isRegistered: true, csca: csca_list[i] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.warn(
|
|
||||||
'None of the following CSCA correspond to the commitment:',
|
|
||||||
csca_list,
|
|
||||||
);
|
|
||||||
return { isRegistered: false, csca: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MigratedPassportData extends Omit<PassportData, 'documentType'> {
|
interface MigratedPassportData extends Omit<PassportData, 'documentType'> {
|
||||||
documentType?: string;
|
documentType?: string;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"@env": ["./env.ts"],
|
"@env": ["./env.ts"],
|
||||||
"@selfxyz/common": ["../common"],
|
"@selfxyz/common": ["../common"],
|
||||||
"@selfxyz/common/*": ["../common/*"],
|
"@selfxyz/common/*": ["../common/*"],
|
||||||
|
"@selfxyz/common/utils/passports/*": ["../common/utils/passports/*"],
|
||||||
"@selfxyz/mobile-sdk-alpha": [
|
"@selfxyz/mobile-sdk-alpha": [
|
||||||
"../packages/mobile-sdk-alpha/src",
|
"../packages/mobile-sdk-alpha/src",
|
||||||
"../packages/mobile-sdk-alpha/dist"
|
"../packages/mobile-sdk-alpha/dist"
|
||||||
|
|||||||
@@ -271,6 +271,11 @@
|
|||||||
"import": "./dist/esm/src/utils/passports/passport.js",
|
"import": "./dist/esm/src/utils/passports/passport.js",
|
||||||
"require": "./dist/cjs/src/utils/passports/passport.cjs"
|
"require": "./dist/cjs/src/utils/passports/passport.cjs"
|
||||||
},
|
},
|
||||||
|
"./utils/passports/validate": {
|
||||||
|
"types": "./dist/esm/src/utils/passports/validate.d.ts",
|
||||||
|
"import": "./dist/esm/src/utils/passports/validate.js",
|
||||||
|
"require": "./dist/cjs/src/utils/passports/validate.cjs"
|
||||||
|
},
|
||||||
"./utils/passports/genMockIdDoc": {
|
"./utils/passports/genMockIdDoc": {
|
||||||
"types": "./dist/esm/src/utils/passports/genMockIdDoc.d.ts",
|
"types": "./dist/esm/src/utils/passports/genMockIdDoc.d.ts",
|
||||||
"import": "./dist/esm/src/utils/passports/genMockIdDoc.js",
|
"import": "./dist/esm/src/utils/passports/genMockIdDoc.js",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { writeFileSync, mkdirSync, readFileSync } from 'node:fs';
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import { shimConfigs } from './shimConfigs.js';
|
import { shimConfigs } from './shimConfigs.js';
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
@@ -25,6 +26,7 @@ const distPackageJson = {
|
|||||||
'.': './esm/index.js',
|
'.': './esm/index.js',
|
||||||
'./constants': './esm/src/constants/index.js',
|
'./constants': './esm/src/constants/index.js',
|
||||||
'./utils': './esm/src/utils/index.js',
|
'./utils': './esm/src/utils/index.js',
|
||||||
|
'./utils/passports/validate': './esm/src/utils/passports/validate.js',
|
||||||
'./types': './esm/src/types/index.js',
|
'./types': './esm/src/types/index.js',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -214,6 +214,11 @@ export const shimConfigs = [
|
|||||||
targetPath: '../../../esm/src/utils/passports/mockDsc.js',
|
targetPath: '../../../esm/src/utils/passports/mockDsc.js',
|
||||||
name: 'utils/passports/mockDsc',
|
name: 'utils/passports/mockDsc',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
shimPath: 'utils/passports/validate',
|
||||||
|
targetPath: '../../../esm/src/utils/passports/validate.js',
|
||||||
|
name: 'utils/passports/validate',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
shimPath: 'utils/passports/mockGeneration',
|
shimPath: 'utils/passports/mockGeneration',
|
||||||
targetPath: '../../../esm/src/utils/passports/mockGeneration.js',
|
targetPath: '../../../esm/src/utils/passports/mockGeneration.js',
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ export {
|
|||||||
ID_CARD_ATTESTATION_ID,
|
ID_CARD_ATTESTATION_ID,
|
||||||
PASSPORT_ATTESTATION_ID,
|
PASSPORT_ATTESTATION_ID,
|
||||||
PCR0_MANAGER_ADDRESS,
|
PCR0_MANAGER_ADDRESS,
|
||||||
RPC_URL,
|
|
||||||
REDIRECT_URL,
|
REDIRECT_URL,
|
||||||
|
RPC_URL,
|
||||||
RegisterVerifierId,
|
RegisterVerifierId,
|
||||||
SignatureAlgorithmIndex,
|
SignatureAlgorithmIndex,
|
||||||
TREE_URL,
|
TREE_URL,
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ import type { UserIdType } from './circuits/uuid.js';
|
|||||||
import { validateUserId } from './circuits/uuid.js';
|
import { validateUserId } from './circuits/uuid.js';
|
||||||
import { formatEndpoint } from './scope.js';
|
import { formatEndpoint } from './scope.js';
|
||||||
|
|
||||||
|
export interface DeferredLinkingTokenResponse {
|
||||||
|
campaign_id: string;
|
||||||
|
campaign_user_id: string;
|
||||||
|
self_app: string; // SelfApp is serialized as a string
|
||||||
|
}
|
||||||
export type EndpointType = 'https' | 'celo' | 'staging_celo' | 'staging_https';
|
export type EndpointType = 'https' | 'celo' | 'staging_celo' | 'staging_https';
|
||||||
|
|
||||||
export type Mode = 'register' | 'dsc' | 'vc_and_disclose';
|
export type Mode = 'register' | 'dsc' | 'vc_and_disclose';
|
||||||
|
|
||||||
export interface SelfApp {
|
export interface SelfApp {
|
||||||
@@ -121,9 +127,3 @@ export class SelfAppBuilder {
|
|||||||
export function getUniversalLink(selfApp: SelfApp): string {
|
export function getUniversalLink(selfApp: SelfApp): string {
|
||||||
return `${REDIRECT_URL}?selfApp=${encodeURIComponent(JSON.stringify(selfApp))}`;
|
return `${REDIRECT_URL}?selfApp=${encodeURIComponent(JSON.stringify(selfApp))}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeferredLinkingTokenResponse {
|
|
||||||
campaign_id: string;
|
|
||||||
campaign_user_id: string;
|
|
||||||
self_app: string; // SelfApp is serialized as a string
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -27,12 +27,21 @@ export {
|
|||||||
hash,
|
hash,
|
||||||
packBytesAndPoseidon,
|
packBytesAndPoseidon,
|
||||||
} from './hash.js';
|
} from './hash.js';
|
||||||
|
export {
|
||||||
|
clientKey,
|
||||||
|
clientPublicKeyHex,
|
||||||
|
ec,
|
||||||
|
encryptAES256GCM,
|
||||||
|
getPayload,
|
||||||
|
getWSDbRelayerUrl,
|
||||||
|
} from './proving.js';
|
||||||
export {
|
export {
|
||||||
findStartPubKeyIndex,
|
findStartPubKeyIndex,
|
||||||
generateCommitment,
|
generateCommitment,
|
||||||
generateNullifier,
|
generateNullifier,
|
||||||
initPassportDataParsing,
|
initPassportDataParsing,
|
||||||
} from './passports/passport.js';
|
} from './passports/passport.js';
|
||||||
|
export type { TEEPayload, TEEPayloadBase, TEEPayloadDisclose } from './proving.js';
|
||||||
export { formatMrz } from './passports/format.js';
|
export { formatMrz } from './passports/format.js';
|
||||||
export { genAndInitMockPassportData } from './passports/genMockPassportData.js';
|
export { genAndInitMockPassportData } from './passports/genMockPassportData.js';
|
||||||
export {
|
export {
|
||||||
@@ -50,12 +59,3 @@ export { getSKIPEM } from './csca.js';
|
|||||||
export { initElliptic } from './certificate_parsing/elliptic.js';
|
export { initElliptic } from './certificate_parsing/elliptic.js';
|
||||||
export { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple.js';
|
export { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple.js';
|
||||||
export { parseDscCertificateData } from './passports/passport_parsing/parseDscCertificateData.js';
|
export { parseDscCertificateData } from './passports/passport_parsing/parseDscCertificateData.js';
|
||||||
export {
|
|
||||||
clientKey,
|
|
||||||
clientPublicKeyHex,
|
|
||||||
ec,
|
|
||||||
encryptAES256GCM,
|
|
||||||
getPayload,
|
|
||||||
getWSDbRelayerUrl,
|
|
||||||
} from './proving.js';
|
|
||||||
export type { TEEPayload, TEEPayloadBase, TEEPayloadDisclose } from './proving.js';
|
|
||||||
|
|||||||
193
common/src/utils/passports/validate.test.ts
Normal file
193
common/src/utils/passports/validate.test.ts
Normal file
File diff suppressed because one or more lines are too long
256
common/src/utils/passports/validate.ts
Normal file
256
common/src/utils/passports/validate.ts
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||||
|
|
||||||
|
import { poseidon2, poseidon5 } from 'poseidon-lite';
|
||||||
|
|
||||||
|
import {
|
||||||
|
API_URL,
|
||||||
|
API_URL_STAGING,
|
||||||
|
ID_CARD_ATTESTATION_ID,
|
||||||
|
PASSPORT_ATTESTATION_ID,
|
||||||
|
} from '../../constants/index.js';
|
||||||
|
import { parseCertificateSimple } from '../../utils/certificate_parsing/parseSimple.js';
|
||||||
|
import { getCircuitNameFromPassportData } from '../../utils/circuits/circuitsName.js';
|
||||||
|
import { packBytesAndPoseidon } from '../../utils/hash/poseidon.js';
|
||||||
|
import { hash } from '../../utils/hash/sha.js';
|
||||||
|
import { formatMrz } from '../../utils/passports/format.js';
|
||||||
|
import { getLeafDscTree } from '../../utils/trees.js';
|
||||||
|
import {
|
||||||
|
AttestationIdHex,
|
||||||
|
type DeployedCircuits,
|
||||||
|
type DocumentCategory,
|
||||||
|
type PassportData,
|
||||||
|
} from '../types.js';
|
||||||
|
import { generateCommitment, generateNullifier } from './passport.js';
|
||||||
|
|
||||||
|
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||||
|
|
||||||
|
export type PassportSupportStatus =
|
||||||
|
| 'passport_metadata_missing'
|
||||||
|
| 'csca_not_found'
|
||||||
|
| 'registration_circuit_not_supported'
|
||||||
|
| 'dsc_circuit_not_supported'
|
||||||
|
| 'passport_supported';
|
||||||
|
|
||||||
|
export async function checkDocumentSupported(
|
||||||
|
passportData: PassportData,
|
||||||
|
opts: {
|
||||||
|
getDeployedCircuits: (docCategory: DocumentCategory) => DeployedCircuits;
|
||||||
|
}
|
||||||
|
): Promise<{
|
||||||
|
status: PassportSupportStatus;
|
||||||
|
details: string;
|
||||||
|
}> {
|
||||||
|
const passportMetadata = passportData.passportMetadata;
|
||||||
|
const document: DocumentCategory = passportData.documentCategory;
|
||||||
|
if (!passportMetadata) {
|
||||||
|
console.warn('Passport metadata is null');
|
||||||
|
return { status: 'passport_metadata_missing', details: passportData.dsc };
|
||||||
|
}
|
||||||
|
if (!passportMetadata.cscaFound) {
|
||||||
|
console.warn('CSCA not found');
|
||||||
|
return { status: 'csca_not_found', details: passportData.dsc };
|
||||||
|
}
|
||||||
|
const circuitNameRegister = getCircuitNameFromPassportData(passportData, 'register');
|
||||||
|
const deployedCircuits = opts.getDeployedCircuits(passportData.documentCategory);
|
||||||
|
if (
|
||||||
|
!circuitNameRegister ||
|
||||||
|
!(
|
||||||
|
deployedCircuits.REGISTER.includes(circuitNameRegister) ||
|
||||||
|
deployedCircuits.REGISTER_ID.includes(circuitNameRegister)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
status: 'registration_circuit_not_supported',
|
||||||
|
details: circuitNameRegister,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const circuitNameDsc = getCircuitNameFromPassportData(passportData, 'dsc');
|
||||||
|
if (
|
||||||
|
!circuitNameDsc ||
|
||||||
|
!(
|
||||||
|
deployedCircuits.DSC.includes(circuitNameDsc) ||
|
||||||
|
deployedCircuits.DSC_ID.includes(circuitNameDsc)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.warn('DSC circuit not supported:', circuitNameDsc);
|
||||||
|
return { status: 'dsc_circuit_not_supported', details: circuitNameDsc };
|
||||||
|
}
|
||||||
|
return { status: 'passport_supported', details: 'null' };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkIfPassportDscIsInTree(
|
||||||
|
passportData: PassportData,
|
||||||
|
dscTree: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
|
||||||
|
const tree = LeanIMT.import(hashFunction, dscTree);
|
||||||
|
const leaf = getLeafDscTree(passportData.dsc_parsed!, passportData.csca_parsed!);
|
||||||
|
const index = tree.indexOf(BigInt(leaf));
|
||||||
|
if (index === -1) {
|
||||||
|
console.warn('DSC not found in the tree');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlternativeCSCA = Record<string, string>;
|
||||||
|
|
||||||
|
export function generateCommitmentInApp(
|
||||||
|
secret: string,
|
||||||
|
attestation_id: string,
|
||||||
|
passportData: PassportData,
|
||||||
|
alternativeCSCA: AlternativeCSCA
|
||||||
|
) {
|
||||||
|
const dg1_packed_hash = packBytesAndPoseidon(formatMrz(passportData.mrz));
|
||||||
|
const eContent_packed_hash = packBytesAndPoseidon(
|
||||||
|
(
|
||||||
|
hash(
|
||||||
|
passportData.passportMetadata!.eContentHashFunction,
|
||||||
|
Array.from(passportData.eContent),
|
||||||
|
'bytes'
|
||||||
|
) as number[]
|
||||||
|
)
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
.map((byte) => byte & 0xff)
|
||||||
|
);
|
||||||
|
|
||||||
|
const csca_list: string[] = [];
|
||||||
|
const commitment_list: string[] = [];
|
||||||
|
|
||||||
|
for (const [cscaKey, cscaValue] of Object.entries(alternativeCSCA)) {
|
||||||
|
try {
|
||||||
|
const formattedCsca = formatCSCAPem(cscaValue);
|
||||||
|
const cscaParsed = parseCertificateSimple(formattedCsca);
|
||||||
|
|
||||||
|
const commitment = poseidon5([
|
||||||
|
secret,
|
||||||
|
attestation_id,
|
||||||
|
dg1_packed_hash,
|
||||||
|
eContent_packed_hash,
|
||||||
|
getLeafDscTree(passportData.dsc_parsed!, cscaParsed),
|
||||||
|
]).toString();
|
||||||
|
|
||||||
|
csca_list.push(formatCSCAPem(cscaValue));
|
||||||
|
commitment_list.push(commitment);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to parse CSCA certificate for key ${cscaKey}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commitment_list.length === 0) {
|
||||||
|
console.error('No valid CSCA certificates found in alternativeCSCA');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { commitment_list, csca_list };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isDocumentNullified(passportData: PassportData) {
|
||||||
|
const nullifier = generateNullifier(passportData);
|
||||||
|
const nullifierHex = `0x${BigInt(nullifier).toString(16)}`;
|
||||||
|
const attestationId =
|
||||||
|
passportData.documentCategory === 'passport'
|
||||||
|
? AttestationIdHex.passport
|
||||||
|
: AttestationIdHex.id_card;
|
||||||
|
console.log('checking for nullifier', nullifierHex, attestationId);
|
||||||
|
const baseUrl = passportData.mock === false ? API_URL : API_URL_STAGING;
|
||||||
|
const controller = new AbortController();
|
||||||
|
const t = setTimeout(() => controller.abort(), 30000);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}/is-nullifier-onchain-with-attestation-id`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ nullifier: nullifierHex, attestation_id: attestationId }),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
clearTimeout(t);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`isDocumentNullified non-OK response: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
return Boolean(data?.data);
|
||||||
|
} catch (e) {
|
||||||
|
const erorr = e instanceof Error ? e : new Error(String(e));
|
||||||
|
clearTimeout(t);
|
||||||
|
// re throw so our catcher can get this
|
||||||
|
throw new Error(
|
||||||
|
`isDocumentNullified request failed: ${erorr.name} ${erorr.message} \n ${erorr.stack}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isUserRegistered(
|
||||||
|
passportData: PassportData,
|
||||||
|
secret: string,
|
||||||
|
getCommitmentTree: (docCategory: DocumentCategory) => string
|
||||||
|
) {
|
||||||
|
if (!passportData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const attestationId =
|
||||||
|
passportData.documentCategory === 'passport' ? PASSPORT_ATTESTATION_ID : ID_CARD_ATTESTATION_ID;
|
||||||
|
const commitment = generateCommitment(secret, attestationId, passportData);
|
||||||
|
const document: DocumentCategory = passportData.documentCategory;
|
||||||
|
const serializedTree = getCommitmentTree(document);
|
||||||
|
const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serializedTree);
|
||||||
|
const index = tree.indexOf(BigInt(commitment));
|
||||||
|
return index !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isUserRegisteredWithAlternativeCSCA(
|
||||||
|
passportData: PassportData,
|
||||||
|
secret: string,
|
||||||
|
{
|
||||||
|
getCommitmentTree,
|
||||||
|
getAltCSCA,
|
||||||
|
}: {
|
||||||
|
getCommitmentTree: (docCategory: DocumentCategory) => string;
|
||||||
|
getAltCSCA: (docCategory: DocumentCategory) => AlternativeCSCA;
|
||||||
|
}
|
||||||
|
): Promise<{ isRegistered: boolean; csca: string | null }> {
|
||||||
|
if (!passportData) {
|
||||||
|
console.error('Passport data is null');
|
||||||
|
return { isRegistered: false, csca: null };
|
||||||
|
}
|
||||||
|
const document: DocumentCategory = passportData.documentCategory;
|
||||||
|
const alternativeCSCA = getAltCSCA(document);
|
||||||
|
const { commitment_list, csca_list } = generateCommitmentInApp(
|
||||||
|
secret,
|
||||||
|
document === 'passport' ? PASSPORT_ATTESTATION_ID : ID_CARD_ATTESTATION_ID,
|
||||||
|
passportData,
|
||||||
|
alternativeCSCA
|
||||||
|
);
|
||||||
|
|
||||||
|
if (commitment_list.length === 0) {
|
||||||
|
console.error('No valid CSCA certificates could be parsed from alternativeCSCA');
|
||||||
|
return { isRegistered: false, csca: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const serializedTree = getCommitmentTree(document);
|
||||||
|
const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serializedTree);
|
||||||
|
for (let i = 0; i < commitment_list.length; i++) {
|
||||||
|
const commitment = commitment_list[i];
|
||||||
|
const index = tree.indexOf(BigInt(commitment));
|
||||||
|
if (index !== -1) {
|
||||||
|
return { isRegistered: true, csca: csca_list[i] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.warn('None of the following CSCA correspond to the commitment:', csca_list);
|
||||||
|
return { isRegistered: false, csca: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCSCAPem(cscaPem: string): string {
|
||||||
|
let cleanedPem = cscaPem.trim();
|
||||||
|
|
||||||
|
if (!cleanedPem.includes('-----BEGIN CERTIFICATE-----')) {
|
||||||
|
cleanedPem = cleanedPem.replace(/[^A-Za-z0-9+/=]/g, '');
|
||||||
|
try {
|
||||||
|
Buffer.from(cleanedPem, 'base64');
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Invalid base64 certificate data: ${error}`);
|
||||||
|
}
|
||||||
|
cleanedPem = `-----BEGIN CERTIFICATE-----\n${cleanedPem}\n-----END CERTIFICATE-----`;
|
||||||
|
}
|
||||||
|
return cleanedPem;
|
||||||
|
}
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
import type { CertificateData } from './certificate_parsing/dataStructure.js';
|
import type { CertificateData } from './certificate_parsing/dataStructure.js';
|
||||||
import type { PassportMetadata } from './passports/passport_parsing/parsePassportData.js';
|
import type { PassportMetadata } from './passports/passport_parsing/parsePassportData.js';
|
||||||
|
|
||||||
export type DocumentCategory = 'passport' | 'id_card';
|
export type DeployedCircuits = {
|
||||||
|
REGISTER: string[];
|
||||||
export type DocumentType = 'passport' | 'id_card' | 'mock_passport' | 'mock_id_card';
|
REGISTER_ID: string[];
|
||||||
|
DSC: string[];
|
||||||
|
DSC_ID: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export interface DocumentCatalog {
|
export interface DocumentCatalog {
|
||||||
documents: DocumentMetadata[];
|
documents: DocumentMetadata[];
|
||||||
selectedDocumentId?: string; // This is now a contentHash
|
selectedDocumentId?: string; // This is now a contentHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DocumentCategory = 'passport' | 'id_card';
|
||||||
|
|
||||||
export interface DocumentMetadata {
|
export interface DocumentMetadata {
|
||||||
id: string; // contentHash as ID for deduplication
|
id: string; // contentHash as ID for deduplication
|
||||||
documentType: string; // passport, mock_passport, id_card, etc.
|
documentType: string; // passport, mock_passport, id_card, etc.
|
||||||
@@ -19,6 +24,8 @@ export interface DocumentMetadata {
|
|||||||
isRegistered?: boolean; // whether the document is registered onChain
|
isRegistered?: boolean; // whether the document is registered onChain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DocumentType = 'passport' | 'id_card' | 'mock_passport' | 'mock_id_card';
|
||||||
|
|
||||||
export type OfacTree = {
|
export type OfacTree = {
|
||||||
passportNoAndNationality: any;
|
passportNoAndNationality: any;
|
||||||
nameAndDob: any;
|
nameAndDob: any;
|
||||||
@@ -100,6 +107,13 @@ export type SignatureAlgorithm =
|
|||||||
| 'ecdsa_sha384_brainpoolP512r1_512'
|
| 'ecdsa_sha384_brainpoolP512r1_512'
|
||||||
| 'ecdsa_sha512_brainpoolP512r1_512';
|
| 'ecdsa_sha512_brainpoolP512r1_512';
|
||||||
|
|
||||||
|
// keys should match DocumentCategory
|
||||||
|
export enum AttestationIdHex {
|
||||||
|
invalid = '0x0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
passport = '0x0000000000000000000000000000000000000000000000000000000000000001',
|
||||||
|
id_card = '0x0000000000000000000000000000000000000000000000000000000000000002',
|
||||||
|
}
|
||||||
|
|
||||||
export function castCSCAProof(proof: any): Proof {
|
export function castCSCAProof(proof: any): Proof {
|
||||||
return {
|
return {
|
||||||
proof: {
|
proof: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bigIntToString,
|
bigIntToString,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const entry = {
|
|||||||
'src/utils/passports/index': 'src/utils/passports/index.ts',
|
'src/utils/passports/index': 'src/utils/passports/index.ts',
|
||||||
'src/utils/passports/format': 'src/utils/passports/format.ts',
|
'src/utils/passports/format': 'src/utils/passports/format.ts',
|
||||||
'src/utils/passports/mock': 'src/utils/passports/mock.ts',
|
'src/utils/passports/mock': 'src/utils/passports/mock.ts',
|
||||||
|
'src/utils/passports/validate': 'src/utils/passports/validate.ts',
|
||||||
'src/utils/passports/dg1': 'src/utils/passports/dg1.ts',
|
'src/utils/passports/dg1': 'src/utils/passports/dg1.ts',
|
||||||
'src/utils/passports/genMockPassportData': 'src/utils/passports/genMockPassportData.ts',
|
'src/utils/passports/genMockPassportData': 'src/utils/passports/genMockPassportData.ts',
|
||||||
'src/utils/passports/genMockIdDoc': 'src/utils/passports/genMockIdDoc.ts',
|
'src/utils/passports/genMockIdDoc': 'src/utils/passports/genMockIdDoc.ts',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default defineConfig({
|
|||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
environment: 'node',
|
environment: 'node',
|
||||||
include: ['tests/**/*.test.ts'],
|
include: ['**/*.test.ts'],
|
||||||
setupFiles: ['./tests/setup.ts'],
|
setupFiles: ['./tests/setup.ts'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user