mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -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:
@@ -16,7 +16,4 @@ export {
|
||||
// From loadingScreenStateText - used in loading screen
|
||||
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';
|
||||
|
||||
@@ -17,6 +17,13 @@ import {
|
||||
getSolidityPackedUserContextData,
|
||||
} from '@selfxyz/common/utils';
|
||||
import { getPublicKey, verifyAttestation } from '@selfxyz/common/utils/attest';
|
||||
import {
|
||||
checkDocumentSupported,
|
||||
checkIfPassportDscIsInTree,
|
||||
isDocumentNullified,
|
||||
isUserRegistered,
|
||||
isUserRegisteredWithAlternativeCSCA,
|
||||
} from '@selfxyz/common/utils/passports/validate';
|
||||
import {
|
||||
clientKey,
|
||||
clientPublicKeyHex,
|
||||
@@ -50,13 +57,6 @@ import {
|
||||
generateTEEInputsDSC,
|
||||
generateTEEInputsRegister,
|
||||
} from '@/utils/proving/provingInputs';
|
||||
import {
|
||||
checkIfPassportDscIsInTree,
|
||||
checkPassportSupported,
|
||||
isDocumentNullified,
|
||||
isUserRegistered,
|
||||
isUserRegisteredWithAlternativeCSCA,
|
||||
} from '@/utils/proving/validateDocument';
|
||||
|
||||
const { trackEvent } = analytics();
|
||||
|
||||
@@ -711,7 +711,10 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
if (!passportData) {
|
||||
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') {
|
||||
console.error(
|
||||
'Passport not supported:',
|
||||
@@ -726,13 +729,15 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
actor!.send({ type: 'PASSPORT_NOT_SUPPORTED' });
|
||||
return;
|
||||
}
|
||||
|
||||
const getCommitmentTree = (documentCategory: DocumentCategory) =>
|
||||
useProtocolStore.getState()[documentCategory].commitment_tree;
|
||||
/// disclosure
|
||||
if (circuitType === 'disclose') {
|
||||
// check if the user is registered using the csca from the passport data.
|
||||
const isRegisteredWithLocalCSCA = await isUserRegistered(
|
||||
passportData,
|
||||
secret as string,
|
||||
getCommitmentTree,
|
||||
);
|
||||
if (isRegisteredWithLocalCSCA) {
|
||||
trackEvent(ProofEvents.VALIDATION_SUCCESS);
|
||||
@@ -750,6 +755,11 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
await isUserRegisteredWithAlternativeCSCA(
|
||||
passportData,
|
||||
secret as string,
|
||||
{
|
||||
getCommitmentTree,
|
||||
getAltCSCA: docType =>
|
||||
useProtocolStore.getState()[docType].alternative_csca,
|
||||
},
|
||||
);
|
||||
if (isRegistered) {
|
||||
reStorePassportDataWithRightCSCA(passportData, csca as string);
|
||||
|
||||
@@ -2,26 +2,8 @@
|
||||
// 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 { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
|
||||
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 { PassportData } from '@selfxyz/common/types';
|
||||
import { isUserRegistered } from '@selfxyz/common/utils/passports/validate';
|
||||
import type { PassportValidationCallbacks } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { isPassportDataValid } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { DocumentEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
@@ -39,13 +21,6 @@ import analytics from '@/utils/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`.
|
||||
*/
|
||||
@@ -152,7 +127,11 @@ export async function checkAndUpdateRegistrationStates(): Promise<void> {
|
||||
}
|
||||
|
||||
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
|
||||
await updateDocumentRegistrationState(documentId, isRegistered);
|
||||
@@ -183,225 +162,7 @@ export async function checkAndUpdateRegistrationStates(): Promise<void> {
|
||||
if (__DEV__) console.log('Registration state check and update completed');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
// UNUSED?
|
||||
|
||||
interface MigratedPassportData extends Omit<PassportData, 'documentType'> {
|
||||
documentType?: string;
|
||||
|
||||
Reference in New Issue
Block a user