mirror of
https://github.com/selfxyz/self.git
synced 2026-01-10 15:18:18 -05:00
Move self app store to mobile sdk (#1040)
This commit is contained in:
@@ -12,12 +12,12 @@
|
||||
|
||||
### Android
|
||||
|
||||
| Requirement | Version | Installation Guide |
|
||||
| --------------------------- | ------------- | ------------------------------------------------------------------------------------- |
|
||||
| Java | 17 | [Install Java](https://www.oracle.com/java/technologies/javase-jdk17-downloads.html) |
|
||||
| Android Studio (Optional)* | Latest | [Install Android Studio](https://developer.android.com/studio) |
|
||||
| Android SDK | Latest | See instructions for Android below |
|
||||
| Android NDK | 27.0.11718014 | See instructions for Android below |
|
||||
| Requirement | Version | Installation Guide |
|
||||
| --------------------------- | ------------- | ------------------------------------------------------------------------------------ |
|
||||
| Java | 17 | [Install Java](https://www.oracle.com/java/technologies/javase-jdk17-downloads.html) |
|
||||
| Android Studio (Optional)\* | Latest | [Install Android Studio](https://developer.android.com/studio) |
|
||||
| Android SDK | Latest | See instructions for Android below |
|
||||
| Android NDK | 27.0.11718014 | See instructions for Android below |
|
||||
|
||||
\* To facilitate the installation of the SDK and the NDK, and to pair with development devices with a conventient QR code, you can use Android Studio.
|
||||
|
||||
@@ -62,39 +62,40 @@ Under **SDK Platforms**, install the platform with the highest API number
|
||||
|
||||
Under **SDK Tools**, check the **Show Package Details** checkbox, expand **NDK (Side by side)**, select version **27.0.11718014** and install.
|
||||
|
||||
|
||||
#### Using sdkmanager via CLI
|
||||
|
||||
Create a directory for the Android SDK. For example `~/android_sdk`. Define the environment variable `ANDROID_HOME` to point that directory.
|
||||
|
||||
Install sdkmanager under `ANDROID_HOME` according to the instructions on https://developer.android.com/tools/sdkmanager
|
||||
|
||||
|
||||
|
||||
List available SDK platforms
|
||||
|
||||
```bash
|
||||
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --list | grep platforms
|
||||
```
|
||||
|
||||
In the list of platforms, find the latest version and install it. (Replace *NN* with the latest version number)
|
||||
In the list of platforms, find the latest version and install it. (Replace _NN_ with the latest version number)
|
||||
|
||||
```bash
|
||||
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-NN"
|
||||
```
|
||||
|
||||
Install the NDK
|
||||
|
||||
```bash
|
||||
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "ndk;27.0.11718014"
|
||||
```
|
||||
|
||||
Define the environment variable `ANDROID_NDK_VERSION` to `27.0.11718014` and `ANDROID_NDK` to `$ANDROID_HOME/ndk/27.0.11718014`
|
||||
|
||||
Install Platform Tools, needed for the `adb` tool
|
||||
|
||||
```bash
|
||||
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install platform-tools
|
||||
```
|
||||
|
||||
Add `$ANDROID_HOME/platform-tools` to your `$PATH` variable
|
||||
|
||||
|
||||
## Run the app
|
||||
|
||||
### Android
|
||||
@@ -108,11 +109,13 @@ In Android Studio, use Device Manager to pair with and connect to your phone.
|
||||
##### Using adb
|
||||
|
||||
In your phone's developer settings, select **Wireless debugging** > **Pair the device using a pairing code**. Using the displayed information, run
|
||||
|
||||
```
|
||||
adb pair PHONE_IP:PAIRING_PORT PAIRING_CODE
|
||||
```
|
||||
|
||||
To connect to the device, find the IP number and port (different port than in the pairing step) directly under Wireless debugging, and run
|
||||
|
||||
```
|
||||
adb connect PHONE_IP:DEVELOPMENT_PORT
|
||||
```
|
||||
@@ -126,11 +129,11 @@ sdk.dir=/path/to/your/android/sdk
|
||||
```
|
||||
|
||||
or create it with
|
||||
|
||||
```bash
|
||||
echo sdk.dir=$ANDROID_HOME > android/local.properties
|
||||
```
|
||||
|
||||
|
||||
Launch the React Native server:
|
||||
|
||||
```bash
|
||||
@@ -160,6 +163,7 @@ pod install
|
||||
And run the app in Xcode.
|
||||
|
||||
#### Simulator Build
|
||||
|
||||
> **Note:** iOS Simulator on Apple Silicon Macs requires Rosetta (x86_64) mode due to simulator architecture compatibility. If you're using a Silicon Mac (M1/M2/M3/M4), you may find that the Rosetta simulator build option is not available by default in Xcode.
|
||||
|
||||
To enable it, open Xcode and go to **Product > Destination > Show All Run Destinations**. This will unlock the ability to select the Rosetta build simulator, allowing you to run the app in the iOS Simulator.
|
||||
@@ -235,6 +239,7 @@ Deployments happen automatically when you merge PRs:
|
||||
2. **Merge to `main`** → Deploys to production
|
||||
|
||||
To control versions with PR labels:
|
||||
|
||||
- `version:major` - Major version bump
|
||||
- `version:minor` - Minor version bump
|
||||
- `version:patch` - Patch version bump (default for main)
|
||||
@@ -257,6 +262,7 @@ git push && git push --tags
|
||||
```
|
||||
|
||||
The release script will:
|
||||
|
||||
- Check for uncommitted changes
|
||||
- Bump the version in package.json
|
||||
- Update iOS and Android native versions
|
||||
@@ -310,7 +316,9 @@ bundle exec fastlane ios build_local
|
||||
### Troubleshooting Deployments
|
||||
|
||||
#### Version Already Exists
|
||||
|
||||
The build system auto-increments build numbers. If you get version conflicts:
|
||||
|
||||
```bash
|
||||
# Check current versions
|
||||
node scripts/version.cjs status
|
||||
@@ -321,6 +329,7 @@ node scripts/version.cjs bump-build android
|
||||
```
|
||||
|
||||
#### Certificate Issues (iOS)
|
||||
|
||||
```bash
|
||||
# Check certificate validity
|
||||
bundle exec fastlane ios check_certs
|
||||
@@ -332,18 +341,22 @@ bundle exec fastlane ios check_certs
|
||||
```
|
||||
|
||||
#### Play Store Upload Issues
|
||||
|
||||
If automated upload fails, the AAB is saved locally:
|
||||
|
||||
- Location: `android/app/build/outputs/bundle/release/app-release.aab`
|
||||
- Upload manually via Play Console
|
||||
|
||||
### Build Optimization
|
||||
|
||||
The CI/CD pipeline uses extensive caching:
|
||||
|
||||
- **iOS builds**: ~15 minutes (with cache)
|
||||
- **Android builds**: ~10 minutes (with cache)
|
||||
- **First build**: ~25 minutes (no cache)
|
||||
|
||||
To speed up local builds:
|
||||
|
||||
```bash
|
||||
# Clean only what's necessary
|
||||
yarn clean:build # Clean build artifacts only
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
"clean:xcode": "rm -rf ~/Library/Developer/Xcode/DerivedData",
|
||||
"clean:xcode-env-local": "rm -f ios/.xcode.env.local",
|
||||
"find:type-imports": "node scripts/find-type-import-issues.mjs",
|
||||
"fmt": "prettier --check .",
|
||||
"fmt:fix": "prettier --write .",
|
||||
"fmt": "yarn prettier --check .",
|
||||
"fmt:fix": "yarn prettier --write .",
|
||||
"format": "yarn nice",
|
||||
"ia": "yarn install-app",
|
||||
"imports:fix": "node ./scripts/alias-imports.cjs",
|
||||
|
||||
@@ -10,12 +10,12 @@ import type { NativeStackHeaderProps } from '@react-navigation/native-stack';
|
||||
import { Clipboard as ClipboardIcon } from '@tamagui/lucide-icons';
|
||||
|
||||
import type { SelfApp } from '@selfxyz/common/utils/appType';
|
||||
import { useSelfAppStore } from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
import { NavBar } from '@/components/NavBar/BaseNavBar';
|
||||
import ActivityIcon from '@/images/icons/activity.svg';
|
||||
import ScanIcon from '@/images/icons/qr_scan.svg';
|
||||
import SettingsIcon from '@/images/icons/settings.svg';
|
||||
import { useSelfAppStore } from '@/stores/selfAppStore';
|
||||
import { black, charcoal, neutral400, slate50, white } from '@/utils/colors';
|
||||
import { extraYPadding } from '@/utils/constants';
|
||||
import { buttonTap } from '@/utils/haptic';
|
||||
|
||||
@@ -60,13 +60,13 @@ const ConfirmBelongingScreen: React.FC<ConfirmBelongingScreenProps> = () => {
|
||||
if (permissionGranted) {
|
||||
const token = await getFCMToken();
|
||||
if (token) {
|
||||
setFcmToken(token);
|
||||
setFcmToken(token, selfClient);
|
||||
trackEvent(ProofEvents.FCM_TOKEN_STORED);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as user confirmed - proving will start automatically when ready
|
||||
setUserConfirmed();
|
||||
setUserConfirmed(selfClient);
|
||||
|
||||
// Navigate to loading screen
|
||||
navigate();
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useIsFocused } from '@react-navigation/native';
|
||||
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
import { useSelfAppStore } from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
import loadingAnimation from '@/assets/animations/loading/misc.json';
|
||||
import failAnimation from '@/assets/animations/proof_failed.json';
|
||||
@@ -24,7 +25,6 @@ import useHapticNavigation from '@/hooks/useHapticNavigation';
|
||||
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
|
||||
import { ProofStatus } from '@/stores/proof-types';
|
||||
import { useProofHistoryStore } from '@/stores/proofHistoryStore';
|
||||
import { useSelfAppStore } from '@/stores/selfAppStore';
|
||||
import { black, white } from '@/utils/colors';
|
||||
import {
|
||||
buttonTap,
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { SelfAppDisclosureConfig } from '@selfxyz/common/utils/appType';
|
||||
import { formatEndpoint } from '@selfxyz/common/utils/scope';
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
import { useSelfAppStore } from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
import miscAnimation from '@/assets/animations/loading/misc.json';
|
||||
import { HeldPrimaryButtonProveScreen } from '@/components/buttons/HeldPrimaryButtonProveScreen';
|
||||
@@ -37,7 +38,6 @@ import {
|
||||
} from '@/providers/passportDataProvider';
|
||||
import { ProofStatus } from '@/stores/proof-types';
|
||||
import { useProofHistoryStore } from '@/stores/proofHistoryStore';
|
||||
import { useSelfAppStore } from '@/stores/selfAppStore';
|
||||
import { black, slate300, white } from '@/utils/colors';
|
||||
import { formatUserId } from '@/utils/formatUserId';
|
||||
import { buttonTap } from '@/utils/haptic';
|
||||
@@ -149,7 +149,7 @@ const ProveScreen: React.FC = () => {
|
||||
);
|
||||
|
||||
function onVerify() {
|
||||
provingStore.setUserConfirmed();
|
||||
provingStore.setUserConfirmed(selfClient);
|
||||
buttonTap();
|
||||
trackEvent(ProofEvents.PROOF_VERIFY_CONFIRMATION_ACCEPTED, {
|
||||
appName: selectedApp?.appName,
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
|
||||
import { useSelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import { ProofEvents } from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
import { useSelfAppStore } from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
import qrScanAnimation from '@/assets/animations/qr_scan.json';
|
||||
import { SecondaryButton } from '@/components/buttons/SecondaryButton';
|
||||
@@ -26,7 +27,6 @@ import useConnectionModal from '@/hooks/useConnectionModal';
|
||||
import useHapticNavigation from '@/hooks/useHapticNavigation';
|
||||
import QRScan from '@/images/icons/qr_code.svg';
|
||||
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
|
||||
import { useSelfAppStore } from '@/stores/selfAppStore';
|
||||
import { black, slate800, white } from '@/utils/colors';
|
||||
import { parseAndValidateUrlParams } from '@/utils/deeplinks';
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import { Linking, Platform } from 'react-native';
|
||||
|
||||
import { countries } from '@selfxyz/common/constants/countries';
|
||||
import type { IdDocInput } from '@selfxyz/common/utils';
|
||||
import { useSelfAppStore } from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
import { navigationRef } from '@/navigation';
|
||||
import { useSelfAppStore } from '@/stores/selfAppStore';
|
||||
import useUserStore from '@/stores/userStore';
|
||||
|
||||
// Validation patterns for each expected parameter
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// 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 type { DocumentCategory, PassportData } from '@selfxyz/common/types';
|
||||
import type { SelfApp } from '@selfxyz/common/utils';
|
||||
import { generateTEEInputsDiscloseStateless } from '@selfxyz/common/utils/circuits/registerInputs';
|
||||
import { useProtocolStore } from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
export function generateTEEInputsDisclose(
|
||||
secret: string,
|
||||
passportData: PassportData,
|
||||
selfApp: SelfApp,
|
||||
) {
|
||||
return generateTEEInputsDiscloseStateless(
|
||||
secret,
|
||||
passportData,
|
||||
selfApp,
|
||||
(document: DocumentCategory, tree) => {
|
||||
const protocolStore = useProtocolStore.getState();
|
||||
switch (tree) {
|
||||
case 'ofac':
|
||||
return protocolStore[document].ofac_trees;
|
||||
case 'commitment':
|
||||
return protocolStore[document].commitment_tree;
|
||||
default:
|
||||
throw new Error('Unknown tree type');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from '@selfxyz/common/utils/proving';
|
||||
import {
|
||||
clearPassportData,
|
||||
generateTEEInputsDisclose,
|
||||
hasAnyValidRegisteredDocument,
|
||||
loadSelectedDocument,
|
||||
markCurrentDocumentAsRegistered,
|
||||
@@ -49,13 +50,10 @@ import {
|
||||
PassportEvents,
|
||||
ProofEvents,
|
||||
} from '@selfxyz/mobile-sdk-alpha/constants/analytics';
|
||||
import { useProtocolStore } from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
import { useSelfAppStore } from '@/stores/selfAppStore';
|
||||
import analytics from '@/utils/analytics';
|
||||
import { generateTEEInputsDisclose } from '@/utils/proving/provingInputs';
|
||||
|
||||
const { trackEvent } = analytics();
|
||||
import {
|
||||
useProtocolStore,
|
||||
useSelfAppStore,
|
||||
} from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
export type ProvingStateType =
|
||||
// Initial states
|
||||
@@ -163,12 +161,20 @@ export const getPostVerificationRoute = () => {
|
||||
// return cloudBackupEnabled ? 'AccountVerifiedSuccess' : 'SaveRecoveryPhrase';
|
||||
};
|
||||
|
||||
type WsHandlers = {
|
||||
message: (event: MessageEvent) => void;
|
||||
open: () => void;
|
||||
error: (error: Event) => void;
|
||||
close: (event: CloseEvent) => void;
|
||||
};
|
||||
|
||||
interface ProvingState {
|
||||
currentState: ProvingStateType;
|
||||
attestation: number[] | null;
|
||||
serverPublicKey: string | null;
|
||||
sharedKey: Buffer | null;
|
||||
wsConnection: WebSocket | null;
|
||||
wsHandlers: WsHandlers | null;
|
||||
socketConnection: Socket | null;
|
||||
uuid: string | null;
|
||||
userConfirmed: boolean;
|
||||
@@ -180,30 +186,43 @@ interface ProvingState {
|
||||
endpointType: EndpointType | null;
|
||||
fcmToken: string | null;
|
||||
env: 'prod' | 'stg' | null;
|
||||
selfClient: SelfClient | null;
|
||||
setFcmToken: (token: string) => void;
|
||||
setFcmToken: (token: string, selfClient: SelfClient) => void;
|
||||
init: (
|
||||
selfClient: SelfClient,
|
||||
circuitType: 'dsc' | 'disclose' | 'register',
|
||||
userConfirmed?: boolean,
|
||||
) => Promise<void>;
|
||||
startFetchingData: () => Promise<void>;
|
||||
startFetchingData: (selfClient: SelfClient) => Promise<void>;
|
||||
validatingDocument: (selfClient: SelfClient) => Promise<void>;
|
||||
initTeeConnection: () => Promise<boolean>;
|
||||
startProving: () => Promise<void>;
|
||||
initTeeConnection: (selfClient: SelfClient) => Promise<boolean>;
|
||||
startProving: (selfClient: SelfClient) => Promise<void>;
|
||||
postProving: (selfClient: SelfClient) => void;
|
||||
setUserConfirmed: () => void;
|
||||
_closeConnections: () => void;
|
||||
_generatePayload: () => Promise<unknown>;
|
||||
_handleWebSocketMessage: (event: MessageEvent) => Promise<void>;
|
||||
setUserConfirmed: (selfClient: SelfClient) => void;
|
||||
_closeConnections: (selfClient: SelfClient) => void;
|
||||
_generatePayload: (selfClient: SelfClient) => Promise<{
|
||||
jsonrpc: '2.0';
|
||||
method: 'openpassport_submit_request';
|
||||
id: 2;
|
||||
params: {
|
||||
uuid: string | null;
|
||||
nonce: number[];
|
||||
cipher_text: number[];
|
||||
auth_tag: number[];
|
||||
};
|
||||
}>;
|
||||
_handleWebSocketMessage: (
|
||||
event: MessageEvent,
|
||||
selfClient: SelfClient,
|
||||
) => Promise<void>;
|
||||
_handleRegisterErrorOrFailure: (selfClient: SelfClient) => void;
|
||||
_startSocketIOStatusListener: (
|
||||
receivedUuid: string,
|
||||
endpointType: EndpointType,
|
||||
selfClient: SelfClient,
|
||||
) => void;
|
||||
_handleWsOpen: () => void;
|
||||
_handleWsError: (error: Event) => void;
|
||||
_handleWsClose: (event: CloseEvent) => void;
|
||||
_handleWsOpen: (selfClient: SelfClient) => void;
|
||||
_handleWsError: (error: Event, selfClient: SelfClient) => void;
|
||||
_handleWsClose: (event: CloseEvent, selfClient: SelfClient) => void;
|
||||
|
||||
_handlePassportNotSupported: (selfClient: SelfClient) => void;
|
||||
_handleAccountRecoveryChoice: (selfClient: SelfClient) => void;
|
||||
@@ -220,22 +239,24 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
) {
|
||||
newActor.subscribe((state: StateFrom<typeof provingMachine>) => {
|
||||
console.log(`State transition: ${state.value}`);
|
||||
trackEvent(ProofEvents.PROVING_STATE_CHANGE, { state: state.value });
|
||||
selfClient.trackEvent(ProofEvents.PROVING_STATE_CHANGE, {
|
||||
state: state.value,
|
||||
});
|
||||
set({ currentState: state.value as ProvingStateType });
|
||||
|
||||
if (state.value === 'fetching_data') {
|
||||
get().startFetchingData();
|
||||
get().startFetchingData(selfClient);
|
||||
}
|
||||
if (state.value === 'validating_document') {
|
||||
get().validatingDocument(selfClient);
|
||||
}
|
||||
|
||||
if (state.value === 'init_tee_connexion') {
|
||||
get().initTeeConnection();
|
||||
get().initTeeConnection(selfClient);
|
||||
}
|
||||
|
||||
if (state.value === 'ready_to_prove' && get().userConfirmed) {
|
||||
get().startProving();
|
||||
get().startProving(selfClient);
|
||||
}
|
||||
|
||||
if (state.value === 'post_proving') {
|
||||
@@ -250,7 +271,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
}
|
||||
|
||||
if (state.value === 'completed') {
|
||||
trackEvent(ProofEvents.PROOF_COMPLETED, {
|
||||
selfClient.trackEvent(ProofEvents.PROOF_COMPLETED, {
|
||||
circuitType: get().circuitType,
|
||||
});
|
||||
|
||||
@@ -313,6 +334,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
serverPublicKey: null,
|
||||
sharedKey: null,
|
||||
wsConnection: null,
|
||||
wsHandlers: null,
|
||||
socketConnection: null,
|
||||
uuid: null,
|
||||
userConfirmed: false,
|
||||
@@ -325,21 +347,22 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
reason: null,
|
||||
endpointType: null,
|
||||
fcmToken: null,
|
||||
selfClient: null,
|
||||
setFcmToken: (token: string) => {
|
||||
setFcmToken: (token: string, selfClient: SelfClient) => {
|
||||
set({ fcmToken: token });
|
||||
trackEvent(ProofEvents.FCM_TOKEN_STORED);
|
||||
selfClient.trackEvent(ProofEvents.FCM_TOKEN_STORED);
|
||||
},
|
||||
_handleWebSocketMessage: async (event: MessageEvent) => {
|
||||
_handleWebSocketMessage: async (
|
||||
event: MessageEvent,
|
||||
selfClient: SelfClient,
|
||||
) => {
|
||||
if (!actor) {
|
||||
console.error('Cannot process message: State machine not initialized.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = JSON.parse(event.data);
|
||||
if (result.result?.attestation) {
|
||||
trackEvent(ProofEvents.ATTESTATION_RECEIVED);
|
||||
selfClient?.trackEvent(ProofEvents.ATTESTATION_RECEIVED);
|
||||
const attestationData = result.result.attestation;
|
||||
set({ attestation: attestationData });
|
||||
|
||||
@@ -352,7 +375,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
return;
|
||||
}
|
||||
|
||||
trackEvent(ProofEvents.ATTESTATION_VERIFIED);
|
||||
selfClient?.trackEvent(ProofEvents.ATTESTATION_VERIFIED);
|
||||
|
||||
const serverKey = ec.keyFromPublic(serverPubkey as string, 'hex');
|
||||
const derivedKey = clientKey.derive(serverKey.getPublic());
|
||||
@@ -361,7 +384,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
serverPublicKey: serverPubkey,
|
||||
sharedKey: Buffer.from(derivedKey.toArray('be', 32)),
|
||||
});
|
||||
trackEvent(ProofEvents.SHARED_KEY_DERIVED);
|
||||
selfClient?.trackEvent(ProofEvents.SHARED_KEY_DERIVED);
|
||||
|
||||
actor!.send({ type: 'CONNECT_SUCCESS' });
|
||||
} else if (
|
||||
@@ -369,7 +392,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
typeof result.result === 'string' &&
|
||||
!result.error
|
||||
) {
|
||||
trackEvent(ProofEvents.WS_HELLO_ACK);
|
||||
selfClient?.trackEvent(ProofEvents.WS_HELLO_ACK);
|
||||
// Received status from TEE
|
||||
const statusUuid = result.result;
|
||||
if (get().uuid !== statusUuid) {
|
||||
@@ -384,18 +407,24 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
console.error(
|
||||
'Cannot start Socket.IO listener: endpointType not set.',
|
||||
);
|
||||
trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
selfClient?.trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
circuitType: get().circuitType,
|
||||
error: get().error_code ?? 'unknown',
|
||||
});
|
||||
actor!.send({ type: 'PROVE_ERROR' });
|
||||
return;
|
||||
}
|
||||
get()._startSocketIOStatusListener(statusUuid, endpointType);
|
||||
get()._startSocketIOStatusListener(
|
||||
statusUuid,
|
||||
endpointType,
|
||||
selfClient,
|
||||
);
|
||||
} else if (result.error) {
|
||||
console.error('Received error from TEE:', result.error);
|
||||
trackEvent(ProofEvents.TEE_WS_ERROR, { error: result.error });
|
||||
trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
selfClient?.trackEvent(ProofEvents.TEE_WS_ERROR, {
|
||||
error: result.error,
|
||||
});
|
||||
selfClient?.trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
circuitType: get().circuitType,
|
||||
error: get().error_code ?? 'unknown',
|
||||
});
|
||||
@@ -406,15 +435,15 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
} catch (error) {
|
||||
console.error('Error processing WebSocket message:', error);
|
||||
if (get().currentState === 'init_tee_connexion') {
|
||||
trackEvent(ProofEvents.TEE_CONN_FAILED, {
|
||||
selfClient?.trackEvent(ProofEvents.TEE_CONN_FAILED, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
actor!.send({ type: 'CONNECT_ERROR' });
|
||||
} else {
|
||||
trackEvent(ProofEvents.TEE_WS_ERROR, {
|
||||
selfClient?.trackEvent(ProofEvents.TEE_WS_ERROR, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
selfClient?.trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
circuitType: get().circuitType,
|
||||
error: get().error_code ?? 'unknown',
|
||||
});
|
||||
@@ -440,6 +469,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
_startSocketIOStatusListener: (
|
||||
receivedUuid: string,
|
||||
endpointType: EndpointType,
|
||||
selfClient: SelfClient,
|
||||
) => {
|
||||
if (!actor) {
|
||||
console.error('Cannot start Socket.IO listener: Actor not available.');
|
||||
@@ -452,19 +482,19 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
transports: ['websocket'],
|
||||
});
|
||||
set({ socketConnection: socket });
|
||||
trackEvent(ProofEvents.SOCKETIO_CONN_STARTED);
|
||||
selfClient.trackEvent(ProofEvents.SOCKETIO_CONN_STARTED);
|
||||
|
||||
socket.on('connect', () => {
|
||||
socket?.emit('subscribe', receivedUuid);
|
||||
trackEvent(ProofEvents.SOCKETIO_SUBSCRIBED);
|
||||
selfClient.trackEvent(ProofEvents.SOCKETIO_SUBSCRIBED);
|
||||
});
|
||||
|
||||
socket.on('connect_error', error => {
|
||||
console.error('SocketIO connection error:', error);
|
||||
trackEvent(ProofEvents.SOCKETIO_CONNECT_ERROR, {
|
||||
selfClient.trackEvent(ProofEvents.SOCKETIO_CONNECT_ERROR, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
circuitType: get().circuitType,
|
||||
error: get().error_code ?? 'unknown',
|
||||
});
|
||||
@@ -479,8 +509,8 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
console.error(
|
||||
'SocketIO disconnected unexpectedly during proof listening.',
|
||||
);
|
||||
trackEvent(ProofEvents.SOCKETIO_DISCONNECT_UNEXPECTED);
|
||||
trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.SOCKETIO_DISCONNECT_UNEXPECTED);
|
||||
selfClient.trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
circuitType: get().circuitType,
|
||||
error: get().error_code ?? 'unknown',
|
||||
});
|
||||
@@ -492,7 +522,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
socket.on('status', (message: unknown) => {
|
||||
const data =
|
||||
typeof message === 'string' ? JSON.parse(message) : message;
|
||||
trackEvent(ProofEvents.SOCKETIO_STATUS_RECEIVED, {
|
||||
selfClient.trackEvent(ProofEvents.SOCKETIO_STATUS_RECEIVED, {
|
||||
status: data.status,
|
||||
});
|
||||
if (data.status === 3 || data.status === 5) {
|
||||
@@ -501,11 +531,11 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
);
|
||||
console.error(data);
|
||||
set({ error_code: data.error_code, reason: data.reason });
|
||||
trackEvent(ProofEvents.SOCKETIO_PROOF_FAILURE, {
|
||||
selfClient.trackEvent(ProofEvents.SOCKETIO_PROOF_FAILURE, {
|
||||
error_code: data.error_code,
|
||||
reason: data.reason,
|
||||
});
|
||||
trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
circuitType: get().circuitType,
|
||||
error: data.error_code ?? 'unknown',
|
||||
});
|
||||
@@ -516,15 +546,15 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
socket?.disconnect();
|
||||
set({ socketConnection: null });
|
||||
if (get().circuitType === 'register') {
|
||||
trackEvent(ProofEvents.REGISTER_COMPLETED);
|
||||
selfClient.trackEvent(ProofEvents.REGISTER_COMPLETED);
|
||||
}
|
||||
trackEvent(ProofEvents.SOCKETIO_PROOF_SUCCESS);
|
||||
selfClient.trackEvent(ProofEvents.SOCKETIO_PROOF_SUCCESS);
|
||||
actor!.send({ type: 'PROVE_SUCCESS' });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_handleWsOpen: () => {
|
||||
_handleWsOpen: (selfClient: SelfClient) => {
|
||||
if (!actor) {
|
||||
return;
|
||||
}
|
||||
@@ -534,7 +564,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
}
|
||||
const connectionUuid = v4();
|
||||
|
||||
trackEvent(ProofEvents.CONNECTION_UUID_GENERATED, {
|
||||
selfClient.trackEvent(ProofEvents.CONNECTION_UUID_GENERATED, {
|
||||
connection_uuid: connectionUuid,
|
||||
});
|
||||
|
||||
@@ -551,11 +581,11 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
uuid: connectionUuid,
|
||||
},
|
||||
};
|
||||
trackEvent(ProofEvents.WS_HELLO_SENT);
|
||||
selfClient.trackEvent(ProofEvents.WS_HELLO_SENT);
|
||||
ws.send(JSON.stringify(helloBody));
|
||||
},
|
||||
|
||||
_handleWsError: (error: Event) => {
|
||||
_handleWsError: (error: Event, selfClient: SelfClient) => {
|
||||
console.error('TEE WebSocket error event:', error);
|
||||
if (!actor) {
|
||||
return;
|
||||
@@ -564,11 +594,12 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
new MessageEvent('error', {
|
||||
data: JSON.stringify({ error: 'WebSocket connection error' }),
|
||||
}),
|
||||
selfClient,
|
||||
);
|
||||
},
|
||||
|
||||
_handleWsClose: (event: CloseEvent) => {
|
||||
trackEvent(ProofEvents.TEE_WS_CLOSED, {
|
||||
_handleWsClose: (event: CloseEvent, selfClient: SelfClient) => {
|
||||
selfClient.trackEvent(ProofEvents.TEE_WS_CLOSED, {
|
||||
code: event.code,
|
||||
reason: event.reason,
|
||||
});
|
||||
@@ -588,6 +619,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
new MessageEvent('error', {
|
||||
data: JSON.stringify({ error: 'WebSocket closed unexpectedly' }),
|
||||
}),
|
||||
selfClient,
|
||||
);
|
||||
}
|
||||
if (get().wsConnection) {
|
||||
@@ -600,8 +632,8 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
circuitType: 'dsc' | 'disclose' | 'register',
|
||||
userConfirmed: boolean = false,
|
||||
) => {
|
||||
trackEvent(ProofEvents.PROVING_INIT);
|
||||
get()._closeConnections();
|
||||
selfClient.trackEvent(ProofEvents.PROVING_INIT);
|
||||
get()._closeConnections(selfClient);
|
||||
|
||||
if (actor) {
|
||||
try {
|
||||
@@ -624,28 +656,29 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
circuitType,
|
||||
endpointType: null,
|
||||
env: null,
|
||||
selfClient,
|
||||
});
|
||||
|
||||
actor = createActor(provingMachine);
|
||||
setupActorSubscriptions(actor, selfClient);
|
||||
actor.start();
|
||||
|
||||
trackEvent(ProofEvents.DOCUMENT_LOAD_STARTED);
|
||||
selfClient.trackEvent(ProofEvents.DOCUMENT_LOAD_STARTED);
|
||||
const selectedDocument = await loadSelectedDocument(selfClient);
|
||||
if (!selectedDocument) {
|
||||
console.error('No document found for proving');
|
||||
trackEvent(PassportEvents.PASSPORT_DATA_NOT_FOUND, { stage: 'init' });
|
||||
selfClient.trackEvent(PassportEvents.PASSPORT_DATA_NOT_FOUND, {
|
||||
stage: 'init',
|
||||
});
|
||||
actor!.send({ type: 'PASSPORT_DATA_NOT_FOUND' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: passportData } = selectedDocument;
|
||||
|
||||
const secret = await get().selfClient?.getPrivateKey();
|
||||
const secret = await selfClient.getPrivateKey();
|
||||
if (!secret) {
|
||||
console.error('Could not load secret');
|
||||
trackEvent(ProofEvents.LOAD_SECRET_FAILED);
|
||||
selfClient.trackEvent(ProofEvents.LOAD_SECRET_FAILED);
|
||||
actor!.send({ type: 'ERROR' });
|
||||
return;
|
||||
}
|
||||
@@ -656,12 +689,12 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
set({ passportData, secret, env });
|
||||
set({ circuitType });
|
||||
actor.send({ type: 'FETCH_DATA' });
|
||||
trackEvent(ProofEvents.FETCH_DATA_STARTED);
|
||||
selfClient.trackEvent(ProofEvents.FETCH_DATA_STARTED);
|
||||
},
|
||||
|
||||
startFetchingData: async () => {
|
||||
startFetchingData: async (selfClient: SelfClient) => {
|
||||
_checkActorInitialized(actor);
|
||||
trackEvent(ProofEvents.FETCH_DATA_STARTED);
|
||||
selfClient.trackEvent(ProofEvents.FETCH_DATA_STARTED);
|
||||
try {
|
||||
const { passportData, env } = get();
|
||||
if (!passportData) {
|
||||
@@ -669,7 +702,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
}
|
||||
if (!passportData?.dsc_parsed) {
|
||||
console.error('Missing parsed DSC in passport data');
|
||||
trackEvent(ProofEvents.FETCH_DATA_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.FETCH_DATA_FAILED, {
|
||||
message: 'Missing parsed DSC in passport data',
|
||||
});
|
||||
actor!.send({ type: 'FETCH_ERROR' });
|
||||
@@ -681,11 +714,11 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
[
|
||||
document
|
||||
].fetch_all(env!, (passportData as PassportData).dsc_parsed!.authorityKeyIdentifier);
|
||||
trackEvent(ProofEvents.FETCH_DATA_SUCCESS);
|
||||
selfClient.trackEvent(ProofEvents.FETCH_DATA_SUCCESS);
|
||||
actor!.send({ type: 'FETCH_SUCCESS' });
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
trackEvent(ProofEvents.FETCH_DATA_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.FETCH_DATA_FAILED, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
actor!.send({ type: 'FETCH_ERROR' });
|
||||
@@ -695,7 +728,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
validatingDocument: async (selfClient: SelfClient) => {
|
||||
_checkActorInitialized(actor);
|
||||
// TODO: for the disclosure, we could check that the selfApp is a valid one.
|
||||
trackEvent(ProofEvents.VALIDATION_STARTED);
|
||||
selfClient.trackEvent(ProofEvents.VALIDATION_STARTED);
|
||||
try {
|
||||
const { passportData, secret, circuitType } = get();
|
||||
if (!passportData) {
|
||||
@@ -711,7 +744,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
isSupported.status,
|
||||
isSupported.details,
|
||||
);
|
||||
trackEvent(PassportEvents.UNSUPPORTED_PASSPORT, {
|
||||
selfClient.trackEvent(PassportEvents.UNSUPPORTED_PASSPORT, {
|
||||
status: isSupported.status,
|
||||
details: isSupported.details,
|
||||
});
|
||||
@@ -732,7 +765,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
getCommitmentTree,
|
||||
);
|
||||
if (isRegisteredWithLocalCSCA) {
|
||||
trackEvent(ProofEvents.VALIDATION_SUCCESS);
|
||||
selfClient.trackEvent(ProofEvents.VALIDATION_SUCCESS);
|
||||
actor!.send({ type: 'VALIDATION_SUCCESS' });
|
||||
return;
|
||||
} else {
|
||||
@@ -770,7 +803,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
}
|
||||
})();
|
||||
|
||||
trackEvent(ProofEvents.ALREADY_REGISTERED);
|
||||
selfClient.trackEvent(ProofEvents.ALREADY_REGISTERED);
|
||||
actor!.send({ type: 'ALREADY_REGISTERED' });
|
||||
return;
|
||||
}
|
||||
@@ -779,7 +812,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
console.warn(
|
||||
'Passport is nullified, but not registered with this secret. Navigating to AccountRecoveryChoice',
|
||||
);
|
||||
trackEvent(ProofEvents.PASSPORT_NULLIFIER_ONCHAIN);
|
||||
selfClient.trackEvent(ProofEvents.PASSPORT_NULLIFIER_ONCHAIN);
|
||||
actor!.send({ type: 'ACCOUNT_RECOVERY_CHOICE' });
|
||||
return;
|
||||
}
|
||||
@@ -789,22 +822,22 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
useProtocolStore.getState()[document].dsc_tree,
|
||||
);
|
||||
if (isDscRegistered) {
|
||||
trackEvent(ProofEvents.DSC_IN_TREE);
|
||||
selfClient.trackEvent(ProofEvents.DSC_IN_TREE);
|
||||
set({ circuitType: 'register' });
|
||||
}
|
||||
trackEvent(ProofEvents.VALIDATION_SUCCESS);
|
||||
selfClient.trackEvent(ProofEvents.VALIDATION_SUCCESS);
|
||||
actor!.send({ type: 'VALIDATION_SUCCESS' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error validating passport:', error);
|
||||
trackEvent(ProofEvents.VALIDATION_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.VALIDATION_FAILED, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
actor!.send({ type: 'VALIDATION_ERROR' });
|
||||
}
|
||||
},
|
||||
|
||||
initTeeConnection: async (): Promise<boolean> => {
|
||||
initTeeConnection: async (selfClient: SelfClient): Promise<boolean> => {
|
||||
const { passportData } = get();
|
||||
if (!passportData) {
|
||||
throw new Error('PassportData is not available');
|
||||
@@ -850,26 +883,36 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
throw new Error('No WebSocket URL available for TEE connection');
|
||||
}
|
||||
|
||||
get()._closeConnections();
|
||||
trackEvent(ProofEvents.TEE_CONN_STARTED);
|
||||
get()._closeConnections(selfClient);
|
||||
selfClient.trackEvent(ProofEvents.TEE_CONN_STARTED);
|
||||
|
||||
return new Promise(resolve => {
|
||||
const ws = new WebSocket(wsRpcUrl);
|
||||
set({ wsConnection: ws });
|
||||
|
||||
const handleConnectSuccess = () => {
|
||||
trackEvent(ProofEvents.TEE_CONN_SUCCESS);
|
||||
selfClient.trackEvent(ProofEvents.TEE_CONN_SUCCESS);
|
||||
resolve(true);
|
||||
};
|
||||
const handleConnectError = (msg: string = 'connect_error') => {
|
||||
trackEvent(ProofEvents.TEE_CONN_FAILED, { message: msg });
|
||||
selfClient.trackEvent(ProofEvents.TEE_CONN_FAILED, { message: msg });
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
ws.addEventListener('message', get()._handleWebSocketMessage);
|
||||
ws.addEventListener('open', get()._handleWsOpen);
|
||||
ws.addEventListener('error', get()._handleWsError);
|
||||
ws.addEventListener('close', get()._handleWsClose);
|
||||
// Create stable handler functions
|
||||
const wsHandlers: WsHandlers = {
|
||||
message: (event: MessageEvent) =>
|
||||
get()._handleWebSocketMessage(event, selfClient),
|
||||
open: () => get()._handleWsOpen(selfClient),
|
||||
error: (error: Event) => get()._handleWsError(error, selfClient),
|
||||
close: (event: CloseEvent) => get()._handleWsClose(event, selfClient),
|
||||
};
|
||||
|
||||
set({ wsConnection: ws, wsHandlers });
|
||||
|
||||
ws.addEventListener('message', wsHandlers.message);
|
||||
ws.addEventListener('open', wsHandlers.open);
|
||||
ws.addEventListener('error', wsHandlers.error);
|
||||
ws.addEventListener('close', wsHandlers.close);
|
||||
|
||||
if (!actor) {
|
||||
return;
|
||||
@@ -886,7 +929,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
});
|
||||
},
|
||||
|
||||
startProving: async () => {
|
||||
startProving: async (selfClient: SelfClient) => {
|
||||
_checkActorInitialized(actor);
|
||||
const { wsConnection, sharedKey, passportData, secret, uuid, fcmToken } =
|
||||
get();
|
||||
@@ -899,7 +942,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
console.error(
|
||||
'Cannot start proving: Missing wsConnection, sharedKey, passportData, secret, or uuid.',
|
||||
);
|
||||
trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
circuitType: get().circuitType,
|
||||
error: get().error_code ?? 'unknown',
|
||||
});
|
||||
@@ -915,27 +958,27 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
registerDeviceToken,
|
||||
} = require('@/utils/notifications/notificationService');
|
||||
const isMockPassport = passportData?.mock;
|
||||
trackEvent(ProofEvents.DEVICE_TOKEN_REG_STARTED);
|
||||
selfClient.trackEvent(ProofEvents.DEVICE_TOKEN_REG_STARTED);
|
||||
await registerDeviceToken(uuid, fcmToken, isMockPassport);
|
||||
trackEvent(ProofEvents.DEVICE_TOKEN_REG_SUCCESS);
|
||||
selfClient.trackEvent(ProofEvents.DEVICE_TOKEN_REG_SUCCESS);
|
||||
} catch (error) {
|
||||
console.error('Error registering device token:', error);
|
||||
trackEvent(ProofEvents.DEVICE_TOKEN_REG_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.DEVICE_TOKEN_REG_FAILED, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
// Continue with the proving process even if token registration fails
|
||||
}
|
||||
}
|
||||
|
||||
trackEvent(ProofEvents.PAYLOAD_GEN_STARTED);
|
||||
const submitBody = await get()._generatePayload();
|
||||
selfClient.trackEvent(ProofEvents.PAYLOAD_GEN_STARTED);
|
||||
const submitBody = await get()._generatePayload(selfClient);
|
||||
wsConnection.send(JSON.stringify(submitBody));
|
||||
trackEvent(ProofEvents.PAYLOAD_SENT);
|
||||
trackEvent(ProofEvents.PROVING_PROCESS_STARTED);
|
||||
selfClient.trackEvent(ProofEvents.PAYLOAD_SENT);
|
||||
selfClient.trackEvent(ProofEvents.PROVING_PROCESS_STARTED);
|
||||
actor!.send({ type: 'START_PROVING' });
|
||||
} catch (error) {
|
||||
console.error('Error during startProving preparation/send:', error);
|
||||
trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
selfClient.trackEvent(ProofEvents.PROOF_FAILED, {
|
||||
circuitType: get().circuitType,
|
||||
error: get().error_code ?? 'unknown',
|
||||
});
|
||||
@@ -943,43 +986,43 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
}
|
||||
},
|
||||
|
||||
setUserConfirmed: () => {
|
||||
setUserConfirmed: (selfClient: SelfClient) => {
|
||||
set({ userConfirmed: true });
|
||||
trackEvent(ProofEvents.USER_CONFIRMED);
|
||||
selfClient.trackEvent(ProofEvents.USER_CONFIRMED);
|
||||
if (get().currentState === 'ready_to_prove') {
|
||||
get().startProving();
|
||||
get().startProving(selfClient);
|
||||
}
|
||||
},
|
||||
|
||||
postProving: (selfClient: SelfClient) => {
|
||||
_checkActorInitialized(actor);
|
||||
const { circuitType } = get();
|
||||
trackEvent(ProofEvents.POST_PROVING_STARTED);
|
||||
selfClient.trackEvent(ProofEvents.POST_PROVING_STARTED);
|
||||
if (circuitType === 'dsc') {
|
||||
setTimeout(() => {
|
||||
trackEvent(ProofEvents.POST_PROVING_CHAIN_STEP, {
|
||||
selfClient.trackEvent(ProofEvents.POST_PROVING_CHAIN_STEP, {
|
||||
from: 'dsc',
|
||||
to: 'register',
|
||||
});
|
||||
get().init(selfClient, 'register', true);
|
||||
}, 1500);
|
||||
} else if (circuitType === 'register') {
|
||||
trackEvent(ProofEvents.POST_PROVING_COMPLETED);
|
||||
selfClient.trackEvent(ProofEvents.POST_PROVING_COMPLETED);
|
||||
actor!.send({ type: 'COMPLETED' });
|
||||
} else if (circuitType === 'disclose') {
|
||||
trackEvent(ProofEvents.POST_PROVING_COMPLETED);
|
||||
selfClient.trackEvent(ProofEvents.POST_PROVING_COMPLETED);
|
||||
actor!.send({ type: 'COMPLETED' });
|
||||
}
|
||||
},
|
||||
|
||||
_closeConnections: () => {
|
||||
const ws = get().wsConnection;
|
||||
if (ws) {
|
||||
_closeConnections: (selfClient: SelfClient) => {
|
||||
const { wsConnection: ws, wsHandlers } = get();
|
||||
if (ws && wsHandlers) {
|
||||
try {
|
||||
ws.removeEventListener('message', get()._handleWebSocketMessage);
|
||||
ws.removeEventListener('open', get()._handleWsOpen);
|
||||
ws.removeEventListener('error', get()._handleWsError);
|
||||
ws.removeEventListener('close', get()._handleWsClose);
|
||||
ws.removeEventListener('message', wsHandlers.message);
|
||||
ws.removeEventListener('open', wsHandlers.open);
|
||||
ws.removeEventListener('error', wsHandlers.error);
|
||||
ws.removeEventListener('close', wsHandlers.close);
|
||||
ws.close();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@@ -987,7 +1030,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
error,
|
||||
);
|
||||
}
|
||||
set({ wsConnection: null });
|
||||
set({ wsConnection: null, wsHandlers: null });
|
||||
}
|
||||
|
||||
const socket = get().socketConnection;
|
||||
@@ -1004,7 +1047,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
});
|
||||
},
|
||||
|
||||
_generatePayload: async () => {
|
||||
_generatePayload: async (selfClient: SelfClient) => {
|
||||
const { circuitType, passportData, secret, uuid, sharedKey, env } = get();
|
||||
if (!passportData) {
|
||||
throw new Error('PassportData is not available');
|
||||
@@ -1082,8 +1125,8 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
forgeKey,
|
||||
);
|
||||
|
||||
trackEvent(ProofEvents.PAYLOAD_GEN_COMPLETED);
|
||||
trackEvent(ProofEvents.PAYLOAD_ENCRYPTED);
|
||||
selfClient.trackEvent(ProofEvents.PAYLOAD_GEN_COMPLETED);
|
||||
selfClient.trackEvent(ProofEvents.PAYLOAD_ENCRYPTED);
|
||||
|
||||
// Persist endpointType for later Socket.IO connection
|
||||
set({ endpointType: endpointType as EndpointType });
|
||||
|
||||
@@ -13,7 +13,7 @@ jest.mock('@/navigation', () => ({
|
||||
}));
|
||||
|
||||
const mockSelfAppStore = { useSelfAppStore: { getState: jest.fn() } };
|
||||
jest.mock('@/stores/selfAppStore', () => mockSelfAppStore);
|
||||
jest.mock('@selfxyz/mobile-sdk-alpha/stores', () => mockSelfAppStore);
|
||||
|
||||
const mockUserStore = { default: { getState: jest.fn() } };
|
||||
jest.mock('@/stores/userStore', () => ({
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
import { useProtocolStore } from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
import type { SelfClient } from '@selfxyz/mobile-sdk-alpha';
|
||||
import {
|
||||
useProtocolStore,
|
||||
useSelfAppStore,
|
||||
} from '@selfxyz/mobile-sdk-alpha/stores';
|
||||
|
||||
import { useSelfAppStore } from '@/stores/selfAppStore';
|
||||
import { useProvingStore } from '@/utils/proving/provingMachine';
|
||||
|
||||
jest.mock('xstate', () => {
|
||||
@@ -17,32 +20,6 @@ jest.mock('@/utils/analytics', () => () => ({
|
||||
trackEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock the proving inputs to return predictable data
|
||||
jest.mock('@/utils/proving/provingInputs', () => ({
|
||||
generateTEEInputsDisclose: jest.fn(() => ({
|
||||
inputs: { s: 1 },
|
||||
circuitName: 'vc_and_disclose',
|
||||
endpointType: 'https',
|
||||
endpoint: 'https://dis',
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock the common register/dsc inputs where provingMachine actually imports from
|
||||
jest.mock('@selfxyz/common/utils/circuits/registerInputs', () => ({
|
||||
generateTEEInputsRegister: jest.fn(() => ({
|
||||
inputs: { r: 1 },
|
||||
circuitName: 'reg',
|
||||
endpointType: 'celo',
|
||||
endpoint: 'https://reg',
|
||||
})),
|
||||
generateTEEInputsDSC: jest.fn(() => ({
|
||||
inputs: { d: 1 },
|
||||
circuitName: 'dsc',
|
||||
endpointType: 'celo',
|
||||
endpoint: 'https://dsc',
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock the proving utils
|
||||
jest.mock('@selfxyz/common/utils/proving', () => {
|
||||
const actual = jest.requireActual('@selfxyz/common/utils/proving') as any;
|
||||
@@ -54,6 +31,52 @@ jest.mock('@selfxyz/common/utils/proving', () => {
|
||||
cipher_text: [1],
|
||||
auth_tag: [2],
|
||||
})),
|
||||
generateTEEInputsRegister: jest.fn(() => ({
|
||||
inputs: { r: 1 },
|
||||
circuitName: 'reg',
|
||||
endpointType: 'celo',
|
||||
endpoint: 'https://reg',
|
||||
})),
|
||||
generateTEEInputsDSC: jest.fn(() => ({
|
||||
inputs: { d: 1 },
|
||||
circuitName: 'dsc',
|
||||
endpointType: 'celo',
|
||||
endpoint: 'https://dsc',
|
||||
})),
|
||||
generateTEEInputsDisclose: jest.fn(() => ({
|
||||
inputs: { s: 1 },
|
||||
circuitName: 'vc_and_disclose',
|
||||
endpointType: 'https',
|
||||
endpoint: 'https://dis',
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock the proving utils
|
||||
jest.mock('@selfxyz/common/utils/circuits/registerInputs', () => {
|
||||
const actual = jest.requireActual(
|
||||
'@selfxyz/common/utils/circuits/registerInputs',
|
||||
) as any;
|
||||
return {
|
||||
...actual,
|
||||
generateTEEInputsRegister: jest.fn(() => ({
|
||||
inputs: { r: 1 },
|
||||
circuitName: 'reg',
|
||||
endpointType: 'celo',
|
||||
endpoint: 'https://reg',
|
||||
})),
|
||||
generateTEEInputsDSC: jest.fn(() => ({
|
||||
inputs: { d: 1 },
|
||||
circuitName: 'dsc',
|
||||
endpointType: 'celo',
|
||||
endpoint: 'https://dsc',
|
||||
})),
|
||||
generateTEEInputsDiscloseStateless: jest.fn(() => ({
|
||||
inputs: { s: 1 },
|
||||
circuitName: 'vc_and_disclose',
|
||||
endpointType: 'https',
|
||||
endpoint: 'https://dis',
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -89,13 +112,17 @@ const {
|
||||
getPayload,
|
||||
encryptAES256GCM,
|
||||
} = require('@selfxyz/common/utils/proving');
|
||||
const { generateTEEInputsDisclose } = require('@/utils/proving/provingInputs');
|
||||
|
||||
const {
|
||||
generateTEEInputsRegister,
|
||||
generateTEEInputsDSC,
|
||||
generateTEEInputsDiscloseStateless,
|
||||
} = require('@selfxyz/common/utils/circuits/registerInputs');
|
||||
|
||||
describe('_generatePayload', () => {
|
||||
const selfClient: SelfClient = {
|
||||
trackEvent: jest.fn(),
|
||||
} as unknown as SelfClient;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
useProvingStore.setState({
|
||||
@@ -203,7 +230,9 @@ describe('_generatePayload', () => {
|
||||
|
||||
it('register circuit', async () => {
|
||||
useProvingStore.setState({ circuitType: 'register' });
|
||||
const payload = await useProvingStore.getState()._generatePayload();
|
||||
const payload = await useProvingStore
|
||||
.getState()
|
||||
._generatePayload(selfClient);
|
||||
expect(generateTEEInputsRegister).toHaveBeenCalled();
|
||||
expect(getPayload).toHaveBeenCalled();
|
||||
expect(encryptAES256GCM).toHaveBeenCalled();
|
||||
@@ -218,7 +247,9 @@ describe('_generatePayload', () => {
|
||||
|
||||
it('dsc circuit', async () => {
|
||||
useProvingStore.setState({ circuitType: 'dsc' });
|
||||
const payload = await useProvingStore.getState()._generatePayload();
|
||||
const payload = await useProvingStore
|
||||
.getState()
|
||||
._generatePayload(selfClient);
|
||||
expect(generateTEEInputsDSC).toHaveBeenCalled();
|
||||
expect(useProvingStore.getState().endpointType).toBe('celo');
|
||||
expect(payload.params.uuid).toBe('123');
|
||||
@@ -226,8 +257,10 @@ describe('_generatePayload', () => {
|
||||
|
||||
it('disclose circuit', async () => {
|
||||
useProvingStore.setState({ circuitType: 'disclose' });
|
||||
const payload = await useProvingStore.getState()._generatePayload();
|
||||
expect(generateTEEInputsDisclose).toHaveBeenCalled();
|
||||
const payload = await useProvingStore
|
||||
.getState()
|
||||
._generatePayload(selfClient);
|
||||
expect(generateTEEInputsDiscloseStateless).toHaveBeenCalled();
|
||||
expect(useProvingStore.getState().endpointType).toBe('https');
|
||||
expect(payload.params.uuid).toBe('123');
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ jest.mock('@selfxyz/mobile-sdk-alpha', () => {
|
||||
});
|
||||
|
||||
describe('startFetchingData', () => {
|
||||
let mockSelfClient: SelfClient;
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
const {
|
||||
@@ -71,8 +72,9 @@ describe('startFetchingData', () => {
|
||||
unsafe_getPrivateKey.mockResolvedValue('secret');
|
||||
|
||||
// Create mock selfClient
|
||||
const mockSelfClient = {
|
||||
mockSelfClient = {
|
||||
getPrivateKey: jest.fn().mockResolvedValue('mock-secret'),
|
||||
trackEvent: jest.fn(),
|
||||
} as unknown as SelfClient;
|
||||
|
||||
useProtocolStore.setState({
|
||||
@@ -91,7 +93,7 @@ describe('startFetchingData', () => {
|
||||
});
|
||||
|
||||
it('emits FETCH_ERROR when dsc_parsed is missing', async () => {
|
||||
await useProvingStore.getState().startFetchingData();
|
||||
await useProvingStore.getState().startFetchingData(mockSelfClient);
|
||||
|
||||
expect(
|
||||
useProtocolStore.getState().passport.fetch_all,
|
||||
|
||||
@@ -36,8 +36,8 @@ describe('provingMachine registration completion', () => {
|
||||
useProvingStore(state => state.init),
|
||||
);
|
||||
const emitMock = jest.fn();
|
||||
|
||||
const selfClient = {
|
||||
trackEvent: jest.fn(),
|
||||
emit: emitMock,
|
||||
} as unknown as SelfClient;
|
||||
|
||||
|
||||
@@ -166,7 +166,6 @@ export default defineConfig({
|
||||
],
|
||||
'screens-prove-utils': [
|
||||
'./src/utils/proving/index.ts',
|
||||
'./src/utils/proving/provingInputs.ts',
|
||||
'./src/utils/proving/loadingScreenStateText.ts',
|
||||
],
|
||||
|
||||
|
||||
@@ -55,10 +55,14 @@ export {
|
||||
SelfAppBuilder,
|
||||
bigIntToString,
|
||||
brutforceSignatureAlgorithmDsc,
|
||||
buildSMT,
|
||||
calculateUserIdentifierHash,
|
||||
findStartPubKeyIndex,
|
||||
formatEndpoint,
|
||||
formatMrz,
|
||||
genAndInitMockPassportData,
|
||||
genMockIdDoc,
|
||||
genMockIdDocAndInitDataParsing,
|
||||
generateCircuitInputsDSC,
|
||||
generateCircuitInputsRegister,
|
||||
generateCircuitInputsVCandDisclose,
|
||||
@@ -69,28 +73,17 @@ export {
|
||||
getLeafCscaTree,
|
||||
getLeafDscTree,
|
||||
getSKIPEM,
|
||||
getSolidityPackedUserContextData,
|
||||
getUniversalLink,
|
||||
hashEndpointWithScope,
|
||||
initElliptic,
|
||||
initPassportDataParsing,
|
||||
parseCertificateSimple,
|
||||
parseDscCertificateData,
|
||||
genMockIdDoc,
|
||||
genMockIdDocAndInitDataParsing,
|
||||
buildSMT,
|
||||
calculateUserIdentifierHash,
|
||||
getSolidityPackedUserContextData,
|
||||
stringToBigInt,
|
||||
} from './src/utils/index.js';
|
||||
|
||||
export {
|
||||
prepareAadhaarRegisterTestData,
|
||||
prepareAadhaarDiscloseTestData,
|
||||
prepareAadhaarRegisterData,
|
||||
} from './src/utils/aadhaar/mockData.js';
|
||||
export { generateTestData, testCustomData } from './src/utils/aadhaar/utils.js';
|
||||
export { createSelector } from './src/utils/aadhaar/constants.js';
|
||||
|
||||
// Hash utilities
|
||||
export {
|
||||
customHasher,
|
||||
@@ -99,3 +92,11 @@ export {
|
||||
hash,
|
||||
packBytesAndPoseidon,
|
||||
} from './src/utils/hash.js';
|
||||
|
||||
export { generateTestData, testCustomData } from './src/utils/aadhaar/utils.js';
|
||||
|
||||
export {
|
||||
prepareAadhaarDiscloseTestData,
|
||||
prepareAadhaarRegisterData,
|
||||
prepareAadhaarRegisterTestData,
|
||||
} from './src/utils/aadhaar/mockData.js';
|
||||
|
||||
@@ -647,8 +647,8 @@
|
||||
"build:watch": "tsup --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check .",
|
||||
"lint:imports": "eslint . --fix",
|
||||
"lint:imports:check": "eslint .",
|
||||
"lint:imports": "yarn eslint --fix .",
|
||||
"lint:imports:check": "yarn eslint .",
|
||||
"nice": "yarn format && yarn lint:imports",
|
||||
"nice:check": "yarn lint && yarn lint:imports:check",
|
||||
"prepublishOnly": "yarn build",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export type Country3LetterCode = keyof typeof countryCodes;
|
||||
export type document_type = 'passport' | 'id_card';
|
||||
export type hashAlgosTypes = 'sha512' | 'sha384' | 'sha256' | 'sha224' | 'sha1';
|
||||
export const AADHAAR_ATTESTATION_ID = '3';
|
||||
export const API_URL = 'https://api.self.xyz';
|
||||
|
||||
export const API_URL_STAGING = 'https://api.staging.self.xyz';
|
||||
|
||||
export const CHAIN_NAME = 'celo';
|
||||
@@ -42,8 +44,6 @@ export const CSCA_TREE_URL_STAGING = 'https://tree.staging.self.xyz/csca';
|
||||
|
||||
export const CSCA_TREE_URL_STAGING_ID_CARD = 'https://tree.staging.self.xyz/csca-id';
|
||||
|
||||
export const AADHAAR_ATTESTATION_ID = '3';
|
||||
|
||||
// we make it global here because passing it to generateCircuitInputsRegister caused trouble
|
||||
export const DEFAULT_MAJORITY = '18';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { buildAadhaarSMT } from '../trees.js';
|
||||
|
||||
async function build_aadhaar_ofac_smt() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable sort-exports/sort-exports */
|
||||
export const MAX_FIELD_BYTE_SIZE = 31;
|
||||
export const NAME_MAX_LENGTH = 2 * MAX_FIELD_BYTE_SIZE; // 62 bytes
|
||||
export const TOTAL_REVEAL_DATA_LENGTH = 119;
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import { calculateAge, generateTestData, testCustomData } from './utils.js';
|
||||
import {
|
||||
convertBigIntToByteArray,
|
||||
decompressByteArray,
|
||||
splitToWords,
|
||||
extractPhoto,
|
||||
} from '@anon-aadhaar/core';
|
||||
import { bufferToHex, Uint8ArrayToCharArray } from '@zk-email/helpers/dist/binary-format.js';
|
||||
import { sha256Pad } from '@zk-email/helpers/dist/sha-utils.js';
|
||||
import { testQRData } from './assets/dataInput.js';
|
||||
import { stringToAsciiArray } from './utils.js';
|
||||
import { packBytesAndPoseidon } from '../hash.js';
|
||||
import { poseidon5 } from 'poseidon-lite';
|
||||
import forge from 'node-forge';
|
||||
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
import { SMT } from '@openpassport/zk-kit-smt';
|
||||
import { poseidon5 } from 'poseidon-lite';
|
||||
|
||||
import { COMMITMENT_TREE_DEPTH } from '../../constants/constants.js';
|
||||
import { findIndexInTree, formatInput } from '../circuits/generateInputs.js';
|
||||
import { packBytesAndPoseidon } from '../hash.js';
|
||||
import {
|
||||
generateMerkleProof,
|
||||
generateSMTProof,
|
||||
getNameDobLeafAadhaar,
|
||||
getNameYobLeafAahaar,
|
||||
} from '../trees.js';
|
||||
import { testQRData } from './assets/dataInput.js';
|
||||
import {
|
||||
calculateAge,
|
||||
extractQRDataFields,
|
||||
generateTestData,
|
||||
stringToAsciiArray,
|
||||
testCustomData,
|
||||
} from './utils.js';
|
||||
|
||||
import { COMMITMENT_TREE_DEPTH } from '../../constants/constants.js';
|
||||
import { extractQRDataFields } from './utils.js';
|
||||
import {
|
||||
convertBigIntToByteArray,
|
||||
decompressByteArray,
|
||||
extractPhoto,
|
||||
splitToWords,
|
||||
} from '@anon-aadhaar/core';
|
||||
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
import { SMT } from '@openpassport/zk-kit-smt';
|
||||
import { bufferToHex, Uint8ArrayToCharArray } from '@zk-email/helpers/dist/binary-format.js';
|
||||
import { sha256Pad } from '@zk-email/helpers/dist/sha-utils.js';
|
||||
|
||||
// Helper function to compute padded name
|
||||
function computePaddedName(name: string): number[] {
|
||||
@@ -163,166 +168,6 @@ function processQRDataSimple(qrData: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareAadhaarRegisterTestData(
|
||||
privKeyPem: string,
|
||||
pubkeyPem: string,
|
||||
secret: string,
|
||||
name?: string,
|
||||
dateOfBirth?: string,
|
||||
gender?: string,
|
||||
pincode?: string,
|
||||
state?: string,
|
||||
timestamp?: string
|
||||
) {
|
||||
const sharedData = processQRData(
|
||||
privKeyPem,
|
||||
name,
|
||||
dateOfBirth,
|
||||
gender,
|
||||
pincode,
|
||||
state,
|
||||
timestamp
|
||||
);
|
||||
|
||||
const delimiterIndices: number[] = [];
|
||||
for (let i = 0; i < sharedData.qrDataPadded.length; i++) {
|
||||
if (sharedData.qrDataPadded[i] === 255) {
|
||||
delimiterIndices.push(i);
|
||||
}
|
||||
if (delimiterIndices.length === 18) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let photoEOI = 0;
|
||||
for (let i = delimiterIndices[17]; i < sharedData.qrDataPadded.length - 1; i++) {
|
||||
if (sharedData.qrDataPadded[i + 1] === 217 && sharedData.qrDataPadded[i] === 255) {
|
||||
photoEOI = i + 1;
|
||||
}
|
||||
}
|
||||
if (photoEOI === 0) {
|
||||
throw new Error('Photo EOI not found');
|
||||
}
|
||||
|
||||
const signatureBytes = sharedData.decodedData.slice(
|
||||
sharedData.decodedData.length - 256,
|
||||
sharedData.decodedData.length
|
||||
);
|
||||
const signature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
|
||||
|
||||
const publicKey = forge.pki.publicKeyFromPem(pubkeyPem);
|
||||
|
||||
const modulusHex = publicKey.n.toString(16);
|
||||
const pubKey = BigInt('0x' + modulusHex);
|
||||
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
const commitment = computeCommitment(
|
||||
BigInt(secret),
|
||||
BigInt(sharedData.qrHash),
|
||||
nullifier,
|
||||
packedCommitment,
|
||||
BigInt(sharedData.photoHash)
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
qrDataPadded: Uint8ArrayToCharArray(sharedData.qrDataPadded),
|
||||
qrDataPaddedLength: sharedData.qrDataPaddedLen,
|
||||
delimiterIndices: delimiterIndices,
|
||||
signature: splitToWords(signature, BigInt(121), BigInt(17)),
|
||||
pubKey: splitToWords(pubKey, BigInt(121), BigInt(17)),
|
||||
secret: secret,
|
||||
photoEOI: photoEOI,
|
||||
};
|
||||
|
||||
return {
|
||||
inputs,
|
||||
nullifier,
|
||||
commitment,
|
||||
};
|
||||
}
|
||||
|
||||
export async function prepareAadhaarRegisterData(qrData: string, secret: string, certs: string[]) {
|
||||
const sharedData = processQRDataSimple(qrData);
|
||||
const delimiterIndices: number[] = [];
|
||||
for (let i = 0; i < sharedData.qrDataPadded.length; i++) {
|
||||
if (sharedData.qrDataPadded[i] === 255) {
|
||||
delimiterIndices.push(i);
|
||||
}
|
||||
if (delimiterIndices.length === 18) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let photoEOI = 0;
|
||||
for (let i = delimiterIndices[17]; i < sharedData.qrDataPadded.length - 1; i++) {
|
||||
if (sharedData.qrDataPadded[i + 1] === 217 && sharedData.qrDataPadded[i] === 255) {
|
||||
photoEOI = i + 1;
|
||||
}
|
||||
}
|
||||
if (photoEOI === 0) {
|
||||
throw new Error('Photo EOI not found');
|
||||
}
|
||||
|
||||
const signatureBytes = sharedData.decodedData.slice(
|
||||
sharedData.decodedData.length - 256,
|
||||
sharedData.decodedData.length
|
||||
);
|
||||
const signature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
|
||||
|
||||
//do promise.all for all certs and pick the one that is valid
|
||||
const certificates = await Promise.all(
|
||||
certs.map(async (cert) => {
|
||||
const certificate = forge.pki.certificateFromPem(cert);
|
||||
const publicKey = certificate.publicKey as forge.pki.rsa.PublicKey;
|
||||
|
||||
try {
|
||||
const md = forge.md.sha256.create();
|
||||
md.update(forge.util.binary.raw.encode(sharedData.signedData));
|
||||
|
||||
const isValid = publicKey.verify(md.digest().getBytes(), signatureBytes);
|
||||
return isValid;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
//find the valid cert
|
||||
const validCert = certificates.indexOf(true);
|
||||
if (validCert === -1) {
|
||||
throw new Error('No valid certificate found');
|
||||
}
|
||||
const certPem = certs[validCert];
|
||||
const cert = forge.pki.certificateFromPem(certPem);
|
||||
const modulusHex = (cert.publicKey as forge.pki.rsa.PublicKey).n.toString(16);
|
||||
const pubKey = BigInt('0x' + modulusHex);
|
||||
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
const commitment = computeCommitment(
|
||||
BigInt(secret),
|
||||
BigInt(sharedData.qrHash),
|
||||
nullifier,
|
||||
packedCommitment,
|
||||
BigInt(sharedData.photoHash)
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
qrDataPadded: Uint8ArrayToCharArray(sharedData.qrDataPadded),
|
||||
qrDataPaddedLength: sharedData.qrDataPaddedLen,
|
||||
delimiterIndices: delimiterIndices,
|
||||
signature: splitToWords(signature, BigInt(121), BigInt(17)),
|
||||
pubKey: splitToWords(pubKey, BigInt(121), BigInt(17)),
|
||||
secret: secret,
|
||||
photoEOI: photoEOI,
|
||||
};
|
||||
|
||||
return {
|
||||
inputs,
|
||||
nullifier,
|
||||
commitment,
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareAadhaarDiscloseTestData(
|
||||
privateKeyPem: string,
|
||||
merkletree: LeanIMT,
|
||||
@@ -444,3 +289,163 @@ export function prepareAadhaarDiscloseTestData(
|
||||
commitment,
|
||||
};
|
||||
}
|
||||
|
||||
export async function prepareAadhaarRegisterData(qrData: string, secret: string, certs: string[]) {
|
||||
const sharedData = processQRDataSimple(qrData);
|
||||
const delimiterIndices: number[] = [];
|
||||
for (let i = 0; i < sharedData.qrDataPadded.length; i++) {
|
||||
if (sharedData.qrDataPadded[i] === 255) {
|
||||
delimiterIndices.push(i);
|
||||
}
|
||||
if (delimiterIndices.length === 18) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let photoEOI = 0;
|
||||
for (let i = delimiterIndices[17]; i < sharedData.qrDataPadded.length - 1; i++) {
|
||||
if (sharedData.qrDataPadded[i + 1] === 217 && sharedData.qrDataPadded[i] === 255) {
|
||||
photoEOI = i + 1;
|
||||
}
|
||||
}
|
||||
if (photoEOI === 0) {
|
||||
throw new Error('Photo EOI not found');
|
||||
}
|
||||
|
||||
const signatureBytes = sharedData.decodedData.slice(
|
||||
sharedData.decodedData.length - 256,
|
||||
sharedData.decodedData.length
|
||||
);
|
||||
const signature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
|
||||
|
||||
//do promise.all for all certs and pick the one that is valid
|
||||
const certificates = await Promise.all(
|
||||
certs.map(async (cert) => {
|
||||
const certificate = forge.pki.certificateFromPem(cert);
|
||||
const publicKey = certificate.publicKey as forge.pki.rsa.PublicKey;
|
||||
|
||||
try {
|
||||
const md = forge.md.sha256.create();
|
||||
md.update(forge.util.binary.raw.encode(sharedData.signedData));
|
||||
|
||||
const isValid = publicKey.verify(md.digest().getBytes(), signatureBytes);
|
||||
return isValid;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
//find the valid cert
|
||||
const validCert = certificates.indexOf(true);
|
||||
if (validCert === -1) {
|
||||
throw new Error('No valid certificate found');
|
||||
}
|
||||
const certPem = certs[validCert];
|
||||
const cert = forge.pki.certificateFromPem(certPem);
|
||||
const modulusHex = (cert.publicKey as forge.pki.rsa.PublicKey).n.toString(16);
|
||||
const pubKey = BigInt('0x' + modulusHex);
|
||||
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
const commitment = computeCommitment(
|
||||
BigInt(secret),
|
||||
BigInt(sharedData.qrHash),
|
||||
nullifier,
|
||||
packedCommitment,
|
||||
BigInt(sharedData.photoHash)
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
qrDataPadded: Uint8ArrayToCharArray(sharedData.qrDataPadded),
|
||||
qrDataPaddedLength: sharedData.qrDataPaddedLen,
|
||||
delimiterIndices: delimiterIndices,
|
||||
signature: splitToWords(signature, BigInt(121), BigInt(17)),
|
||||
pubKey: splitToWords(pubKey, BigInt(121), BigInt(17)),
|
||||
secret: secret,
|
||||
photoEOI: photoEOI,
|
||||
};
|
||||
|
||||
return {
|
||||
inputs,
|
||||
nullifier,
|
||||
commitment,
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareAadhaarRegisterTestData(
|
||||
privKeyPem: string,
|
||||
pubkeyPem: string,
|
||||
secret: string,
|
||||
name?: string,
|
||||
dateOfBirth?: string,
|
||||
gender?: string,
|
||||
pincode?: string,
|
||||
state?: string,
|
||||
timestamp?: string
|
||||
) {
|
||||
const sharedData = processQRData(
|
||||
privKeyPem,
|
||||
name,
|
||||
dateOfBirth,
|
||||
gender,
|
||||
pincode,
|
||||
state,
|
||||
timestamp
|
||||
);
|
||||
|
||||
const delimiterIndices: number[] = [];
|
||||
for (let i = 0; i < sharedData.qrDataPadded.length; i++) {
|
||||
if (sharedData.qrDataPadded[i] === 255) {
|
||||
delimiterIndices.push(i);
|
||||
}
|
||||
if (delimiterIndices.length === 18) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let photoEOI = 0;
|
||||
for (let i = delimiterIndices[17]; i < sharedData.qrDataPadded.length - 1; i++) {
|
||||
if (sharedData.qrDataPadded[i + 1] === 217 && sharedData.qrDataPadded[i] === 255) {
|
||||
photoEOI = i + 1;
|
||||
}
|
||||
}
|
||||
if (photoEOI === 0) {
|
||||
throw new Error('Photo EOI not found');
|
||||
}
|
||||
|
||||
const signatureBytes = sharedData.decodedData.slice(
|
||||
sharedData.decodedData.length - 256,
|
||||
sharedData.decodedData.length
|
||||
);
|
||||
const signature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
|
||||
|
||||
const publicKey = forge.pki.publicKeyFromPem(pubkeyPem);
|
||||
|
||||
const modulusHex = publicKey.n.toString(16);
|
||||
const pubKey = BigInt('0x' + modulusHex);
|
||||
|
||||
const nullifier = nullifierHash(sharedData.extractedFields);
|
||||
const packedCommitment = computePackedCommitment(sharedData.extractedFields);
|
||||
const commitment = computeCommitment(
|
||||
BigInt(secret),
|
||||
BigInt(sharedData.qrHash),
|
||||
nullifier,
|
||||
packedCommitment,
|
||||
BigInt(sharedData.photoHash)
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
qrDataPadded: Uint8ArrayToCharArray(sharedData.qrDataPadded),
|
||||
qrDataPaddedLength: sharedData.qrDataPaddedLen,
|
||||
delimiterIndices: delimiterIndices,
|
||||
signature: splitToWords(signature, BigInt(121), BigInt(17)),
|
||||
pubKey: splitToWords(pubKey, BigInt(121), BigInt(17)),
|
||||
secret: secret,
|
||||
photoEOI: photoEOI,
|
||||
};
|
||||
|
||||
return {
|
||||
inputs,
|
||||
nullifier,
|
||||
commitment,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
import forge from 'node-forge';
|
||||
|
||||
import {
|
||||
convertBigIntToByteArray,
|
||||
decompressByteArray,
|
||||
returnFullId,
|
||||
extractPhoto,
|
||||
getEndIndex,
|
||||
getRandomBytes,
|
||||
IdFields,
|
||||
rawDataToCompressedQR,
|
||||
replaceBytesBetween,
|
||||
IdFields,
|
||||
extractPhoto,
|
||||
getRandomBytes,
|
||||
getEndIndex,
|
||||
returnFullId,
|
||||
} from '@anon-aadhaar/core';
|
||||
import forge from 'node-forge';
|
||||
|
||||
export function stringToAsciiArray(str: string) {
|
||||
return str.split('').map((char) => char.charCodeAt(0));
|
||||
export interface ExtractedQRData {
|
||||
name: string;
|
||||
yob: string;
|
||||
mob: string;
|
||||
dob: string;
|
||||
gender: string;
|
||||
pincode: string;
|
||||
state: string;
|
||||
aadhaarLast4Digits: string;
|
||||
phoneNoLast4Digits: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
// This is the official test data issued by the UIDAI
|
||||
// In this script we'll change the signed data to emulate the specs of the Aadhaar QR V2
|
||||
// and sign the data again with our own certificates.
|
||||
// data on https://uidai.gov.in/en/ecosystem/authentication-devices-documents/qr-code-reader.html
|
||||
// This data is copied from https://github.dev/anon-aadhaar/anon-aadhaar/blob/main/packages/circuits/src/helpers/extractor.circom
|
||||
export const testCustomData =
|
||||
'2374971804270526477833002468783965837992554564899874087591661303561346432389832047870524302186901344489362368642972767716416349990805756094923115719687656090691368051627957878187788907419297818953295185555346288172578594637886352753543271000481717080003254556962148594350559820352806251787713278744047402230989238559317351232114240089849934148895256488140236015024800731753594740948640957680138566468247224859669467819596919398964809164399637893729212452791889199675715949918925838319591794702333094022248132120531152523331442741730158840977243402215102904932650832502847295644794421419704633765033761284508863534321317394686768650111457751139630853448637215423705157211510636160227953566227527799608082928846103264491539001327407775670834868948113753614112563650255058316849200536533335903554984254814901522086937767458409075617572843449110393213525925388131214952874629655799772119820372255291052673056372346072235458198199995637720424196884145247220163810790179386390283738429482893152518286247124911446073389185062482901364671389605727763080854673156754021728522287806275420847159574631844674460263574901590412679291518508010087116598357407343835408554094619585212373168435612645646129147973594416508676872819776522537778717985070402222824965034768103900739105784663244748432502180989441389718131079445941981681118258324511923246198334046020123727749408128519721102477302359413240175102907322619462289965085963377744024233678337951462006962521823224880199210318367946130004264196899778609815012001799773327514133268825910089483612283510244566484854597156100473055413090101948456959122378865704840756793122956663218517626099291311352417342899623681483097817511136427210593032393600010728324905512596767095096153856032112835755780472808814199620390836980020899858288860556611564167406292139646289142056168261133256777093245980048335918156712295254776487472431445495668303900536289283098315798552328294391152828182614909451410115516297083658174657554955228963550255866282688308751041517464999930825273776417639569977754844191402927594739069037851707477839207593911886893016618794870530622356073909077832279869798641545167528509966656120623184120128052588408742941658045827255866966100249857968956536613250770326334844204927432961924987891433020671754710428050564671868464658436926086493709176888821257183419013229795869757265111599482263223604228286513011751601176504567030118257385997460972803240338899836840030438830725520798480181575861397469056536579877274090338750406459700907704031830137890544492015701251066934352867527112361743047684237105216779177819594030160887368311805926405114938744235859610328064947158936962470654636736991567663705830950312548447653861922078087824048793236971354828540758657075837209006713701763902429652486225300535997260665898927924843608750347193892239342462507130025307878412116604096773706728162016134101751551184021079984480254041743057914746472840768175369369852937574401874295943063507273467384747124843744395375119899278823903202010381949145094804675442110869084589592876721655764753871572233276245590041302887094585204427900634246823674277680009401177473636685542700515621164233992970974893989913447733956146698563285998205950467321954304';
|
||||
export const FIELD_POSITIONS = {
|
||||
REFERENCE_ID: 2,
|
||||
NAME: 3,
|
||||
DOB: 4,
|
||||
GENDER: 5,
|
||||
PINCODE: 11,
|
||||
STATE: 13,
|
||||
PHONE_NO: 17,
|
||||
PHOTO: 18,
|
||||
} as const;
|
||||
|
||||
// Will sign the data with the keys generated for test
|
||||
const signNewTestData = (newSignedData: Uint8Array, privKeyPem: string) => {
|
||||
@@ -45,65 +58,33 @@ const signNewTestData = (newSignedData: Uint8Array, privKeyPem: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const generateTestData = ({
|
||||
privKeyPem,
|
||||
data,
|
||||
dob,
|
||||
gender,
|
||||
pincode,
|
||||
state,
|
||||
photo,
|
||||
name,
|
||||
timestamp,
|
||||
}: {
|
||||
privKeyPem: string;
|
||||
data: string;
|
||||
dob?: string;
|
||||
gender?: string;
|
||||
pincode?: string;
|
||||
state?: string;
|
||||
photo?: boolean;
|
||||
name?: string;
|
||||
timestamp?: string;
|
||||
}) => {
|
||||
const qrDataBytes = convertBigIntToByteArray(BigInt(data));
|
||||
const decodedData = decompressByteArray(qrDataBytes);
|
||||
export function calculateAge(
|
||||
dob: string,
|
||||
mob: string,
|
||||
yob: string
|
||||
): { age: number; currentYear: number; currentMonth: number; currentDay: number } {
|
||||
const currentDate = new Date();
|
||||
const currentYear = currentDate.getFullYear();
|
||||
const currentMonth = currentDate.getMonth() + 1; // getMonth() returns 0-11
|
||||
const currentDay = currentDate.getDate();
|
||||
|
||||
// Turning test data V1 into V2
|
||||
// Adding the version specifier prefix,
|
||||
// the last 4 digits of phone number and timestamp to now
|
||||
const dataToSign = createCustomV2TestData({
|
||||
signedData: decodedData.slice(0, decodedData.length - 256),
|
||||
dob,
|
||||
pincode,
|
||||
gender,
|
||||
state,
|
||||
photo,
|
||||
name,
|
||||
timestamp,
|
||||
});
|
||||
const birthYear = parseInt(yob);
|
||||
const birthMonth = parseInt(mob);
|
||||
const birthDay = parseInt(dob);
|
||||
|
||||
// Signing the newly generated testData
|
||||
const signature = signNewTestData(dataToSign, privKeyPem);
|
||||
let age = currentYear - birthYear;
|
||||
|
||||
// Reconstructing the whole QR data
|
||||
const tempData = Buffer.concat([dataToSign, signature]);
|
||||
|
||||
// Compressing the data to have it in the same format as the QR code
|
||||
const newCompressedData = rawDataToCompressedQR(tempData);
|
||||
const newQrData = {
|
||||
testQRData: newCompressedData.toString(),
|
||||
...returnFullId(dataToSign),
|
||||
if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) {
|
||||
age--;
|
||||
}
|
||||
return {
|
||||
age,
|
||||
currentYear,
|
||||
currentMonth,
|
||||
currentDay,
|
||||
};
|
||||
}
|
||||
|
||||
return newQrData;
|
||||
};
|
||||
|
||||
// This modify the test data to make it compliant with the secure Aadhaar QR V2 2022
|
||||
// - Adds the version specifier at the beginning 'V2'
|
||||
// - Mocks last 4 digits of phone number '1234' after VTC
|
||||
// - Refresh timestamp data to now
|
||||
// - Optionally it can take parameters to change the test data fields (dob, pinCode, gender, state)
|
||||
export const createCustomV2TestData = ({
|
||||
signedData,
|
||||
dob,
|
||||
@@ -238,99 +219,6 @@ export const createCustomV2TestData = ({
|
||||
return newData;
|
||||
};
|
||||
|
||||
export function calculateAge(
|
||||
dob: string,
|
||||
mob: string,
|
||||
yob: string
|
||||
): { age: number; currentYear: number; currentMonth: number; currentDay: number } {
|
||||
const currentDate = new Date();
|
||||
const currentYear = currentDate.getFullYear();
|
||||
const currentMonth = currentDate.getMonth() + 1; // getMonth() returns 0-11
|
||||
const currentDay = currentDate.getDate();
|
||||
|
||||
const birthYear = parseInt(yob);
|
||||
const birthMonth = parseInt(mob);
|
||||
const birthDay = parseInt(dob);
|
||||
|
||||
let age = currentYear - birthYear;
|
||||
|
||||
if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) {
|
||||
age--;
|
||||
}
|
||||
return {
|
||||
age,
|
||||
currentYear,
|
||||
currentMonth,
|
||||
currentDay,
|
||||
};
|
||||
}
|
||||
|
||||
export function returnNewDateString(timestamp?: string): string {
|
||||
const newDate = timestamp ? new Date(+timestamp) : new Date();
|
||||
|
||||
// Convert the UTC date to IST by adding 5 hours and 30 minutes
|
||||
const offsetHours = 5;
|
||||
const offsetMinutes = 30;
|
||||
newDate.setUTCHours(newDate.getUTCHours() + offsetHours);
|
||||
newDate.setUTCMinutes(newDate.getUTCMinutes() + offsetMinutes);
|
||||
|
||||
return (
|
||||
newDate.getUTCFullYear().toString() +
|
||||
(newDate.getUTCMonth() + 1).toString().padStart(2, '0') +
|
||||
newDate.getUTCDate().toString().padStart(2, '0') +
|
||||
newDate.getUTCHours().toString().padStart(2, '0') +
|
||||
newDate.getUTCMinutes().toString().padStart(2, '0') +
|
||||
newDate.getUTCSeconds().toString().padStart(2, '0') +
|
||||
newDate.getUTCMilliseconds().toString().padStart(3, '0')
|
||||
);
|
||||
}
|
||||
export const FIELD_POSITIONS = {
|
||||
REFERENCE_ID: 2,
|
||||
NAME: 3,
|
||||
DOB: 4,
|
||||
GENDER: 5,
|
||||
PINCODE: 11,
|
||||
STATE: 13,
|
||||
PHONE_NO: 17,
|
||||
PHOTO: 18,
|
||||
} as const;
|
||||
|
||||
function asciiArrayToString(asciiArray: number[]): string {
|
||||
return asciiArray
|
||||
.filter((byte) => byte !== 0)
|
||||
.map((byte) => String.fromCharCode(byte))
|
||||
.join('');
|
||||
}
|
||||
|
||||
function extractFieldData(
|
||||
data: Uint8Array,
|
||||
delimiterIndices: number[],
|
||||
position: number
|
||||
): number[] {
|
||||
const startIndex = delimiterIndices[position - 1] + 1;
|
||||
const endIndex = delimiterIndices[position];
|
||||
|
||||
const fieldData: number[] = [];
|
||||
for (let i = startIndex; i < endIndex; i++) {
|
||||
fieldData.push(data[i]);
|
||||
}
|
||||
|
||||
return fieldData;
|
||||
}
|
||||
|
||||
export interface ExtractedQRData {
|
||||
name: string;
|
||||
yob: string;
|
||||
mob: string;
|
||||
dob: string;
|
||||
gender: string;
|
||||
pincode: string;
|
||||
state: string;
|
||||
aadhaarLast4Digits: string;
|
||||
phoneNoLast4Digits: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export function extractQRDataFields(qrData: string | Uint8Array): ExtractedQRData {
|
||||
let qrDataBytes: Uint8Array;
|
||||
|
||||
@@ -428,3 +316,117 @@ export function extractQRDataFields(qrData: string | Uint8Array): ExtractedQRDat
|
||||
timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
export const generateTestData = ({
|
||||
privKeyPem,
|
||||
data,
|
||||
dob,
|
||||
gender,
|
||||
pincode,
|
||||
state,
|
||||
photo,
|
||||
name,
|
||||
timestamp,
|
||||
}: {
|
||||
privKeyPem: string;
|
||||
data: string;
|
||||
dob?: string;
|
||||
gender?: string;
|
||||
pincode?: string;
|
||||
state?: string;
|
||||
photo?: boolean;
|
||||
name?: string;
|
||||
timestamp?: string;
|
||||
}) => {
|
||||
const qrDataBytes = convertBigIntToByteArray(BigInt(data));
|
||||
const decodedData = decompressByteArray(qrDataBytes);
|
||||
|
||||
// Turning test data V1 into V2
|
||||
// Adding the version specifier prefix,
|
||||
// the last 4 digits of phone number and timestamp to now
|
||||
const dataToSign = createCustomV2TestData({
|
||||
signedData: decodedData.slice(0, decodedData.length - 256),
|
||||
dob,
|
||||
pincode,
|
||||
gender,
|
||||
state,
|
||||
photo,
|
||||
name,
|
||||
timestamp,
|
||||
});
|
||||
|
||||
// Signing the newly generated testData
|
||||
const signature = signNewTestData(dataToSign, privKeyPem);
|
||||
|
||||
// Reconstructing the whole QR data
|
||||
const tempData = Buffer.concat([dataToSign, signature]);
|
||||
|
||||
// Compressing the data to have it in the same format as the QR code
|
||||
const newCompressedData = rawDataToCompressedQR(tempData);
|
||||
const newQrData = {
|
||||
testQRData: newCompressedData.toString(),
|
||||
...returnFullId(dataToSign),
|
||||
};
|
||||
|
||||
return newQrData;
|
||||
};
|
||||
|
||||
export function returnNewDateString(timestamp?: string): string {
|
||||
const newDate = timestamp ? new Date(+timestamp) : new Date();
|
||||
|
||||
// Convert the UTC date to IST by adding 5 hours and 30 minutes
|
||||
const offsetHours = 5;
|
||||
const offsetMinutes = 30;
|
||||
newDate.setUTCHours(newDate.getUTCHours() + offsetHours);
|
||||
newDate.setUTCMinutes(newDate.getUTCMinutes() + offsetMinutes);
|
||||
|
||||
return (
|
||||
newDate.getUTCFullYear().toString() +
|
||||
(newDate.getUTCMonth() + 1).toString().padStart(2, '0') +
|
||||
newDate.getUTCDate().toString().padStart(2, '0') +
|
||||
newDate.getUTCHours().toString().padStart(2, '0') +
|
||||
newDate.getUTCMinutes().toString().padStart(2, '0') +
|
||||
newDate.getUTCSeconds().toString().padStart(2, '0') +
|
||||
newDate.getUTCMilliseconds().toString().padStart(3, '0')
|
||||
);
|
||||
}
|
||||
|
||||
function asciiArrayToString(asciiArray: number[]): string {
|
||||
return asciiArray
|
||||
.filter((byte) => byte !== 0)
|
||||
.map((byte) => String.fromCharCode(byte))
|
||||
.join('');
|
||||
}
|
||||
|
||||
function extractFieldData(
|
||||
data: Uint8Array,
|
||||
delimiterIndices: number[],
|
||||
position: number
|
||||
): number[] {
|
||||
const startIndex = delimiterIndices[position - 1] + 1;
|
||||
const endIndex = delimiterIndices[position];
|
||||
|
||||
const fieldData: number[] = [];
|
||||
for (let i = startIndex; i < endIndex; i++) {
|
||||
fieldData.push(data[i]);
|
||||
}
|
||||
|
||||
return fieldData;
|
||||
}
|
||||
|
||||
// This is the official test data issued by the UIDAI
|
||||
// In this script we'll change the signed data to emulate the specs of the Aadhaar QR V2
|
||||
// and sign the data again with our own certificates.
|
||||
// data on https://uidai.gov.in/en/ecosystem/authentication-devices-documents/qr-code-reader.html
|
||||
// This data is copied from https://github.dev/anon-aadhaar/anon-aadhaar/blob/main/packages/circuits/src/helpers/extractor.circom
|
||||
export function stringToAsciiArray(str: string) {
|
||||
return str.split('').map((char) => char.charCodeAt(0));
|
||||
}
|
||||
|
||||
// This modify the test data to make it compliant with the secure Aadhaar QR V2 2022
|
||||
// - Adds the version specifier at the beginning 'V2'
|
||||
// - Mocks last 4 digits of phone number '1234' after VTC
|
||||
// - Refresh timestamp data to now
|
||||
// - Optionally it can take parameters to change the test data fields (dob, pinCode, gender, state)
|
||||
export const testCustomData =
|
||||
'2374971804270526477833002468783965837992554564899874087591661303561346432389832047870524302186901344489362368642972767716416349990805756094923115719687656090691368051627957878187788907419297818953295185555346288172578594637886352753543271000481717080003254556962148594350559820352806251787713278744047402230989238559317351232114240089849934148895256488140236015024800731753594740948640957680138566468247224859669467819596919398964809164399637893729212452791889199675715949918925838319591794702333094022248132120531152523331442741730158840977243402215102904932650832502847295644794421419704633765033761284508863534321317394686768650111457751139630853448637215423705157211510636160227953566227527799608082928846103264491539001327407775670834868948113753614112563650255058316849200536533335903554984254814901522086937767458409075617572843449110393213525925388131214952874629655799772119820372255291052673056372346072235458198199995637720424196884145247220163810790179386390283738429482893152518286247124911446073389185062482901364671389605727763080854673156754021728522287806275420847159574631844674460263574901590412679291518508010087116598357407343835408554094619585212373168435612645646129147973594416508676872819776522537778717985070402222824965034768103900739105784663244748432502180989441389718131079445941981681118258324511923246198334046020123727749408128519721102477302359413240175102907322619462289965085963377744024233678337951462006962521823224880199210318367946130004264196899778609815012001799773327514133268825910089483612283510244566484854597156100473055413090101948456959122378865704840756793122956663218517626099291311352417342899623681483097817511136427210593032393600010728324905512596767095096153856032112835755780472808814199620390836980020899858288860556611564167406292139646289142056168261133256777093245980048335918156712295254776487472431445495668303900536289283098315798552328294391152828182614909451410115516297083658174657554955228963550255866282688308751041517464999930825273776417639569977754844191402927594739069037851707477839207593911886893016618794870530622356073909077832279869798641545167528509966656120623184120128052588408742941658045827255866966100249857968956536613250770326334844204927432961924987891433020671754710428050564671868464658436926086493709176888821257183419013229795869757265111599482263223604228286513011751601176504567030118257385997460972803240338899836840030438830725520798480181575861397469056536579877274090338750406459700907704031830137890544492015701251066934352867527112361743047684237105216779177819594030160887368311805926405114938744235859610328064947158936962470654636736991567663705830950312548447653861922078087824048793236971354828540758657075837209006713701763902429652486225300535997260665898927924843608750347193892239342462507130025307878412116604096773706728162016134101751551184021079984480254041743057914746472840768175369369852937574401874295943063507273467384747124843744395375119899278823903202010381949145094804675442110869084589592876721655764753871572233276245590041302887094585204427900634246823674277680009401177473636685542700515621164233992970974893989913447733956146698563285998205950467321954304';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { MAX_BYTES_IN_FIELD } from '../constants/constants.js';
|
||||
|
||||
export function bigIntToChunkedBytes(
|
||||
num: BigInt | bigint,
|
||||
num: bigint | bigint,
|
||||
bytesPerChunk: number,
|
||||
numChunks: number
|
||||
) {
|
||||
const res: string[] = [];
|
||||
const bigintNum: bigint = typeof num == 'bigint' ? num : num.valueOf();
|
||||
const bigintNum: bigint = typeof num == 'bigint' ? num : BigInt(num);
|
||||
const msk = (1n << BigInt(bytesPerChunk)) - 1n;
|
||||
for (let i = 0; i < numChunks; ++i) {
|
||||
res.push(((bigintNum >> BigInt(i * bytesPerChunk)) & msk).toString());
|
||||
|
||||
45
common/src/utils/hash.test.ts
Normal file
45
common/src/utils/hash.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { calculateUserIdentifierHash } from './hash';
|
||||
|
||||
describe('calculateUserIdentifierHash', () => {
|
||||
it('should return a bigint', () => {
|
||||
const result = calculateUserIdentifierHash(
|
||||
1,
|
||||
'550e8400-e29b-41d4-a716-446655440000',
|
||||
'some data'
|
||||
);
|
||||
expect(typeof result).toBe('bigint');
|
||||
});
|
||||
|
||||
it('should return the same hash for identical inputs', () => {
|
||||
const destChainID = 42;
|
||||
const userID = 'abcdef12-3456-7890-abcd-ef1234567890';
|
||||
const userDefinedData = 'Test data';
|
||||
const hash1 = calculateUserIdentifierHash(destChainID, userID, userDefinedData);
|
||||
const hash2 = calculateUserIdentifierHash(destChainID, userID, userDefinedData);
|
||||
expect(hash1).toBe(hash2);
|
||||
expect(hash1).toMatchInlineSnapshot(`525133570835708563534412370019423387022853755228n`);
|
||||
});
|
||||
|
||||
it('should return different hash for different inputs', () => {
|
||||
const hash1 = calculateUserIdentifierHash(
|
||||
42,
|
||||
'abcdef12-3456-7890-abcd-ef1234567890',
|
||||
'Test data'
|
||||
);
|
||||
const hash2 = calculateUserIdentifierHash(
|
||||
42,
|
||||
'abcdef12-3456-7890-abcd-ef1234567890',
|
||||
'Different data'
|
||||
);
|
||||
expect(hash1).not.toBe(hash2);
|
||||
expect(hash1).toMatchInlineSnapshot(`525133570835708563534412370019423387022853755228n`);
|
||||
});
|
||||
it('should handle user ids starting with 0x', () => {
|
||||
const hash1 = calculateUserIdentifierHash(42, '0xabcdef1234567890', 'Test data');
|
||||
const hash2 = calculateUserIdentifierHash(42, 'abcdef1234567890', 'Test data');
|
||||
expect(hash1).toBe(hash2);
|
||||
expect(hash1).toMatchInlineSnapshot(`830654111289877969679298811043657652615780822337n`);
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,7 @@ export function calculateUserIdentifierHash(
|
||||
destChainID: number,
|
||||
userID: string,
|
||||
userDefinedData: string
|
||||
): BigInt {
|
||||
): bigint {
|
||||
const solidityPackedUserContextData = getSolidityPackedUserContextData(
|
||||
destChainID,
|
||||
userID,
|
||||
@@ -133,7 +133,8 @@ export function getSolidityPackedUserContextData(
|
||||
['bytes32', 'bytes32', 'bytes'],
|
||||
[
|
||||
ethers.zeroPadValue(ethers.toBeHex(destChainID), 32),
|
||||
ethers.zeroPadValue('0x' + userIdHex, 32),
|
||||
|
||||
ethers.zeroPadValue(userIdHex.startsWith('0x') ? userIdHex : '0x' + userIdHex, 32),
|
||||
ethers.toUtf8Bytes(userDefinedData),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -2,14 +2,15 @@ import countries from 'i18n-iso-countries';
|
||||
// @ts-ignore
|
||||
import en from 'i18n-iso-countries/langs/en.json' with { type: 'json' };
|
||||
import {
|
||||
poseidon12,
|
||||
poseidon13,
|
||||
poseidon2,
|
||||
poseidon3,
|
||||
poseidon5,
|
||||
poseidon6,
|
||||
poseidon10,
|
||||
poseidon12,
|
||||
poseidon13,
|
||||
} from 'poseidon-lite';
|
||||
|
||||
import {
|
||||
CSCA_TREE_DEPTH,
|
||||
DSC_TREE_DEPTH,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
max_dsc_bytes,
|
||||
OFAC_TREE_LEVELS,
|
||||
} from '../constants/constants.js';
|
||||
import { packBytes } from './bytes.js';
|
||||
import type { CertificateData } from './certificate_parsing/dataStructure.js';
|
||||
import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple.js';
|
||||
import { stringToAsciiBigIntArray } from './circuits/uuid.js';
|
||||
@@ -26,7 +28,6 @@ import {
|
||||
DscCertificateMetaData,
|
||||
parseDscCertificateData,
|
||||
} from './passports/passport_parsing/parseDscCertificateData.js';
|
||||
import { packBytes } from './bytes.js';
|
||||
|
||||
import { IMT } from '@openpassport/zk-kit-imt';
|
||||
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
|
||||
@@ -36,6 +37,52 @@ import { SMT } from '@openpassport/zk-kit-smt';
|
||||
// SideEffect here
|
||||
countries.registerLocale(en);
|
||||
|
||||
//---------------------------
|
||||
// AADHAAR
|
||||
//---------------------------
|
||||
export function buildAadhaarSMT(field: any[], treetype: string): [number, number, SMT] {
|
||||
let count = 0;
|
||||
let startTime = performance.now();
|
||||
|
||||
const hash2 = (childNodes: ChildNodes) =>
|
||||
childNodes.length === 2 ? poseidon2(childNodes) : poseidon3(childNodes);
|
||||
const tree = new SMT(hash2, true);
|
||||
|
||||
for (let i = 0; i < field.length; i++) {
|
||||
const entry = field[i];
|
||||
|
||||
if (i !== 0) {
|
||||
console.log('Processing', treetype, 'number', i, 'out of', field.length);
|
||||
}
|
||||
|
||||
let leaf = BigInt(0);
|
||||
let reverse_leaf = BigInt(0);
|
||||
if (treetype == 'name_and_dob') {
|
||||
leaf = processNameAndDobAadhaar(entry, i);
|
||||
reverse_leaf = processNameAndDobAadhaar(entry, i, true);
|
||||
} else if (treetype == 'name_and_yob') {
|
||||
leaf = processNameAndYobAadhaar(entry, i);
|
||||
reverse_leaf = processNameAndYobAadhaar(entry, i, true);
|
||||
}
|
||||
|
||||
if (leaf == BigInt(0) || tree.createProof(leaf).membership) {
|
||||
console.log('This entry already exists in the tree, skipping...');
|
||||
continue;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
tree.add(leaf, BigInt(1));
|
||||
if (reverse_leaf == BigInt(0) || tree.createProof(reverse_leaf).membership) {
|
||||
console.log('This entry already exists in the tree, skipping...');
|
||||
continue;
|
||||
}
|
||||
tree.add(reverse_leaf, BigInt(1));
|
||||
count += 1;
|
||||
}
|
||||
|
||||
return [count, performance.now() - startTime, tree];
|
||||
}
|
||||
|
||||
// SMT trees for 3 levels of matching :
|
||||
// 1. Passport Number and Nationality tree : level 3 (Absolute Match)
|
||||
// 2. Name and date of birth combo tree : level 2 (High Probability Match)
|
||||
@@ -266,12 +313,6 @@ export function getLeafCscaTree(csca_parsed: CertificateData): string {
|
||||
return getLeaf(csca_parsed, 'csca');
|
||||
}
|
||||
|
||||
export function getLeafDscTree(dsc_parsed: CertificateData, csca_parsed: CertificateData): string {
|
||||
const dscLeaf = getLeaf(dsc_parsed, 'dsc');
|
||||
const cscaLeaf = getLeaf(csca_parsed, 'csca');
|
||||
return poseidon2([dscLeaf, cscaLeaf]).toString();
|
||||
}
|
||||
|
||||
function processPassportNoAndNationality(
|
||||
passno: string,
|
||||
nationality: string,
|
||||
@@ -480,6 +521,12 @@ function processCountry(country1: string, country2: string, i: number) {
|
||||
return leaf;
|
||||
}
|
||||
|
||||
export function getLeafDscTree(dsc_parsed: CertificateData, csca_parsed: CertificateData): string {
|
||||
const dscLeaf = getLeaf(dsc_parsed, 'dsc');
|
||||
const cscaLeaf = getLeaf(csca_parsed, 'csca');
|
||||
return poseidon2([dscLeaf, cscaLeaf]).toString();
|
||||
}
|
||||
|
||||
export function getLeafDscTreeFromDscCertificateMetadata(
|
||||
dscParsed: CertificateData,
|
||||
dscMetaData: DscCertificateMetaData
|
||||
@@ -501,6 +548,18 @@ export function getNameDobLeaf(
|
||||
return generateSmallKey(poseidon2([getDobLeaf(dobMrz), getNameLeaf(nameMrz)]));
|
||||
}
|
||||
|
||||
export const getNameDobLeafAadhaar = (name: string, year: string, month: string, day: string) => {
|
||||
const paddedName = name
|
||||
.toUpperCase()
|
||||
.padEnd(62, '\0')
|
||||
.split('')
|
||||
.map((char) => char.charCodeAt(0));
|
||||
const namePacked = packBytes(paddedName);
|
||||
return generateSmallKey(
|
||||
poseidon5([namePacked[0], namePacked[1], BigInt(year), BigInt(month), BigInt(day)])
|
||||
);
|
||||
};
|
||||
|
||||
export function getNameLeaf(nameMrz: (bigint | number)[], i?: number): bigint {
|
||||
const middleChunks: bigint[] = [];
|
||||
const chunks: (number | bigint)[][] = [];
|
||||
@@ -544,76 +603,6 @@ export function getNameYobLeaf(
|
||||
return generateSmallKey(poseidon2([getYearLeaf(yobMrz), getNameLeaf(nameMrz)]));
|
||||
}
|
||||
|
||||
export function getPassportNumberAndNationalityLeaf(
|
||||
passport: (bigint | number)[],
|
||||
nationality: (bigint | number)[],
|
||||
i?: number
|
||||
): bigint {
|
||||
if (passport.length !== 9) {
|
||||
console.log('parsed passport length is not 9:', i, passport);
|
||||
return;
|
||||
}
|
||||
if (nationality.length !== 3) {
|
||||
console.log('parsed nationality length is not 3:', i, nationality);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fullHash = poseidon12(passport.concat(nationality));
|
||||
return generateSmallKey(fullHash);
|
||||
} catch (err) {
|
||||
console.log('err : passport', err, i, passport);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------
|
||||
|
||||
// AADHAAR
|
||||
|
||||
//---------------------------
|
||||
|
||||
export function buildAadhaarSMT(field: any[], treetype: string): [number, number, SMT] {
|
||||
let count = 0;
|
||||
let startTime = performance.now();
|
||||
|
||||
const hash2 = (childNodes: ChildNodes) =>
|
||||
childNodes.length === 2 ? poseidon2(childNodes) : poseidon3(childNodes);
|
||||
const tree = new SMT(hash2, true);
|
||||
|
||||
for (let i = 0; i < field.length; i++) {
|
||||
const entry = field[i];
|
||||
|
||||
if (i !== 0) {
|
||||
console.log('Processing', treetype, 'number', i, 'out of', field.length);
|
||||
}
|
||||
|
||||
let leaf = BigInt(0);
|
||||
let reverse_leaf = BigInt(0);
|
||||
if (treetype == 'name_and_dob') {
|
||||
leaf = processNameAndDobAadhaar(entry, i);
|
||||
reverse_leaf = processNameAndDobAadhaar(entry, i, true);
|
||||
} else if (treetype == 'name_and_yob') {
|
||||
leaf = processNameAndYobAadhaar(entry, i);
|
||||
reverse_leaf = processNameAndYobAadhaar(entry, i, true);
|
||||
}
|
||||
|
||||
if (leaf == BigInt(0) || tree.createProof(leaf).membership) {
|
||||
console.log('This entry already exists in the tree, skipping...');
|
||||
continue;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
tree.add(leaf, BigInt(1));
|
||||
if (reverse_leaf == BigInt(0) || tree.createProof(reverse_leaf).membership) {
|
||||
console.log('This entry already exists in the tree, skipping...');
|
||||
continue;
|
||||
}
|
||||
tree.add(reverse_leaf, BigInt(1));
|
||||
count += 1;
|
||||
}
|
||||
|
||||
return [count, performance.now() - startTime, tree];
|
||||
}
|
||||
|
||||
const processNameAndDobAadhaar = (entry: any, i: number, reverse: boolean = false): bigint => {
|
||||
let firstName = entry.First_Name;
|
||||
let lastName = entry.Last_Name;
|
||||
@@ -684,18 +673,6 @@ const processDobAadhaar = (year: string, month: string, day: string): bigint[] =
|
||||
return [year, month, day].map(BigInt);
|
||||
};
|
||||
|
||||
export const getNameDobLeafAadhaar = (name: string, year: string, month: string, day: string) => {
|
||||
const paddedName = name
|
||||
.toUpperCase()
|
||||
.padEnd(62, '\0')
|
||||
.split('')
|
||||
.map((char) => char.charCodeAt(0));
|
||||
const namePacked = packBytes(paddedName);
|
||||
return generateSmallKey(
|
||||
poseidon5([namePacked[0], namePacked[1], BigInt(year), BigInt(month), BigInt(day)])
|
||||
);
|
||||
};
|
||||
|
||||
export const getNameYobLeafAahaar = (name: string, year: string) => {
|
||||
const paddedName = name
|
||||
.toUpperCase()
|
||||
@@ -706,3 +683,24 @@ export const getNameYobLeafAahaar = (name: string, year: string) => {
|
||||
|
||||
return generateSmallKey(poseidon3([namePacked[0], namePacked[1], BigInt(year)]));
|
||||
};
|
||||
|
||||
export function getPassportNumberAndNationalityLeaf(
|
||||
passport: (bigint | number)[],
|
||||
nationality: (bigint | number)[],
|
||||
i?: number
|
||||
): bigint {
|
||||
if (passport.length !== 9) {
|
||||
console.log('parsed passport length is not 9:', i, passport);
|
||||
return;
|
||||
}
|
||||
if (nationality.length !== 3) {
|
||||
console.log('parsed nationality length is not 3:', i, nationality);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fullHash = poseidon12(passport.concat(nationality));
|
||||
return generateSmallKey(fullHash);
|
||||
} catch (err) {
|
||||
console.log('err : passport', err, i, passport);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"build": "yarn workspaces foreach --topological-dev --parallel --exclude @selfxyz/contracts -i --all run build",
|
||||
"check:versions": "node scripts/check-package-versions.mjs",
|
||||
"format": "SKIP_BUILD_DEPS=1 yarn format:root && yarn format:github && SKIP_BUILD_DEPS=1 yarn workspaces foreach --parallel -i --all --exclude self-workspace-root run format",
|
||||
"format:github": "prettier --parser yaml --write .github/**/*.yml --single-quote false",
|
||||
"format:root": "prettier --parser markdown --write *.md scripts/**/*.md && prettier --parser yaml --write .*.{yml,yaml} --single-quote false && prettier --write scripts/**/*.{js,mjs,ts} && prettier --parser json --write scripts/**/*.json",
|
||||
"format:github": "yarn prettier --parser yaml --write .github/**/*.yml --single-quote false",
|
||||
"format:root": "echo 'format markdown' && yarn prettier --parser markdown --write *.md && echo 'format yaml' && yarn prettier --parser yaml --write .*.{yml,yaml} --single-quote false && yarn prettier --write scripts/**/*.{js,mjs,ts} && yarn prettier --parser json --write scripts/**/*.json",
|
||||
"gitleaks": "gitleaks protect --staged --redact --config=.gitleaks.toml",
|
||||
"postinstall": "node scripts/run-patch-package.cjs",
|
||||
"lint": "yarn lint:headers && yarn workspaces foreach --parallel -i --all --exclude self-workspace-root run lint",
|
||||
@@ -46,6 +46,7 @@
|
||||
"husky": "9.1.7",
|
||||
"knip": "^5.62.0",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0",
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
"demo:ios": "yarn workspace demo-app ios",
|
||||
"demo:start": "yarn workspace demo-app start",
|
||||
"demo:test": "yarn workspace demo-app test",
|
||||
"fmt": "prettier --check .",
|
||||
"fmt:fix": "prettier --write .",
|
||||
"fmt": "yarn prettier --check .",
|
||||
"fmt:fix": "yarn prettier --write .",
|
||||
"format": "sh -c 'if [ -z \"$SKIP_BUILD_DEPS\" ]; then yarn nice; else yarn fmt:fix; fi'",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
@@ -70,6 +70,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@selfxyz/common": "workspace:^",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tslib": "^2.6.2",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
|
||||
@@ -63,6 +63,8 @@ export { extractMRZInfo, formatDateToYYMMDD, scanMRZ } from './mrz';
|
||||
|
||||
export { generateMockDocument, signatureAlgorithmToStrictSignatureAlgorithm } from './mock/generator';
|
||||
|
||||
export { generateTEEInputsDisclose } from './processing/generate-disclosure-inputs';
|
||||
|
||||
// Core functions
|
||||
export { isPassportDataValid } from './validation/document';
|
||||
|
||||
@@ -73,5 +75,4 @@ export { parseNFCResponse, scanNFC } from './nfc';
|
||||
export { reactNativeScannerAdapter } from './adapters/react-native/scanner';
|
||||
|
||||
export { scanQRProof } from './qr';
|
||||
|
||||
export { webScannerShim } from './adapters/web/shims';
|
||||
|
||||
@@ -98,6 +98,10 @@ export { formatDateToYYMMDD, scanMRZ } from './mrz';
|
||||
|
||||
export { generateMockDocument, signatureAlgorithmToStrictSignatureAlgorithm } from './mock/generator';
|
||||
|
||||
export { generateTEEInputsDisclose } from './processing/generate-disclosure-inputs';
|
||||
|
||||
// Documents utils
|
||||
|
||||
// Core functions
|
||||
export { isPassportDataValid } from './validation/document';
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 type { DocumentCategory, PassportData } from '@selfxyz/common/types';
|
||||
import type { SelfApp } from '@selfxyz/common/utils';
|
||||
import { generateTEEInputsDiscloseStateless } from '@selfxyz/common/utils/circuits/registerInputs';
|
||||
|
||||
import { useProtocolStore } from '../stores/protocolStore';
|
||||
|
||||
export function generateTEEInputsDisclose(secret: string, passportData: PassportData, selfApp: SelfApp) {
|
||||
return generateTEEInputsDiscloseStateless(secret, passportData, selfApp, (document: DocumentCategory, tree) => {
|
||||
const protocolStore = useProtocolStore.getState();
|
||||
const docStore = (protocolStore as any)[document];
|
||||
if (!docStore) {
|
||||
throw new Error(`Unknown or unloaded document category in protocol store: ${document}`);
|
||||
}
|
||||
switch (tree) {
|
||||
case 'ofac':
|
||||
return docStore.ofac_trees;
|
||||
case 'commitment':
|
||||
if (!docStore.commitment_tree) {
|
||||
throw new Error('Commitment tree not loaded');
|
||||
}
|
||||
return docStore.commitment_tree;
|
||||
default:
|
||||
throw new Error('Unknown tree type');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3,3 +3,4 @@
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
export { useProtocolStore } from './protocolStore';
|
||||
export { useSelfAppStore } from './selfAppStore';
|
||||
|
||||
@@ -17,11 +17,7 @@ interface SelfAppState {
|
||||
cleanSelfApp: () => void;
|
||||
setSelfApp: (selfApp: SelfApp | null) => void;
|
||||
_initSocket: (sessionId: string) => Socket;
|
||||
handleProofResult: (
|
||||
proof_verified: boolean,
|
||||
error_code?: string,
|
||||
reason?: string,
|
||||
) => void;
|
||||
handleProofResult: (proof_verified: boolean, error_code?: string, reason?: string) => void;
|
||||
}
|
||||
|
||||
export const useSelfAppStore = create<SelfAppState>((set, get) => ({
|
||||
@@ -30,9 +26,7 @@ export const useSelfAppStore = create<SelfAppState>((set, get) => ({
|
||||
socket: null,
|
||||
|
||||
_initSocket: (sessionId: string): Socket => {
|
||||
const connectionUrl = WS_DB_RELAYER.startsWith('https')
|
||||
? WS_DB_RELAYER.replace(/^https/, 'wss')
|
||||
: WS_DB_RELAYER;
|
||||
const connectionUrl = WS_DB_RELAYER.startsWith('https') ? WS_DB_RELAYER.replace(/^https/, 'wss') : WS_DB_RELAYER;
|
||||
const socketUrl = `${connectionUrl}/websocket`;
|
||||
|
||||
// Create a new socket connection using the updated URL.
|
||||
@@ -72,8 +66,7 @@ export const useSelfAppStore = create<SelfAppState>((set, get) => ({
|
||||
// Listen for the event only once per connection attempt
|
||||
socket.once('self_app', (data: unknown) => {
|
||||
try {
|
||||
const appData: SelfApp =
|
||||
typeof data === 'string' ? JSON.parse(data) : (data as SelfApp);
|
||||
const appData: SelfApp = typeof data === 'string' ? JSON.parse(data) : (data as SelfApp);
|
||||
|
||||
// Basic validation
|
||||
if (!appData || typeof appData !== 'object' || !appData.sessionId) {
|
||||
@@ -130,18 +123,12 @@ export const useSelfAppStore = create<SelfAppState>((set, get) => ({
|
||||
set({ selfApp: null, sessionId: null, socket: null });
|
||||
},
|
||||
|
||||
handleProofResult: (
|
||||
proof_verified: boolean,
|
||||
error_code?: string,
|
||||
reason?: string,
|
||||
) => {
|
||||
handleProofResult: (proof_verified: boolean, error_code?: string, reason?: string) => {
|
||||
const socket = get().socket;
|
||||
const sessionId = get().sessionId;
|
||||
|
||||
if (!socket || !sessionId) {
|
||||
console.error(
|
||||
'[SelfAppStore] Cannot handleProofResult: Socket or SessionId missing.',
|
||||
);
|
||||
console.error('[SelfAppStore] Cannot handleProofResult: Socket or SessionId missing.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { PassportData, SelfApp } from '@selfxyz/common';
|
||||
|
||||
import { generateTEEInputsDisclose } from '../../src/processing/generate-disclosure-inputs';
|
||||
import { useProtocolStore } from '../../src/stores/protocolStore';
|
||||
// Mocks for dependencies
|
||||
const mockSecret = '0x' + '00'.repeat(30) + 'a4ec'; // 32-byte hex string
|
||||
const mockPassportData: PassportData = {
|
||||
mrz: 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<<<<L898902C36UTO7408122F1204159ZE184226B<<<<',
|
||||
dsc: '',
|
||||
eContent: [],
|
||||
signedAttr: [],
|
||||
encryptedDigest: [],
|
||||
passportMetadata: {
|
||||
dataGroups: 'dg1',
|
||||
dg1Size: 100,
|
||||
dg1HashSize: 32,
|
||||
dg1HashFunction: 'sha256',
|
||||
dg1HashOffset: 0,
|
||||
dgPaddingBytes: 0,
|
||||
eContentSize: 100,
|
||||
eContentHashFunction: 'sha256',
|
||||
eContentHashOffset: 0,
|
||||
signedAttrSize: 100,
|
||||
signedAttrHashFunction: 'sha256',
|
||||
signatureAlgorithm: 'rsa',
|
||||
saltLength: 32,
|
||||
curveOrExponent: '65537',
|
||||
signatureAlgorithmBits: 0,
|
||||
countryCode: '',
|
||||
cscaFound: false,
|
||||
cscaHashFunction: '',
|
||||
cscaSignatureAlgorithm: '',
|
||||
cscaSaltLength: 0,
|
||||
cscaCurveOrExponent: '',
|
||||
cscaSignatureAlgorithmBits: 0,
|
||||
dsc: '',
|
||||
csca: '',
|
||||
},
|
||||
dsc_parsed: {
|
||||
tbsBytes: new Array(100).fill(1),
|
||||
signatureAlgorithm: 'rsa',
|
||||
publicKeyAlgorithm: 'rsa',
|
||||
publicKeyDetails: {
|
||||
modulus: '12345',
|
||||
exponent: '65537',
|
||||
},
|
||||
signature: new Array(100).fill(1),
|
||||
} as any,
|
||||
csca_parsed: {
|
||||
tbsBytes: new Array(100).fill(1),
|
||||
signatureAlgorithm: 'rsa',
|
||||
publicKeyAlgorithm: 'rsa',
|
||||
publicKeyDetails: {
|
||||
modulus: '12345',
|
||||
exponent: '65537',
|
||||
},
|
||||
signature: new Array(100).fill(1),
|
||||
} as any,
|
||||
documentType: 'passport',
|
||||
documentCategory: 'passport',
|
||||
mock: true,
|
||||
};
|
||||
const mockSelfApp: SelfApp = {
|
||||
userId: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
||||
appName: 'TestSelfApp',
|
||||
logoBase64: '',
|
||||
endpointType: 'https',
|
||||
endpoint: 'https://test.example.com',
|
||||
deeplinkCallback: '',
|
||||
header: '',
|
||||
scope: 'test',
|
||||
sessionId: '',
|
||||
userIdType: 'hex',
|
||||
devMode: false,
|
||||
disclosures: {},
|
||||
version: 0,
|
||||
chainID: 42220,
|
||||
userDefinedData: '',
|
||||
};
|
||||
|
||||
vi.mock('../../src/stores/protocolStore', () => ({
|
||||
useProtocolStore: {
|
||||
getState: () => ({
|
||||
passport: {
|
||||
ofac_trees: {
|
||||
nameAndDob: '{"root":["0"]}',
|
||||
nameAndYob: '{"root":["0"]}',
|
||||
passportNoAndNationality: '{"root":["0"]}',
|
||||
},
|
||||
commitment_tree: '[[]]',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('generateTEEInputsDisclose', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
it('throws error for unknown document category', () => {
|
||||
// Mock the store to return an unknown document category
|
||||
vi.spyOn(useProtocolStore, 'getState').mockReturnValue({
|
||||
unknown: undefined,
|
||||
} as any);
|
||||
|
||||
expect(() => generateTEEInputsDisclose(mockSecret, mockPassportData, mockSelfApp)).toThrowError(
|
||||
`Unknown or unloaded document category in protocol store: passport`,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws error for unknown tree type', () => {
|
||||
// This test doesn't make sense as written since tree type is determined internally
|
||||
// Let's test the commitment tree validation instead
|
||||
vi.spyOn(useProtocolStore, 'getState').mockReturnValue({
|
||||
passport: {
|
||||
ofac_trees: 'ofac-tree-data',
|
||||
commitment_tree: undefined,
|
||||
},
|
||||
} as any);
|
||||
|
||||
expect(() => generateTEEInputsDisclose(mockSecret, mockPassportData, mockSelfApp)).toThrowError(
|
||||
`Invalid OFAC tree structure: missing required fields`,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws error if commitment tree not loaded', () => {
|
||||
vi.spyOn(useProtocolStore, 'getState').mockReturnValue({
|
||||
passport: {
|
||||
ofac_trees: 'ofac-tree-data',
|
||||
commitment_tree: undefined,
|
||||
},
|
||||
} as any);
|
||||
|
||||
expect(() => generateTEEInputsDisclose(mockSecret, mockPassportData, mockSelfApp)).toThrowError(
|
||||
`Invalid OFAC tree structure: missing required fields`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -5038,6 +5038,7 @@ __metadata:
|
||||
eslint-plugin-sort-exports: "npm:^0.8.0"
|
||||
jsdom: "npm:^24.0.0"
|
||||
prettier: "npm:^3.5.3"
|
||||
socket.io-client: "npm:^4.8.1"
|
||||
tslib: "npm:^2.6.2"
|
||||
tsup: "npm:^8.0.1"
|
||||
typescript: "npm:^5.9.2"
|
||||
@@ -23234,6 +23235,7 @@ __metadata:
|
||||
husky: "npm:9.1.7"
|
||||
knip: "npm:^5.62.0"
|
||||
patch-package: "npm:^8.0.0"
|
||||
prettier: "npm:^3.5.3"
|
||||
react: "npm:^18.3.1"
|
||||
react-native: "npm:0.76.9"
|
||||
typescript: "npm:^5.9.2"
|
||||
|
||||
Reference in New Issue
Block a user