From 5305ef83fcf6b80b80afe8c80eaf2801ba092c84 Mon Sep 17 00:00:00 2001 From: Justin Hernandez Date: Wed, 6 Aug 2025 15:18:42 -0700 Subject: [PATCH] Feat: Improved import export sorting for app and common (#833) * save import sorting work * remove dupe headers and fix type errors * sort imports and exports * fix errors from export sorting * fix tests * codex feedback * fix exports * fix exports and tweak test build * fix export and format * fix license headers * fix app building and clean up test errors * fix android local e2e test * improve caching * final fixes * remove invalid option * fix sorting and get random values loading * fix import sorting --- .github/workflows/mobile-e2e.yml | 46 +- app/.eslintrc.cjs | 106 +- app/App.tsx | 2 - app/Gemfile.lock | 2 +- app/env.ts | 26 +- app/index.js | 15 +- app/metro.config.cjs | 4 - app/package.json | 8 +- app/scripts/check-duplicate-headers.cjs | 63 + app/scripts/test-e2e-local.sh | 23 +- app/src/RemoteConfig.shared.ts | 249 ++-- app/src/RemoteConfig.ts | 43 +- app/src/RemoteConfig.web.ts | 37 +- app/src/Segment.ts | 3 +- app/src/Sentry.ts | 48 +- app/src/Sentry.web.ts | 48 +- app/src/assets/animations/loader.ts | 2 +- app/src/components/Disclosures.tsx | 7 +- app/src/components/Mnemonic.tsx | 3 +- app/src/components/NavBar/BaseNavBar.tsx | 48 +- app/src/components/NavBar/DefaultNavBar.tsx | 5 +- app/src/components/NavBar/HomeNavBar.tsx | 3 +- app/src/components/NavBar/ProgressNavBar.tsx | 12 +- app/src/components/TextsContainer.tsx | 3 +- app/src/components/buttons/AbstractButton.tsx | 6 +- .../buttons/HeldPrimaryButtonProveScreen.tsx | 3 +- app/src/components/buttons/PrimaryButton.tsx | 3 +- .../buttons/PrimaryButtonLongHold.shared.ts | 14 +- .../buttons/PrimaryButtonLongHold.tsx | 15 +- .../buttons/PrimaryButtonLongHold.web.tsx | 9 +- .../components/buttons/SecondaryButton.tsx | 3 +- app/src/components/native/PassportCamera.tsx | 8 +- .../components/native/PassportCamera.web.tsx | 2 +- app/src/components/native/QRCodeScanner.tsx | 8 +- app/src/components/native/RCTFragment.tsx | 13 +- app/src/components/typography/Additional.tsx | 3 +- app/src/components/typography/Caution.tsx | 3 +- app/src/components/typography/Description.tsx | 3 +- app/src/components/typography/Title.tsx | 2 +- app/src/consts/analytics.ts | 108 +- app/src/consts/links.ts | 16 +- app/src/hooks/useAppUpdates.ts | 3 +- app/src/hooks/useAppUpdates.web.ts | 3 +- app/src/hooks/useHapticNavigation.ts | 5 +- app/src/hooks/useModal.ts | 5 +- app/src/layouts/AppLayout.tsx | 3 +- app/src/layouts/ExpandableBottomLayout.tsx | 3 +- app/src/layouts/SimpleScrolledTitleLayout.tsx | 3 +- app/src/mocks/react-native-gesture-handler.ts | 29 +- .../mocks/react-native-safe-area-context.js | 32 +- app/src/navigation/aesop.ts | 5 +- app/src/navigation/dev.ts | 6 +- app/src/navigation/home.ts | 6 +- app/src/navigation/index.tsx | 21 +- app/src/navigation/misc.tsx | 3 +- app/src/navigation/passport.ts | 3 +- app/src/navigation/prove.ts | 6 +- app/src/navigation/recovery.ts | 6 +- app/src/navigation/recovery.web.ts | 3 +- app/src/navigation/settings.ts | 6 +- app/src/navigation/settings.web.ts | 6 +- app/src/providers/authProvider.tsx | 22 +- app/src/providers/authProvider.web.tsx | 24 +- .../notificationTrackingProvider.tsx | 6 +- .../notificationTrackingProvider.web.tsx | 3 +- app/src/providers/passportDataProvider.tsx | 1190 ++++++++--------- app/src/providers/remoteConfigProvider.tsx | 4 +- app/src/screens/dev/DevFeatureFlagsScreen.tsx | 2 +- app/src/screens/dev/DevSettingsScreen.tsx | 20 +- app/src/screens/dev/MockDataScreen.tsx | 22 +- .../screens/dev/MockDataScreenDeepLink.tsx | 10 +- app/src/screens/home/DisclaimerScreen.tsx | 3 +- app/src/screens/home/HomeScreen.tsx | 12 +- .../screens/home/ProofHistoryDetailScreen.tsx | 6 +- app/src/screens/home/ProofHistoryScreen.tsx | 8 +- app/src/screens/misc/LoadingScreen.tsx | 13 +- app/src/screens/misc/ModalScreen.tsx | 14 +- app/src/screens/misc/SplashScreen.tsx | 3 +- .../passport/NFCMethodSelectionScreen.tsx | 3 +- .../screens/passport/PassportCameraScreen.tsx | 9 +- .../passport/PassportCameraTroubleScreen.tsx | 3 +- .../passport/PassportNFCScanScreen.tsx | 20 +- .../passport/PassportNFCTroubleScreen.tsx | 3 +- .../passport/PassportOnboardingScreen.tsx | 3 +- .../screens/prove/ConfirmBelongingScreen.tsx | 4 +- .../prove/ProofRequestStatusScreen.tsx | 3 +- app/src/screens/prove/ProveScreen.tsx | 16 +- app/src/screens/prove/QRCodeTroubleScreen.tsx | 3 +- app/src/screens/prove/ViewFinderScreen.tsx | 17 +- .../recovery/AccountRecoveryChoiceScreen.tsx | 3 +- .../recovery/AccountVerifiedSuccessScreen.tsx | 3 +- .../recovery/RecoverWithPhraseScreen.tsx | 5 +- .../screens/settings/CloudBackupScreen.tsx | 6 +- .../settings/ManageDocumentsScreen.tsx | 5 +- .../settings/PassportDataInfoScreen.tsx | 6 +- app/src/screens/settings/SettingsScreen.tsx | 12 +- app/src/stores/database.ts | 8 +- app/src/stores/database.web.ts | 8 +- app/src/stores/proof-types.ts | 50 +- app/src/stores/proofHistoryStore.ts | 6 +- app/src/stores/protocolStore.ts | 3 +- app/src/stores/selfAppStore.tsx | 8 +- app/src/stores/settingStore.ts | 3 +- app/src/stores/userStore.ts | 3 +- app/src/utils/cloudBackup/google.ts | 26 +- app/src/utils/cloudBackup/helpers.ts | 2 +- app/src/utils/cloudBackup/index.ts | 109 +- app/src/utils/cloudBackup/ios.ts | 30 +- app/src/utils/colors.ts | 79 +- app/src/utils/deeplinks.ts | 56 +- app/src/utils/ethers.ts | 2 +- app/src/utils/haptic/index.ts | 122 +- app/src/utils/haptic/shared.ts | 14 +- app/src/utils/haptic/trigger.ts | 3 +- app/src/utils/haptic/trigger.web.ts | 3 +- app/src/utils/modalCallbackRegistry.ts | 8 +- app/src/utils/nfcScanner.ts | 40 +- .../notificationService.shared.ts | 38 +- .../notifications/notificationService.ts | 81 +- .../notifications/notificationService.web.ts | 65 +- app/src/utils/ofac.ts | 4 +- app/src/utils/proving/attest.ts | 456 +++---- app/src/utils/proving/cose.ts | 3 +- app/src/utils/proving/index.ts | 18 +- .../utils/proving/loadingScreenStateText.ts | 72 +- app/src/utils/proving/provingInputs.ts | 32 +- app/src/utils/proving/provingMachine.ts | 71 +- app/src/utils/proving/provingUtils.ts | 69 +- app/src/utils/proving/validateDocument.ts | 599 ++++----- app/src/utils/utils.ts | 34 +- app/tamagui.config.ts | 4 +- app/tests/__setup__/@env.js | 20 +- .../__setup__/notificationServiceMock.js | 13 +- app/tests/src/RemoteConfig.test.ts | 26 +- app/tests/src/hooks/useAesopRedesign.test.ts | 4 +- app/tests/src/hooks/useAppUpdates.test.ts | 10 +- .../src/hooks/useConnectionModal.test.ts | 6 +- .../src/hooks/useHapticNavigation.test.ts | 9 +- app/tests/src/hooks/useMnemonic.test.ts | 6 +- app/tests/src/hooks/useModal.test.ts | 6 +- .../src/hooks/useRecoveryPrompts.test.ts | 4 +- .../providers/remoteConfigProvider.test.tsx | 6 +- app/tests/src/stores/database.test.ts | 9 + .../src/stores/proofHistoryStore.test.ts | 12 +- app/tests/src/stores/settingStore.test.ts | 4 +- app/tests/utils/cloudBackup.test.ts | 18 +- app/tests/utils/ethers.test.ts | 1 - app/tests/utils/notificationService.test.ts | 30 +- app/tests/utils/proving/coseVerify.test.ts | 1 + .../proving/loadingScreenStateText.test.ts | 4 +- .../provingMachine.generatePayload.test.ts | 2 - app/tests/web-build-render.test.ts | 3 +- app/vite.config.ts | 5 +- app/web/main.tsx | 8 +- common/.eslintrc.cjs | 116 ++ common/index.ts | 144 +- common/package.json | 109 +- common/scripts/testExports.js | 183 ++- common/scripts/validateExports.js | 199 +++ common/src/constants/constants.ts | 342 ++--- common/src/constants/countries.ts | 4 +- common/src/constants/index.ts | 50 +- common/src/constants/sampleDataHashes.ts | 30 +- common/src/constants/vkey.ts | 1094 +++++++-------- common/src/scripts/generateCountryOptions.ts | 5 +- common/src/types/app.ts | 2 +- common/src/types/index.ts | 2 +- common/src/types/passport.ts | 3 +- common/src/utils/appType.ts | 13 +- common/src/utils/bytes.ts | 200 +-- .../utils/certificate_parsing/certUtils.ts | 11 +- .../utils/certificate_parsing/curveUtils.ts | 6 +- .../src/utils/certificate_parsing/curves.ts | 132 +- .../certificate_parsing/dataStructure.ts | 18 +- .../src/utils/certificate_parsing/elliptic.ts | 2 +- common/src/utils/certificate_parsing/index.ts | 50 +- .../src/utils/certificate_parsing/oidUtils.ts | 8 +- common/src/utils/certificate_parsing/oids.ts | 94 +- .../certificate_parsing/parseCertificate.ts | 2 +- .../parseCertificateNode.ts | 5 +- .../parseCertificateSimple.ts | 372 +++--- .../utils/certificate_parsing/parseNode.ts | 2 +- common/src/utils/certificate_parsing/utils.ts | 38 +- common/src/utils/circuits/circuitsName.ts | 2 +- common/src/utils/circuits/formatOutputs.ts | 164 +-- common/src/utils/circuits/generateInputs.ts | 229 ++-- common/src/utils/circuits/index.ts | 53 +- common/src/utils/circuits/uuid.ts | 50 +- .../src/utils/contracts/forbiddenCountries.ts | 2 +- common/src/utils/contracts/formatCallData.ts | 66 +- common/src/utils/contracts/index.ts | 7 +- common/src/utils/csca.ts | 106 +- common/src/utils/date.ts | 30 +- common/src/utils/hash.ts | 163 +-- common/src/utils/hash/custom.ts | 2 +- common/src/utils/hash/sha.ts | 2 +- common/src/utils/index.ts | 76 +- common/src/utils/passportData.ts | 3 +- common/src/utils/passports/dg1.ts | 5 +- common/src/utils/passports/format.ts | 116 +- common/src/utils/passports/genMockIdDoc.ts | 74 +- .../utils/passports/genMockPassportData.ts | 63 +- common/src/utils/passports/getMockDSC.ts | 2 +- common/src/utils/passports/index.ts | 15 +- common/src/utils/passports/mock.ts | 6 +- common/src/utils/passports/parsing.ts | 6 +- common/src/utils/passports/passport.ts | 319 ++--- .../passport_parsing/brutForceDscSignature.ts | 13 +- .../brutForcePassportSignature.ts | 13 +- .../parseDscCertificateData.ts | 2 +- .../passport_parsing/parsePassportData.ts | 7 +- common/src/utils/passports/signature.ts | 6 +- common/src/utils/scope.ts | 31 +- common/src/utils/selfAttestation.ts | 2 +- common/src/utils/shaPad.ts | 145 +- common/src/utils/trees.ts | 442 +++--- common/src/utils/types.ts | 26 +- common/tests/genMockPassportData.test.ts | 3 +- common/tests/scope.test.ts | 1 + common/tsup.config.ts | 2 +- .../commitmentRegistration.test.ts | 2 +- .../test/integration/vcAndDisclose.test.ts | 2 +- contracts/test/utils/deployment.ts | 2 +- contracts/test/utils/deploymentV2.ts | 2 +- contracts/test/v2/discloseId.test.ts | 2 +- contracts/test/v2/disclosePassport.test.ts | 2 +- sdk/core/index.ts | 2 +- sdk/qrcode/components/SelfQRcode.tsx | 5 +- yarn.lock | 61 +- 229 files changed, 5707 insertions(+), 4867 deletions(-) create mode 100644 app/scripts/check-duplicate-headers.cjs create mode 100644 common/.eslintrc.cjs create mode 100644 common/scripts/validateExports.js diff --git a/.github/workflows/mobile-e2e.yml b/.github/workflows/mobile-e2e.yml index 2f8062e17..edfccdae9 100644 --- a/.github/workflows/mobile-e2e.yml +++ b/.github/workflows/mobile-e2e.yml @@ -181,6 +181,20 @@ jobs: run: curl -Ls "https://get.maestro.mobile.dev" | bash - name: Add Maestro to path run: echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" + - name: Cache Node modules + uses: actions/cache@v4 + with: + path: app/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('app/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Cache Ruby gems + uses: actions/cache@v4 + with: + path: app/vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('app/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- - name: Cache Pods uses: actions/cache@v4 with: @@ -193,10 +207,30 @@ jobs: - name: Cache Xcode build uses: actions/cache@v4 with: - path: app/ios/build - key: ${{ runner.os }}-xcode-${{ hashFiles('app/ios/Podfile.lock') }} + path: | + app/ios/build + ~/Library/Developer/Xcode/DerivedData + ~/Library/Caches/com.apple.dt.Xcode + key: ${{ runner.os }}-xcode-${{ hashFiles('app/ios/Podfile.lock') }}-${{ hashFiles('app/ios/OpenPassport.xcworkspace/contents.xcworkspacedata') }} restore-keys: | + ${{ runner.os }}-xcode-${{ hashFiles('app/ios/Podfile.lock') }}- ${{ runner.os }}-xcode- + - name: Cache Xcode Index + uses: actions/cache@v4 + with: + path: app/ios/build/Index.noindex + key: ${{ runner.os }}-xcode-index-${{ hashFiles('app/ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-xcode-index- + - name: Cache iOS Simulator + uses: actions/cache@v4 + with: + path: | + ~/Library/Developer/CoreSimulator/Devices + ~/Library/Developer/Xcode/iOS DeviceSupport + key: ${{ runner.os }}-simulator-${{ hashFiles('app/ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-simulator- - name: Build dependencies (outside main flow) run: | echo "Building dependencies..." @@ -212,13 +246,21 @@ jobs: echo "Setting up iOS Simulator..." echo "Available simulators:" xcrun simctl list devices | grep "iPhone 15" + + # Boot simulator and wait for it to be ready xcrun simctl boot "iPhone 15" || true xcrun simctl bootstatus "iPhone 15" -b + + # Wait for simulator to be fully ready + echo "Waiting for simulator to be ready..." + sleep 10 + echo "Simulator status:" xcrun simctl list devices | grep "iPhone 15" - name: Build iOS App run: | echo "Building iOS app..." + # Use cached derived data and enable parallel builds for faster compilation xcodebuild -workspace app/ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration Release -sdk iphonesimulator -derivedDataPath app/ios/build -jobs "$(sysctl -n hw.ncpu)" -parallelizeTargets -quiet || { echo "āŒ iOS build failed"; exit 1; } echo "āœ… iOS build succeeded" - name: Install and Test on iOS diff --git a/app/.eslintrc.cjs b/app/.eslintrc.cjs index b1e1b270f..85f929083 100644 --- a/app/.eslintrc.cjs +++ b/app/.eslintrc.cjs @@ -17,7 +17,7 @@ module.exports = { 'plugin:jest/recommended', 'plugin:prettier/recommended', ], - plugins: ['header', 'simple-import-sort', 'import'], + plugins: ['header', 'simple-import-sort', 'import', 'sort-exports'], ignorePatterns: [ 'ios/', 'android/', @@ -41,34 +41,111 @@ module.exports = { 'import/ignore': ['react-native'], }, rules: { - // Import/Export Rules + // Enhanced Import/Export Rules + 'import/order': 'off', 'no-duplicate-imports': 'off', - 'simple-import-sort/exports': 'warn', - 'simple-import-sort/imports': 'warn', - // Header rule + // Import sorting with explicit groups for your project structure + + 'simple-import-sort/imports': [ + 'error', + { + groups: [ + // Node.js built-ins + + ['^node:'], + ['^node:.*/'], + // External packages + + ['^[a-zA-Z]'], + // Internal workspace packages + + ['^@selfxyz/'], + // Internal relative imports + + ['^[./]'], + ], + }, + ], + + // Export sorting - using sort-exports for better type prioritization + + 'sort-exports/sort-exports': [ + 'error', + { sortDir: 'asc', ignoreCase: false, sortExportKindFirst: 'type' }, + ], + + // Type import enforcement + + '@typescript-eslint/consistent-type-imports': [ + 'error', + { prefer: 'type-imports' }, + ], + + // Standard import rules + + 'import/first': 'error', + 'import/newline-after-import': 'error', + 'import/no-duplicates': 'error', + + // Header rule - configured to prevent duplicates, single line header only + 'header/header': [ - 2, + 'error', 'line', ' SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11', - 2, + ], + + // Prevent empty lines at the beginning and end of files, and limit consecutive empty lines + + 'no-multiple-empty-lines': [ + 'error', + { + max: 1, + maxEOF: 0, + maxBOF: 0, + }, + ], + // Enforce empty line after header comments (but not at file start) + + 'lines-around-comment': [ + 'error', + { + beforeBlockComment: false, + afterBlockComment: false, + beforeLineComment: false, + afterLineComment: false, + allowBlockStart: true, + allowBlockEnd: false, + allowObjectStart: false, + allowObjectEnd: false, + allowArrayStart: false, + allowArrayEnd: false, + allowClassStart: false, + allowClassEnd: false, + applyDefaultIgnorePatterns: false, + }, ], // Add prettier rule to show prettier errors as ESLint errors + 'prettier/prettier': ['warn', {}, { usePrettierrc: true }], // React Core Rules + 'react/no-unescaped-entities': 'off', 'react/prop-types': 'off', 'react/react-in-jsx-scope': 'off', 'react-native/no-inline-styles': 'off', // React Hooks Rules + 'react-hooks/exhaustive-deps': 'warn', // General JavaScript Rules // Warn on common issues but don't block development + 'no-console': 'warn', 'no-empty-pattern': 'off', 'prefer-const': 'warn', @@ -85,6 +162,7 @@ module.exports = { 'no-empty': 'off', // Override rules conflicting with TypeScript union formatting + '@typescript-eslint/indent': 'off', }, overrides: [ @@ -105,5 +183,19 @@ module.exports = { 'no-undef': 'off', }, }, + { + // Disable export sorting for files with dependency issues + files: [ + 'src/components/NavBar/BaseNavBar.tsx', + 'src/navigation/index.tsx', + 'src/providers/passportDataProvider.tsx', + 'src/utils/cloudBackup/helpers.ts', + 'src/utils/haptic/index.ts', + 'src/utils/proving/provingUtils.ts', + ], + rules: { + 'sort-exports/sort-exports': 'off', + }, + }, ], }; diff --git a/app/App.tsx b/app/App.tsx index 181beb58d..fcd1ce5fd 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -1,8 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 // CI/CD Pipeline Test - July 31, 2025 - With Permissions Fix -import 'react-native-get-random-values'; - import { Buffer } from 'buffer'; import React from 'react'; import { YStack } from 'tamagui'; diff --git a/app/Gemfile.lock b/app/Gemfile.lock index 551da5fef..ebeece4f9 100644 --- a/app/Gemfile.lock +++ b/app/Gemfile.lock @@ -25,7 +25,7 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.4.0) - aws-partitions (1.1142.0) + aws-partitions (1.1143.0) aws-sdk-core (3.229.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) diff --git a/app/env.ts b/app/env.ts index 36dbb4d33..63ca99dff 100644 --- a/app/env.ts +++ b/app/env.ts @@ -1,18 +1,22 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +export const DEFAULT_DOB = undefined; + +export const DEFAULT_DOE = undefined; + +export const DEFAULT_PNUMBER = undefined; + +export const ENABLE_DEBUG_LOGS = process.env.ENABLE_DEBUG_LOGS === 'true'; + +export const GOOGLE_SIGNIN_ANDROID_CLIENT_ID = + process.env.GOOGLE_SIGNIN_ANDROID_CLIENT_ID; + +export const GOOGLE_SIGNIN_WEB_CLIENT_ID = + process.env.GOOGLE_SIGNIN_WEB_CLIENT_ID; /* This file provides compatiblity between how web expects env variables to be and how native does. * on web it is aliased to @env on native it is not used */ - export const IS_TEST_BUILD = process.env.IS_TEST_BUILD === 'true'; -export const GOOGLE_SIGNIN_ANDROID_CLIENT_ID = - process.env.GOOGLE_SIGNIN_ANDROID_CLIENT_ID; -export const GOOGLE_SIGNIN_WEB_CLIENT_ID = - process.env.GOOGLE_SIGNIN_WEB_CLIENT_ID; -export const SENTRY_DSN = process.env.SENTRY_DSN; -export const SEGMENT_KEY = process.env.SEGMENT_KEY; -export const ENABLE_DEBUG_LOGS = process.env.ENABLE_DEBUG_LOGS === 'true'; -export const DEFAULT_PNUMBER = undefined; -export const DEFAULT_DOB = undefined; -export const DEFAULT_DOE = undefined; export const MIXPANEL_NFC_PROJECT_TOKEN = undefined; +export const SEGMENT_KEY = process.env.SEGMENT_KEY; +export const SENTRY_DSN = process.env.SENTRY_DSN; diff --git a/app/index.js b/app/index.js index 290cf446a..cb319624a 100644 --- a/app/index.js +++ b/app/index.js @@ -3,10 +3,12 @@ /** * @format */ -import './src/utils/ethers'; -import 'react-native-gesture-handler'; -import { config } from '@tamagui/config/v2-native'; +// CRITICAL: Import crypto polyfill FIRST, before any modules that use crypto/uuid +// eslint-disable-next-line simple-import-sort/imports +import 'react-native-get-random-values'; + +import { Buffer } from 'buffer'; import React from 'react'; import { AppRegistry, LogBox } from 'react-native'; import { createTamagui, TamaguiProvider } from 'tamagui'; @@ -14,6 +16,13 @@ import { createTamagui, TamaguiProvider } from 'tamagui'; import App from './App'; import { name as appName } from './app.json'; +import './src/utils/ethers'; +import 'react-native-gesture-handler'; +import { config } from '@tamagui/config/v2-native'; + +// Set global Buffer before any other imports +global.Buffer = Buffer; + const tamaguiConfig = createTamagui(config); LogBox.ignoreLogs([ diff --git a/app/metro.config.cjs b/app/metro.config.cjs index 1d9af6c61..0a5422371 100644 --- a/app/metro.config.cjs +++ b/app/metro.config.cjs @@ -27,10 +27,6 @@ const extraNodeModules = { 'dist/esm/src/constants/index.js', ), // Constants subpaths - '@selfxyz/common/constants/core': path.resolve( - commonPath, - 'dist/esm/src/constants/constants.js', - ), '@selfxyz/common/constants/countries': path.resolve( commonPath, 'dist/esm/src/constants/countries.js', diff --git a/app/package.json b/app/package.json index 7cb439042..0d1b80e5c 100644 --- a/app/package.json +++ b/app/package.json @@ -32,8 +32,9 @@ "install-app:setup": "yarn install && yarn build:deps && cd ios && bundle install && bundle exec pod install && cd ..", "ios": "yarn build:deps && react-native run-ios --scheme OpenPassport", "ios:fastlane-debug": "yarn reinstall && bundle exec fastlane --verbose ios internal_test", - "lint": "eslint .", - "lint:fix": "eslint --fix .", + "lint": "yarn lint:headers && eslint .", + "lint:fix": "yarn lint:headers && eslint --fix .", + "lint:headers": "node scripts/check-duplicate-headers.cjs", "mobile-deploy": "node scripts/mobile-deploy-confirm.cjs both", "mobile-deploy:android": "node scripts/mobile-deploy-confirm.cjs android", "mobile-deploy:ios": "node scripts/mobile-deploy-confirm.cjs ios", @@ -52,7 +53,7 @@ "tag:release": "node scripts/tag.js release", "tag:remove": "node scripts/tag.js remove", "test": "jest --passWithNoTests && node --test scripts/tests/*.cjs", - "test:build": "yarn build:deps && yarn web:build && yarn types && yarn analyze:bundle:ios", + "test:build": "yarn build:deps && yarn types && yarn analyze:bundle:ios && yarn test", "test:coverage": "jest --coverage --passWithNoTests", "test:coverage:ci": "jest --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json", "test:e2e:android": "cd android && ./gradlew assembleDebug && cd .. && maestro test tests/e2e/launch.android.flow.yaml", @@ -177,6 +178,7 @@ "eslint-plugin-jest": "^28.11.1", "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-sort-exports": "^0.9.1", "jest": "^29.6.3", "prettier": "^3.5.3", "react-native-svg-transformer": "^1.5.0", diff --git a/app/scripts/check-duplicate-headers.cjs b/app/scripts/check-duplicate-headers.cjs new file mode 100644 index 000000000..5ecaff310 --- /dev/null +++ b/app/scripts/check-duplicate-headers.cjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const { glob } = require('glob'); + +const LICENSE_HEADER_PATTERN = /SPDX-License-Identifier:/; +const EXTENSIONS = ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx']; + +function checkFile(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const lines = content.split('\n'); + + const headerLines = lines + .map((line, index) => ({ line, index: index + 1 })) + .filter(({ line }) => LICENSE_HEADER_PATTERN.test(line)); + + if (headerLines.length > 1) { + console.error(`\nāŒ Multiple license headers found in ${filePath}:`); + headerLines.forEach(({ index }) => { + console.error(` Line ${index}`); + }); + return false; + } + + return true; +} + +function main() { + let hasErrors = false; + + // Get all relevant files + const patterns = EXTENSIONS.map(ext => path.join('src', ext)); + patterns.push(...EXTENSIONS.map(ext => path.join('tests', ext))); + patterns.push('*.ts', '*.tsx', '*.js', '*.jsx'); + + for (const pattern of patterns) { + const files = glob.sync(pattern, { + ignore: ['node_modules/**', 'dist/**', 'build/**', '**/*.d.ts'], + }); + + for (const file of files) { + if (!checkFile(file)) { + hasErrors = true; + } + } + } + + if (hasErrors) { + console.error( + '\nšŸ’” Fix: Remove duplicate license headers. Only keep the one at the top of each file.\n', + ); + process.exit(1); + } else { + console.log('āœ… No duplicate license headers found'); + } +} + +if (require.main === module) { + main(); +} + +module.exports = { checkFile }; diff --git a/app/scripts/test-e2e-local.sh b/app/scripts/test-e2e-local.sh index 784583cf4..fb11fa576 100755 --- a/app/scripts/test-e2e-local.sh +++ b/app/scripts/test-e2e-local.sh @@ -469,20 +469,19 @@ install_android_app() { log_info "Installing on emulator: $EMULATOR_ID" # Dynamically find the latest 'aapt' tool path and determine package name - # First try 'aapt' (classic tool) for package name extraction, then fall back to 'aapt2' with supported commands - AAPT_PATH=$(find "$ANDROID_HOME/build-tools" -type f -name "aapt" | sort -r | head -n 1) - if [ -n "$AAPT_PATH" ]; then - log_info "Using aapt to get package name from $APK_PATH..." - ACTUAL_PACKAGE=$("$AAPT_PATH" dump badging "$APK_PATH" 2>/dev/null | grep "package:" | sed -E "s/.*name='([^']+)'.*/\1/" | head -1) + # Prioritize 'aapt2' for reliability, then fall back to 'aapt'. + AAPT2_PATH=$(find "$ANDROID_HOME/build-tools" -type f -name "aapt2" | sort -r | head -n 1) + if [ -n "$AAPT2_PATH" ]; then + log_info "Using aapt2 to get package name from $APK_PATH..." + ACTUAL_PACKAGE=$("$AAPT2_PATH" dump packagename "$APK_PATH" 2>/dev/null | head -1) else - log_warning "aapt not found, trying aapt2..." - AAPT2_PATH=$(find "$ANDROID_HOME/build-tools" -type f -name "aapt2" | sort -r | head -n 1) - if [ -n "$AAPT2_PATH" ]; then - log_info "Found aapt2 at: $AAPT2_PATH" - # aapt2 doesn't support 'dump packagename', so we'll use 'dump xmltree' to parse the manifest - ACTUAL_PACKAGE=$("$AAPT2_PATH" dump xmltree "$APK_PATH" AndroidManifest.xml 2>/dev/null | grep -A1 "package=" | grep "A:" | sed -E "s/.*A: package=\"([^\"]+)\".*/\1/" | head -1) + log_warning "aapt2 not found, falling back to aapt..." + AAPT_PATH=$(find "$ANDROID_HOME/build-tools" -type f -name "aapt" | sort -r | head -n 1) + if [ -n "$AAPT_PATH" ]; then + log_info "Found aapt at: $AAPT_PATH" + ACTUAL_PACKAGE=$("$AAPT_PATH" dump badging "$APK_PATH" 2>/dev/null | grep "package:" | sed -E "s/.*name='([^']+)'.*/\1/" | head -1) else - log_error "Neither aapt nor aapt2 found in $ANDROID_HOME/build-tools" + log_error "Neither aapt2 nor aapt found in $ANDROID_HOME/build-tools" echo "Please ensure your Android build-tools are installed correctly." exit 1 fi diff --git a/app/src/RemoteConfig.shared.ts b/app/src/RemoteConfig.shared.ts index 49240d1d5..85736b0f8 100644 --- a/app/src/RemoteConfig.shared.ts +++ b/app/src/RemoteConfig.shared.ts @@ -1,19 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -// Shared types and constants for RemoteConfig - -export type FeatureFlagValue = string | boolean | number; - -export interface LocalOverride { - [key: string]: FeatureFlagValue; -} - -export const LOCAL_OVERRIDES_KEY = 'feature_flag_overrides'; - -export const defaultFlags: Record = { - aesop: false, -}; - export interface FeatureFlagInfo { key: string; remoteValue?: FeatureFlagValue; @@ -23,11 +9,11 @@ export interface FeatureFlagInfo { type: 'boolean' | 'string' | 'number'; } -// Shared interfaces for platform-specific implementations -export interface StorageBackend { - getItem(key: string): Promise; - setItem(key: string, value: string): Promise; - removeItem(key: string): Promise; +// Shared types and constants for RemoteConfig +export type FeatureFlagValue = string | boolean | number; + +export interface LocalOverride { + [key: string]: FeatureFlagValue; } export interface RemoteConfigBackend { @@ -43,70 +29,21 @@ export interface RemoteConfigBackend { fetchAndActivate(): Promise; } -// Helper function to detect and parse remote config values -export const getRemoteConfigValue = ( - remoteConfig: RemoteConfigBackend, - key: string, - defaultValue: FeatureFlagValue, -): FeatureFlagValue => { - const configValue = remoteConfig.getValue(key); +export interface StorageBackend { + getItem(key: string): Promise; + setItem(key: string, value: string): Promise; + removeItem(key: string): Promise; +} - if (typeof defaultValue === 'boolean') { - return configValue.asBoolean(); - } else if (typeof defaultValue === 'number') { - return configValue.asNumber(); - } else if (typeof defaultValue === 'string') { - return configValue.asString(); - } +export const LOCAL_OVERRIDES_KEY = 'feature_flag_overrides'; - // Fallback: try to infer type from the remote config value - const stringValue = configValue.asString(); - if (stringValue === 'true' || stringValue === 'false') { - return configValue.asBoolean(); - } - if (!Number.isNaN(Number(stringValue)) && stringValue !== '') { - return configValue.asNumber(); - } - return stringValue; -}; - -// Local override management -export const getLocalOverrides = async ( +export const clearAllLocalOverrides = async ( storage: StorageBackend, -): Promise => { - try { - const overrides = await storage.getItem(LOCAL_OVERRIDES_KEY); - if (!overrides) { - return {}; - } - return JSON.parse(overrides); - } catch (error) { - console.error('Failed to get local overrides:', error); - - // If JSON parsing fails, clear the corrupt data - if (error instanceof SyntaxError) { - try { - await storage.removeItem(LOCAL_OVERRIDES_KEY); - } catch (removeError) { - console.error('Failed to clear corrupt local overrides:', removeError); - } - } - - return {}; - } -}; - -export const setLocalOverride = async ( - storage: StorageBackend, - flag: string, - value: FeatureFlagValue, ): Promise => { try { - const overrides = await getLocalOverrides(storage); - overrides[flag] = value; - await storage.setItem(LOCAL_OVERRIDES_KEY, JSON.stringify(overrides)); + await storage.removeItem(LOCAL_OVERRIDES_KEY); } catch (error) { - console.error('Failed to set local override:', error); + console.error('Failed to clear all local overrides:', error); } }; @@ -123,54 +60,9 @@ export const clearLocalOverride = async ( } }; -export const clearAllLocalOverrides = async ( - storage: StorageBackend, -): Promise => { - try { - await storage.removeItem(LOCAL_OVERRIDES_KEY); - } catch (error) { - console.error('Failed to clear all local overrides:', error); - } -}; - -export const initRemoteConfig = async ( - remoteConfig: RemoteConfigBackend, -): Promise => { - await remoteConfig.setDefaults(defaultFlags); - await remoteConfig.setConfigSettings({ - minimumFetchIntervalMillis: __DEV__ ? 0 : 3600000, - }); - try { - await remoteConfig.fetchAndActivate(); - } catch (err) { - console.log('Remote config fetch failed', err); - } -}; - -export const getFeatureFlag = async ( - remoteConfig: RemoteConfigBackend, - storage: StorageBackend, - flag: string, - defaultValue: T, -): Promise => { - try { - // Check local overrides first - const localOverrides = await getLocalOverrides(storage); - if (Object.prototype.hasOwnProperty.call(localOverrides, flag)) { - return localOverrides[flag] as T; - } - - // Return default value for string flags - if (typeof defaultValue === 'string') { - return defaultValue; - } - - // Fall back to remote config for number and boolean flags - return getRemoteConfigValue(remoteConfig, flag, defaultValue) as T; - } catch (error) { - console.error('Failed to get feature flag:', error); - return defaultValue; - } +// Shared interfaces for platform-specific implementations +export const defaultFlags: Record = { + aesop: false, }; export const getAllFeatureFlags = async ( @@ -256,6 +148,99 @@ export const getAllFeatureFlags = async ( } }; +export const getFeatureFlag = async ( + remoteConfig: RemoteConfigBackend, + storage: StorageBackend, + flag: string, + defaultValue: T, +): Promise => { + try { + // Check local overrides first + const localOverrides = await getLocalOverrides(storage); + if (Object.prototype.hasOwnProperty.call(localOverrides, flag)) { + return localOverrides[flag] as T; + } + + // Return default value for string flags + if (typeof defaultValue === 'string') { + return defaultValue; + } + + // Fall back to remote config for number and boolean flags + return getRemoteConfigValue(remoteConfig, flag, defaultValue) as T; + } catch (error) { + console.error('Failed to get feature flag:', error); + return defaultValue; + } +}; + +// Local override management +export const getLocalOverrides = async ( + storage: StorageBackend, +): Promise => { + try { + const overrides = await storage.getItem(LOCAL_OVERRIDES_KEY); + if (!overrides) { + return {}; + } + return JSON.parse(overrides); + } catch (error) { + console.error('Failed to get local overrides:', error); + + // If JSON parsing fails, clear the corrupt data + if (error instanceof SyntaxError) { + try { + await storage.removeItem(LOCAL_OVERRIDES_KEY); + } catch (removeError) { + console.error('Failed to clear corrupt local overrides:', removeError); + } + } + + return {}; + } +}; + +// Helper function to detect and parse remote config values +export const getRemoteConfigValue = ( + remoteConfig: RemoteConfigBackend, + key: string, + defaultValue: FeatureFlagValue, +): FeatureFlagValue => { + const configValue = remoteConfig.getValue(key); + + if (typeof defaultValue === 'boolean') { + return configValue.asBoolean(); + } else if (typeof defaultValue === 'number') { + return configValue.asNumber(); + } else if (typeof defaultValue === 'string') { + return configValue.asString(); + } + + // Fallback: try to infer type from the remote config value + const stringValue = configValue.asString(); + if (stringValue === 'true' || stringValue === 'false') { + return configValue.asBoolean(); + } + if (!Number.isNaN(Number(stringValue)) && stringValue !== '') { + return configValue.asNumber(); + } + return stringValue; +}; + +export const initRemoteConfig = async ( + remoteConfig: RemoteConfigBackend, +): Promise => { + await remoteConfig.setDefaults(defaultFlags); + await remoteConfig.setConfigSettings({ + minimumFetchIntervalMillis: __DEV__ ? 0 : 3600000, + }); + try { + await remoteConfig.fetchAndActivate(); + } catch (err) { + console.log('Remote config fetch failed', err); + } +}; + export const refreshRemoteConfig = async ( remoteConfig: RemoteConfigBackend, ): Promise => { @@ -265,3 +250,17 @@ export const refreshRemoteConfig = async ( console.log('Remote config refresh failed', err); } }; + +export const setLocalOverride = async ( + storage: StorageBackend, + flag: string, + value: FeatureFlagValue, +): Promise => { + try { + const overrides = await getLocalOverrides(storage); + overrides[flag] = value; + await storage.setItem(LOCAL_OVERRIDES_KEY, JSON.stringify(overrides)); + } catch (error) { + console.error('Failed to set local override:', error); + } +}; diff --git a/app/src/RemoteConfig.ts b/app/src/RemoteConfig.ts index 53ac0be44..70e65e931 100644 --- a/app/src/RemoteConfig.ts +++ b/app/src/RemoteConfig.ts @@ -1,22 +1,24 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import AsyncStorage from '@react-native-async-storage/async-storage'; -import remoteConfig from '@react-native-firebase/remote-config'; - +import type { + FeatureFlagValue, + RemoteConfigBackend, + StorageBackend, +} from './RemoteConfig.shared'; import { clearAllLocalOverrides as clearAllLocalOverridesShared, clearLocalOverride as clearLocalOverrideShared, - FeatureFlagValue, getAllFeatureFlags as getAllFeatureFlagsShared, getFeatureFlag as getFeatureFlagShared, getLocalOverrides as getLocalOverridesShared, initRemoteConfig as initRemoteConfigShared, refreshRemoteConfig as refreshRemoteConfigShared, - RemoteConfigBackend, setLocalOverride as setLocalOverrideShared, - StorageBackend, } from './RemoteConfig.shared'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import remoteConfig from '@react-native-firebase/remote-config'; + // Mobile-specific storage backend using AsyncStorage const mobileStorageBackend: StorageBackend = { getItem: async (key: string): Promise => { @@ -49,17 +51,17 @@ const mobileRemoteConfigBackend: RemoteConfigBackend = { }, }; -// Export the shared functions with mobile-specific backends -export const getLocalOverrides = () => - getLocalOverridesShared(mobileStorageBackend); -export const setLocalOverride = (flag: string, value: FeatureFlagValue) => - setLocalOverrideShared(mobileStorageBackend, flag, value); -export const clearLocalOverride = (flag: string) => - clearLocalOverrideShared(mobileStorageBackend, flag); +export type { FeatureFlagValue } from './RemoteConfig.shared'; + export const clearAllLocalOverrides = () => clearAllLocalOverridesShared(mobileStorageBackend); -export const initRemoteConfig = () => - initRemoteConfigShared(mobileRemoteConfigBackend); + +export const clearLocalOverride = (flag: string) => + clearLocalOverrideShared(mobileStorageBackend, flag); + +export const getAllFeatureFlags = () => + getAllFeatureFlagsShared(mobileRemoteConfigBackend, mobileStorageBackend); +// Export the shared functions with mobile-specific backends export const getFeatureFlag = ( flag: string, defaultValue: T, @@ -70,10 +72,13 @@ export const getFeatureFlag = ( flag, defaultValue, ); -export const getAllFeatureFlags = () => - getAllFeatureFlagsShared(mobileRemoteConfigBackend, mobileStorageBackend); +export const getLocalOverrides = () => + getLocalOverridesShared(mobileStorageBackend); +export const initRemoteConfig = () => + initRemoteConfigShared(mobileRemoteConfigBackend); +// Re-export types for convenience export const refreshRemoteConfig = () => refreshRemoteConfigShared(mobileRemoteConfigBackend); -// Re-export types for convenience -export type { FeatureFlagValue } from './RemoteConfig.shared'; +export const setLocalOverride = (flag: string, value: FeatureFlagValue) => + setLocalOverrideShared(mobileStorageBackend, flag, value); diff --git a/app/src/RemoteConfig.web.ts b/app/src/RemoteConfig.web.ts index f33240afe..31ea722d6 100644 --- a/app/src/RemoteConfig.web.ts +++ b/app/src/RemoteConfig.web.ts @@ -3,18 +3,20 @@ // Web-compatible version using LocalStorage and Firebase Web SDK // This file provides the same API as RemoteConfig.ts but for web environments +import type { + FeatureFlagValue, + RemoteConfigBackend, + StorageBackend, +} from './RemoteConfig.shared'; import { clearAllLocalOverrides as clearAllLocalOverridesShared, clearLocalOverride as clearLocalOverrideShared, - FeatureFlagValue, getAllFeatureFlags as getAllFeatureFlagsShared, getFeatureFlag as getFeatureFlagShared, getLocalOverrides as getLocalOverridesShared, initRemoteConfig as initRemoteConfigShared, refreshRemoteConfig as refreshRemoteConfigShared, - RemoteConfigBackend, setLocalOverride as setLocalOverrideShared, - StorageBackend, } from './RemoteConfig.shared'; // Web-specific storage backend using LocalStorage @@ -85,17 +87,17 @@ class MockFirebaseRemoteConfig implements RemoteConfigBackend { const webRemoteConfigBackend: RemoteConfigBackend = new MockFirebaseRemoteConfig(); -// Export the shared functions with web-specific backends -export const getLocalOverrides = () => - getLocalOverridesShared(webStorageBackend); -export const setLocalOverride = (flag: string, value: FeatureFlagValue) => - setLocalOverrideShared(webStorageBackend, flag, value); -export const clearLocalOverride = (flag: string) => - clearLocalOverrideShared(webStorageBackend, flag); +export type { FeatureFlagValue } from './RemoteConfig.shared'; + export const clearAllLocalOverrides = () => clearAllLocalOverridesShared(webStorageBackend); -export const initRemoteConfig = () => - initRemoteConfigShared(webRemoteConfigBackend); + +export const clearLocalOverride = (flag: string) => + clearLocalOverrideShared(webStorageBackend, flag); + +export const getAllFeatureFlags = () => + getAllFeatureFlagsShared(webRemoteConfigBackend, webStorageBackend); +// Export the shared functions with web-specific backends export const getFeatureFlag = ( flag: string, defaultValue: T, @@ -106,10 +108,13 @@ export const getFeatureFlag = ( flag, defaultValue, ); -export const getAllFeatureFlags = () => - getAllFeatureFlagsShared(webRemoteConfigBackend, webStorageBackend); +export const getLocalOverrides = () => + getLocalOverridesShared(webStorageBackend); +export const initRemoteConfig = () => + initRemoteConfigShared(webRemoteConfigBackend); +// Re-export types for convenience export const refreshRemoteConfig = () => refreshRemoteConfigShared(webRemoteConfigBackend); -// Re-export types for convenience -export type { FeatureFlagValue } from './RemoteConfig.shared'; +export const setLocalOverride = (flag: string, value: FeatureFlagValue) => + setLocalOverrideShared(webStorageBackend, flag, value); diff --git a/app/src/Segment.ts b/app/src/Segment.ts index 694124db9..cca906432 100644 --- a/app/src/Segment.ts +++ b/app/src/Segment.ts @@ -1,14 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import '@ethersproject/shims'; - import { SEGMENT_KEY } from '@env'; +import type { SegmentEvent } from '@segment/analytics-react-native'; import { BackgroundFlushPolicy, createClient, EventPlugin, PluginType, - SegmentEvent, } from '@segment/analytics-react-native'; let segmentClient: ReturnType | null = null; diff --git a/app/src/Sentry.ts b/app/src/Sentry.ts index 785fbe3ad..b9f274bd5 100644 --- a/app/src/Sentry.ts +++ b/app/src/Sentry.ts @@ -3,7 +3,29 @@ import { SENTRY_DSN } from '@env'; import * as Sentry from '@sentry/react-native'; -export const isSentryDisabled = !SENTRY_DSN; +export const captureException = ( + error: Error, + context?: Record, +) => { + if (isSentryDisabled) { + return; + } + Sentry.captureException(error, { + extra: context, + }); +}; + +export const captureMessage = ( + message: string, + context?: Record, +) => { + if (isSentryDisabled) { + return; + } + Sentry.captureMessage(message, { + extra: context, + }); +}; export const initSentry = () => { if (isSentryDisabled) { @@ -32,29 +54,7 @@ export const initSentry = () => { return Sentry; }; -export const captureException = ( - error: Error, - context?: Record, -) => { - if (isSentryDisabled) { - return; - } - Sentry.captureException(error, { - extra: context, - }); -}; - -export const captureMessage = ( - message: string, - context?: Record, -) => { - if (isSentryDisabled) { - return; - } - Sentry.captureMessage(message, { - extra: context, - }); -}; +export const isSentryDisabled = !SENTRY_DSN; export const wrapWithSentry = (App: React.ComponentType) => { return isSentryDisabled ? App : Sentry.wrap(App); diff --git a/app/src/Sentry.web.ts b/app/src/Sentry.web.ts index c2534eb9c..b82082f7f 100644 --- a/app/src/Sentry.web.ts +++ b/app/src/Sentry.web.ts @@ -3,7 +3,29 @@ import { SENTRY_DSN } from '@env'; import * as Sentry from '@sentry/react'; -export const isSentryDisabled = !SENTRY_DSN; +export const captureException = ( + error: Error, + context?: Record, +) => { + if (isSentryDisabled) { + return; + } + Sentry.captureException(error, { + extra: context, + }); +}; + +export const captureMessage = ( + message: string, + context?: Record, +) => { + if (isSentryDisabled) { + return; + } + Sentry.captureMessage(message, { + extra: context, + }); +}; export const initSentry = () => { if (isSentryDisabled) { @@ -31,29 +53,7 @@ export const initSentry = () => { return Sentry; }; -export const captureException = ( - error: Error, - context?: Record, -) => { - if (isSentryDisabled) { - return; - } - Sentry.captureException(error, { - extra: context, - }); -}; - -export const captureMessage = ( - message: string, - context?: Record, -) => { - if (isSentryDisabled) { - return; - } - Sentry.captureMessage(message, { - extra: context, - }); -}; +export const isSentryDisabled = !SENTRY_DSN; export const wrapWithSentry = (App: React.ComponentType) => { return isSentryDisabled ? App : Sentry.withProfiler(App); diff --git a/app/src/assets/animations/loader.ts b/app/src/assets/animations/loader.ts index f55f6a6ed..1654d0ed0 100644 --- a/app/src/assets/animations/loader.ts +++ b/app/src/assets/animations/loader.ts @@ -1,4 +1,4 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -export const loadPassportAnimation = () => import('./passport_verify.json'); export const loadMiscAnimation = () => import('./loading/misc.json'); +export const loadPassportAnimation = () => import('./passport_verify.json'); diff --git a/app/src/components/Disclosures.tsx b/app/src/components/Disclosures.tsx index eb34bd31e..6a0de2acb 100644 --- a/app/src/components/Disclosures.tsx +++ b/app/src/components/Disclosures.tsx @@ -1,11 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import type { Country3LetterCode } from '@selfxyz/common/constants'; -import { countryCodes } from '@selfxyz/common/constants/core'; -import type { SelfAppDisclosureConfig } from '@selfxyz/common/utils'; import React from 'react'; import { XStack, YStack } from 'tamagui'; +import type { Country3LetterCode } from '@selfxyz/common/constants'; +import { countryCodes } from '@selfxyz/common/constants'; +import type { SelfAppDisclosureConfig } from '@selfxyz/common/utils'; + import { BodyText } from '../components/typography/BodyText'; import CheckMark from '../images/icons/checkmark.svg'; import { slate200, slate500 } from '../utils/colors'; diff --git a/app/src/components/Mnemonic.tsx b/app/src/components/Mnemonic.tsx index 50925c36c..803a3636d 100644 --- a/app/src/components/Mnemonic.tsx +++ b/app/src/components/Mnemonic.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import Clipboard from '@react-native-clipboard/clipboard'; import React, { useCallback, useState } from 'react'; import { Button, Text, XStack, YStack } from 'tamagui'; @@ -16,6 +15,8 @@ import { } from '../utils/colors'; import { confirmTap } from '../utils/haptic'; +import Clipboard from '@react-native-clipboard/clipboard'; + interface MnemonicProps { words?: string[]; onRevealWords?: () => Promise; diff --git a/app/src/components/NavBar/BaseNavBar.tsx b/app/src/components/NavBar/BaseNavBar.tsx index ae818bdc6..1c1b010c8 100644 --- a/app/src/components/NavBar/BaseNavBar.tsx +++ b/app/src/components/NavBar/BaseNavBar.tsx @@ -1,19 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { ChevronLeft, X } from '@tamagui/lucide-icons'; import React, { useMemo } from 'react'; -import { SystemBars, SystemBarStyle } from 'react-native-edge-to-edge'; -import { - Button, - TextProps, - View, - ViewProps, - XStack, - XStackProps, -} from 'tamagui'; +import type { SystemBarStyle } from 'react-native-edge-to-edge'; +import { SystemBars } from 'react-native-edge-to-edge'; +import type { TextProps, ViewProps, XStackProps } from 'tamagui'; +import { Button, View, XStack } from 'tamagui'; import { Title } from '../typography/Title'; +import { ChevronLeft, X } from '@tamagui/lucide-icons'; + interface NavBarProps extends XStackProps { children: React.ReactNode; backgroundColor?: string; @@ -78,22 +74,6 @@ export const LeftAction: React.FC = ({ return {children}; }; -export const RightAction: React.FC = ({ - component, - onPress, - ...props -}) => { - if (!component) { - return null; - } - - return ( - - {component} - - ); -}; - const NavBarTitle: React.FC = ({ children, ...props }) => { if (!children) { return null; @@ -128,6 +108,22 @@ const Container: React.FC = ({ ); }; +export const RightAction: React.FC = ({ + component, + onPress, + ...props +}) => { + if (!component) { + return null; + } + + return ( + + {component} + + ); +}; + export const NavBar = { Container, Title: NavBarTitle, diff --git a/app/src/components/NavBar/DefaultNavBar.tsx b/app/src/components/NavBar/DefaultNavBar.tsx index 1e6618435..0c526c435 100644 --- a/app/src/components/NavBar/DefaultNavBar.tsx +++ b/app/src/components/NavBar/DefaultNavBar.tsx @@ -1,15 +1,16 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackHeaderProps } from '@react-navigation/native-stack'; import React from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { TextStyle, ViewStyle } from 'tamagui'; +import type { TextStyle, ViewStyle } from 'tamagui'; import { white } from '../../utils/colors'; import { extraYPadding } from '../../utils/constants'; import { buttonTap } from '../../utils/haptic'; import { NavBar } from './BaseNavBar'; +import type { NativeStackHeaderProps } from '@react-navigation/native-stack'; + export const DefaultNavBar = (props: NativeStackHeaderProps) => { const { goBack, canGoBack } = props.navigation; const { options } = props; diff --git a/app/src/components/NavBar/HomeNavBar.tsx b/app/src/components/NavBar/HomeNavBar.tsx index b25830c57..96d294fdf 100644 --- a/app/src/components/NavBar/HomeNavBar.tsx +++ b/app/src/components/NavBar/HomeNavBar.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackHeaderProps } from '@react-navigation/native-stack'; import React from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Button } from 'tamagui'; @@ -12,6 +11,8 @@ import { extraYPadding } from '../../utils/constants'; import { buttonTap } from '../../utils/haptic'; import { NavBar } from './BaseNavBar'; +import type { NativeStackHeaderProps } from '@react-navigation/native-stack'; + export const HomeNavBar = (props: NativeStackHeaderProps) => { const insets = useSafeAreaInsets(); return ( diff --git a/app/src/components/NavBar/ProgressNavBar.tsx b/app/src/components/NavBar/ProgressNavBar.tsx index cb3227007..8e06fc300 100644 --- a/app/src/components/NavBar/ProgressNavBar.tsx +++ b/app/src/components/NavBar/ProgressNavBar.tsx @@ -1,17 +1,19 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { - NativeStackHeaderProps, - NativeStackNavigationOptions, -} from '@react-navigation/native-stack'; import React from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { TextStyle, ViewStyle, XStack, YStack } from 'tamagui'; +import type { TextStyle, ViewStyle } from 'tamagui'; +import { XStack, YStack } from 'tamagui'; import { cyan300, slate200, white } from '../../utils/colors'; import { buttonTap } from '../../utils/haptic'; import { NavBar } from './BaseNavBar'; +import type { + NativeStackHeaderProps, + NativeStackNavigationOptions, +} from '@react-navigation/native-stack'; + interface ProgressNavBarProps extends NativeStackHeaderProps { currentStep?: number; totalSteps?: number; diff --git a/app/src/components/TextsContainer.tsx b/app/src/components/TextsContainer.tsx index 5fbf69af6..3b68aa7d4 100644 --- a/app/src/components/TextsContainer.tsx +++ b/app/src/components/TextsContainer.tsx @@ -1,7 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React from 'react'; -import { StyleSheet, View, ViewStyle } from 'react-native'; +import type { ViewStyle } from 'react-native'; +import { StyleSheet, View } from 'react-native'; interface TextsContainerProps { children: React.ReactNode; diff --git a/app/src/components/buttons/AbstractButton.tsx b/app/src/components/buttons/AbstractButton.tsx index 1f6dcdfef..ff9a925fc 100644 --- a/app/src/components/buttons/AbstractButton.tsx +++ b/app/src/components/buttons/AbstractButton.tsx @@ -1,8 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React from 'react'; -import { Platform, StyleSheet, ViewStyle } from 'react-native'; -import { Button, Text, ViewProps } from 'tamagui'; +import type { ViewStyle } from 'react-native'; +import { Platform, StyleSheet } from 'react-native'; +import type { ViewProps } from 'tamagui'; +import { Button, Text } from 'tamagui'; import { shouldShowAesopRedesign } from '../../hooks/useAesopRedesign'; import analytics from '../../utils/analytics'; diff --git a/app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx b/app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx index edaafe9df..cd599ca22 100644 --- a/app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx +++ b/app/src/components/buttons/HeldPrimaryButtonProveScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useMachine } from '@xstate/react'; import React, { useEffect } from 'react'; import { ActivityIndicator, View } from 'react-native'; import { assign, createMachine } from 'xstate'; @@ -10,6 +9,8 @@ import { black } from '../../utils/colors'; import Description from '../typography/Description'; import { HeldPrimaryButton } from './PrimaryButtonLongHold'; +import { useMachine } from '@xstate/react'; + interface HeldPrimaryButtonProveScreenProps { onVerify: () => void; selectedAppSessionId: string | undefined | null; diff --git a/app/src/components/buttons/PrimaryButton.tsx b/app/src/components/buttons/PrimaryButton.tsx index c47eef97d..1710e4676 100644 --- a/app/src/components/buttons/PrimaryButton.tsx +++ b/app/src/components/buttons/PrimaryButton.tsx @@ -4,7 +4,8 @@ import React from 'react'; import { amber50, black, slate300, white } from '../../utils/colors'; import { normalizeBorderWidth } from '../../utils/styleUtils'; -import AbstractButton, { ButtonProps } from './AbstractButton'; +import type { ButtonProps } from './AbstractButton'; +import AbstractButton from './AbstractButton'; export function PrimaryButton({ children, ...props }: ButtonProps) { const { borderWidth, ...restProps } = props; diff --git a/app/src/components/buttons/PrimaryButtonLongHold.shared.ts b/app/src/components/buttons/PrimaryButtonLongHold.shared.ts index 533e3a33f..f3023ee0a 100644 --- a/app/src/components/buttons/PrimaryButtonLongHold.shared.ts +++ b/app/src/components/buttons/PrimaryButtonLongHold.shared.ts @@ -1,13 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { ButtonProps } from './AbstractButton'; - -export type RGBA = `rgba(${number}, ${number}, ${number}, ${number})`; - -export const ACTION_TIMER = 600; // time in ms -//slate400 to slate800 but in rgb -export const COLORS: RGBA[] = ['rgba(30, 41, 59, 0.3)', 'rgba(30, 41, 59, 1)']; +import type { ButtonProps } from './AbstractButton'; export interface HeldPrimaryButtonProps extends ButtonProps { onLongPress: () => void; } + +export type RGBA = `rgba(${number}, ${number}, ${number}, ${number})`; // time in ms +//slate400 to slate800 but in rgb +export const ACTION_TIMER = 600; + +export const COLORS: RGBA[] = ['rgba(30, 41, 59, 0.3)', 'rgba(30, 41, 59, 1)']; diff --git a/app/src/components/buttons/PrimaryButtonLongHold.tsx b/app/src/components/buttons/PrimaryButtonLongHold.tsx index e12562e03..0a383c876 100644 --- a/app/src/components/buttons/PrimaryButtonLongHold.tsx +++ b/app/src/components/buttons/PrimaryButtonLongHold.tsx @@ -1,19 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React, { useEffect, useState } from 'react'; -import { - Animated, - LayoutChangeEvent, - StyleSheet, - useAnimatedValue, -} from 'react-native'; +import type { LayoutChangeEvent } from 'react-native'; +import { Animated, StyleSheet, useAnimatedValue } from 'react-native'; import { PrimaryButton } from './PrimaryButton'; -import { - ACTION_TIMER, - COLORS, - HeldPrimaryButtonProps, -} from './PrimaryButtonLongHold.shared'; +import type { HeldPrimaryButtonProps } from './PrimaryButtonLongHold.shared'; +import { ACTION_TIMER, COLORS } from './PrimaryButtonLongHold.shared'; export function HeldPrimaryButton({ children, diff --git a/app/src/components/buttons/PrimaryButtonLongHold.web.tsx b/app/src/components/buttons/PrimaryButtonLongHold.web.tsx index 509b782a5..2a7e8d24b 100644 --- a/app/src/components/buttons/PrimaryButtonLongHold.web.tsx +++ b/app/src/components/buttons/PrimaryButtonLongHold.web.tsx @@ -1,16 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React, { useEffect, useState } from 'react'; -import { LayoutChangeEvent } from 'react-native'; +import type { LayoutChangeEvent } from 'react-native'; // Tamagui imports for web import { AnimatePresence, YStack } from 'tamagui'; import { PrimaryButton } from './PrimaryButton'; -import { - ACTION_TIMER, - COLORS, - HeldPrimaryButtonProps, -} from './PrimaryButtonLongHold.shared'; +import type { HeldPrimaryButtonProps } from './PrimaryButtonLongHold.shared'; +import { ACTION_TIMER, COLORS } from './PrimaryButtonLongHold.shared'; export function HeldPrimaryButton({ children, diff --git a/app/src/components/buttons/SecondaryButton.tsx b/app/src/components/buttons/SecondaryButton.tsx index ed5db7685..980e6721b 100644 --- a/app/src/components/buttons/SecondaryButton.tsx +++ b/app/src/components/buttons/SecondaryButton.tsx @@ -4,7 +4,8 @@ import React from 'react'; import { slate200, slate300, slate500, white } from '../../utils/colors'; import { normalizeBorderWidth } from '../../utils/styleUtils'; -import AbstractButton, { ButtonProps } from './AbstractButton'; +import type { ButtonProps } from './AbstractButton'; +import AbstractButton from './AbstractButton'; export function SecondaryButton({ children, ...props }: ButtonProps) { const { borderWidth, ...restProps } = props; diff --git a/app/src/components/native/PassportCamera.tsx b/app/src/components/native/PassportCamera.tsx index 3bfa55284..250dd0d1a 100644 --- a/app/src/components/native/PassportCamera.tsx +++ b/app/src/components/native/PassportCamera.tsx @@ -1,12 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React, { useCallback } from 'react'; -import { - NativeSyntheticEvent, - PixelRatio, - Platform, - requireNativeComponent, -} from 'react-native'; +import type { NativeSyntheticEvent } from 'react-native'; +import { PixelRatio, Platform, requireNativeComponent } from 'react-native'; import { extractMRZInfo } from '../../utils/utils'; import { RCTFragment } from './RCTFragment'; diff --git a/app/src/components/native/PassportCamera.web.tsx b/app/src/components/native/PassportCamera.web.tsx index a6eee2d51..7140363a5 100644 --- a/app/src/components/native/PassportCamera.web.tsx +++ b/app/src/components/native/PassportCamera.web.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect } from 'react'; -import { extractMRZInfo } from '../../utils/utils'; +import type { extractMRZInfo } from '../../utils/utils'; // TODO: Web find a lightweight ocr or mrz scanner. diff --git a/app/src/components/native/QRCodeScanner.tsx b/app/src/components/native/QRCodeScanner.tsx index 4e8695c88..a82811132 100644 --- a/app/src/components/native/QRCodeScanner.tsx +++ b/app/src/components/native/QRCodeScanner.tsx @@ -1,12 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React, { useCallback } from 'react'; -import { - NativeSyntheticEvent, - PixelRatio, - Platform, - requireNativeComponent, -} from 'react-native'; +import type { NativeSyntheticEvent } from 'react-native'; +import { PixelRatio, Platform, requireNativeComponent } from 'react-native'; import { RCTFragment } from './RCTFragment'; diff --git a/app/src/components/native/RCTFragment.tsx b/app/src/components/native/RCTFragment.tsx index 30a7c1e9a..60502c4bd 100644 --- a/app/src/components/native/RCTFragment.tsx +++ b/app/src/components/native/RCTFragment.tsx @@ -1,12 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React, { useEffect, useRef } from 'react'; -import { - findNodeHandle, +import type { NativeSyntheticEvent, requireNativeComponent, - UIManager, } from 'react-native'; +import { findNodeHandle, UIManager } from 'react-native'; + +export interface FragmentProps { + isMounted: boolean; +} export interface RCTFragmentViewManagerProps { RCTFragmentViewManager: ReturnType; @@ -25,10 +28,6 @@ export interface RCTFragmentViewManagerProps { ) => void; } -export interface FragmentProps { - isMounted: boolean; -} - function dispatchCommand( fragmentComponentName: string, viewId: number, diff --git a/app/src/components/typography/Additional.tsx b/app/src/components/typography/Additional.tsx index c9ad225c0..f129c0f51 100644 --- a/app/src/components/typography/Additional.tsx +++ b/app/src/components/typography/Additional.tsx @@ -1,7 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React from 'react'; -import { StyleSheet, Text, TextProps } from 'react-native'; +import type { TextProps } from 'react-native'; +import { StyleSheet, Text } from 'react-native'; import { shouldShowAesopRedesign } from '../../hooks/useAesopRedesign'; import { slate400 } from '../../utils/colors'; diff --git a/app/src/components/typography/Caution.tsx b/app/src/components/typography/Caution.tsx index b4b73ef5e..e96544148 100644 --- a/app/src/components/typography/Caution.tsx +++ b/app/src/components/typography/Caution.tsx @@ -1,7 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import React from 'react'; -import { StyleSheet, Text, TextProps } from 'react-native'; +import type { TextProps } from 'react-native'; +import { StyleSheet, Text } from 'react-native'; import { slate700 } from '../../utils/colors'; import { dinot } from '../../utils/fonts'; diff --git a/app/src/components/typography/Description.tsx b/app/src/components/typography/Description.tsx index 39f35045c..3b75c0f18 100644 --- a/app/src/components/typography/Description.tsx +++ b/app/src/components/typography/Description.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { StyleSheet } from 'react-native'; -import { Text, TextProps } from 'tamagui'; +import type { TextProps } from 'tamagui'; +import { Text } from 'tamagui'; import { shouldShowAesopRedesign } from '../../hooks/useAesopRedesign'; import { slate500 } from '../../utils/colors'; diff --git a/app/src/components/typography/Title.tsx b/app/src/components/typography/Title.tsx index 3b84b8bc1..50d281a1d 100644 --- a/app/src/components/typography/Title.tsx +++ b/app/src/components/typography/Title.tsx @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { StyleProp, TextStyle } from 'react-native'; +import type { StyleProp, TextStyle } from 'react-native'; import { styled, Text } from 'tamagui'; import { advercase } from '../../utils/fonts'; diff --git a/app/src/consts/analytics.ts b/app/src/consts/analytics.ts index 37c13e5b7..ffa6aaf92 100644 --- a/app/src/consts/analytics.ts +++ b/app/src/consts/analytics.ts @@ -9,13 +9,6 @@ export const AppEvents = { UPDATE_STARTED: 'App: Update Started', }; -export const NotificationEvents = { - BACKGROUND_NOTIFICATION_OPENED: - 'Notification: Background Notification Opened', - COLD_START_NOTIFICATION_OPENED: - 'Notification: Cold Start Notification Opened', -}; - export const AuthEvents = { AUTHENTICATION_TIMEOUT: 'Auth: Authentication Timeout', BIOMETRIC_AUTH_FAILED: 'Auth: Biometric Auth Failed', @@ -31,6 +24,60 @@ export const AuthEvents = { MNEMONIC_RESTORE_SUCCESS: 'Auth: Mnemonic Restore Success', }; +export const BackupEvents = { + ACCOUNT_RECOVERY_COMPLETED: 'Backup: Account Recovery Completed', + ACCOUNT_RECOVERY_STARTED: 'Backup: Account Recovery Started', + ACCOUNT_VERIFICATION_COMPLETED: 'Backup: Account Verification Completed', + CLOUD_BACKUP_CANCELLED: 'Backup: Cloud Backup Cancelled', + CLOUD_BACKUP_CONTINUE: 'Backup: Cloud Backup Continue', + CLOUD_BACKUP_DISABLED_DONE: 'Backup: Cloud Backup Disabled Done', + CLOUD_BACKUP_DISABLE_STARTED: 'Backup: Cloud Backup Disable Started', + CLOUD_BACKUP_ENABLED_DONE: 'Backup: Cloud Backup Enabled Done', + CLOUD_BACKUP_ENABLE_STARTED: 'Backup: Cloud Backup Enable Started', + CLOUD_BACKUP_STARTED: 'Backup: Cloud Backup Started', + CLOUD_RESTORE_FAILED_PASSPORT_NOT_REGISTERED: + 'Backup: Cloud Restore Failed: Passport Not Registered', + CLOUD_RESTORE_FAILED_UNKNOWN: 'Backup: Cloud Restore Failed: Unknown Error', + CLOUD_RESTORE_SUCCESS: 'Backup: Cloud Restore Success', + CREATE_NEW_ACCOUNT: 'Backup: Create New Account', + MANUAL_RECOVERY_SELECTED: 'Backup: Manual Recovery Selected', +}; + +export const DocumentEvents = { + ADD_NEW_MOCK_SELECTED: 'Document: Add New Document via Mock', + ADD_NEW_SCAN_SELECTED: 'Document: Add New Document via Scan', + DOCUMENT_DELETED: 'Document: Document Deleted', + DOCUMENT_SELECTED: 'Document: Document Selected', + DOCUMENTS_FETCHED: 'Document: Documents Fetched', + MANAGE_SCREEN_OPENED: 'Document: Manage Documents Screen Opened', + NO_DOCUMENTS_FOUND: 'Document: No Documents Found', + PASSPORT_INFO_OPENED: 'Document: Passport Info Screen Opened', + PASSPORT_METADATA_LOADED: 'Document: Passport Metadata Loaded', + VALIDATE_DOCUMENT_FAILED: 'Document: Validate Document Failed', + DOCUMENT_VALIDATED: 'Document: Document Validated', +}; + +export const MockDataEvents = { + CANCEL_GENERATION: 'Mock Data: Cancel Generation', + CREATE_DEEP_LINK: 'Mock Data: Create Deep Link', + DECREASE_EXPIRY_YEARS: 'Mock Data: Decrease Expiry Years', + ENABLE_ADVANCED_MODE: 'Mock Data: Enable Advanced Mode', + GENERATE_DATA: 'Mock Data: Generate Data', + INCREASE_EXPIRY_YEARS: 'Mock Data: Increase Expiry Years', + OPEN_ALGORITHM_SELECTION: 'Mock Data: Open Algorithm Selection', + OPEN_COUNTRY_SELECTION: 'Mock Data: Open Country Selection', + SELECT_ALGORITHM: 'Mock Data: Select Algorithm', + SELECT_COUNTRY: 'Mock Data: Select Country', + TOGGLE_OFAC_LIST: 'Mock Data: Toggle OFAC List', +}; + +export const NotificationEvents = { + BACKGROUND_NOTIFICATION_OPENED: + 'Notification: Background Notification Opened', + COLD_START_NOTIFICATION_OPENED: + 'Notification: Cold Start Notification Opened', +}; + export const PassportEvents = { CAMERA_SCAN_CANCELLED: 'Passport: Camera Scan Cancelled', CAMERA_SCAN_FAILED: 'Passport: Camera Scan Failed', @@ -116,50 +163,3 @@ export const SettingsEvents = { CONNECTION_MODAL_OPENED: 'Settings: Connection Modal Opened', CONNECTION_SETTINGS_OPENED: 'Settings: Connection Settings Opened', }; - -export const BackupEvents = { - ACCOUNT_RECOVERY_COMPLETED: 'Backup: Account Recovery Completed', - ACCOUNT_RECOVERY_STARTED: 'Backup: Account Recovery Started', - ACCOUNT_VERIFICATION_COMPLETED: 'Backup: Account Verification Completed', - CLOUD_BACKUP_CANCELLED: 'Backup: Cloud Backup Cancelled', - CLOUD_BACKUP_CONTINUE: 'Backup: Cloud Backup Continue', - CLOUD_BACKUP_DISABLED_DONE: 'Backup: Cloud Backup Disabled Done', - CLOUD_BACKUP_DISABLE_STARTED: 'Backup: Cloud Backup Disable Started', - CLOUD_BACKUP_ENABLED_DONE: 'Backup: Cloud Backup Enabled Done', - CLOUD_BACKUP_ENABLE_STARTED: 'Backup: Cloud Backup Enable Started', - CLOUD_BACKUP_STARTED: 'Backup: Cloud Backup Started', - CLOUD_RESTORE_FAILED_PASSPORT_NOT_REGISTERED: - 'Backup: Cloud Restore Failed: Passport Not Registered', - CLOUD_RESTORE_FAILED_UNKNOWN: 'Backup: Cloud Restore Failed: Unknown Error', - CLOUD_RESTORE_SUCCESS: 'Backup: Cloud Restore Success', - CREATE_NEW_ACCOUNT: 'Backup: Create New Account', - MANUAL_RECOVERY_SELECTED: 'Backup: Manual Recovery Selected', -}; - -export const MockDataEvents = { - CANCEL_GENERATION: 'Mock Data: Cancel Generation', - CREATE_DEEP_LINK: 'Mock Data: Create Deep Link', - DECREASE_EXPIRY_YEARS: 'Mock Data: Decrease Expiry Years', - ENABLE_ADVANCED_MODE: 'Mock Data: Enable Advanced Mode', - GENERATE_DATA: 'Mock Data: Generate Data', - INCREASE_EXPIRY_YEARS: 'Mock Data: Increase Expiry Years', - OPEN_ALGORITHM_SELECTION: 'Mock Data: Open Algorithm Selection', - OPEN_COUNTRY_SELECTION: 'Mock Data: Open Country Selection', - SELECT_ALGORITHM: 'Mock Data: Select Algorithm', - SELECT_COUNTRY: 'Mock Data: Select Country', - TOGGLE_OFAC_LIST: 'Mock Data: Toggle OFAC List', -}; - -export const DocumentEvents = { - ADD_NEW_MOCK_SELECTED: 'Document: Add New Document via Mock', - ADD_NEW_SCAN_SELECTED: 'Document: Add New Document via Scan', - DOCUMENT_DELETED: 'Document: Document Deleted', - DOCUMENT_SELECTED: 'Document: Document Selected', - DOCUMENTS_FETCHED: 'Document: Documents Fetched', - MANAGE_SCREEN_OPENED: 'Document: Manage Documents Screen Opened', - NO_DOCUMENTS_FOUND: 'Document: No Documents Found', - PASSPORT_INFO_OPENED: 'Document: Passport Info Screen Opened', - PASSPORT_METADATA_LOADED: 'Document: Passport Metadata Loaded', - VALIDATE_DOCUMENT_FAILED: 'Document: Validate Document Failed', - DOCUMENT_VALIDATED: 'Document: Document Validated', -}; diff --git a/app/src/consts/links.ts b/app/src/consts/links.ts index 870c88784..45d146f06 100644 --- a/app/src/consts/links.ts +++ b/app/src/consts/links.ts @@ -1,19 +1,19 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -export const selfUrl = 'https://self.xyz'; +export const appStoreUrl = 'https://apps.apple.com/app/self-zk/id6478563710'; -export const termsUrl = 'https://self.xyz/terms'; +export const gitHubUrl = 'https://github.com/selfxyz/self'; + +export const playStoreUrl = + 'https://play.google.com/store/apps/details?id=com.proofofpassportapp'; export const privacyUrl = 'https://self.xyz/privacy'; +export const selfUrl = 'https://self.xyz'; + export const supportedBiometricIdsUrl = 'https://docs.self.xyz/use-self/self-map-countries-list'; export const telegramUrl = 'https://t.me/self_xyz'; -export const gitHubUrl = 'https://github.com/selfxyz/self'; - -export const appStoreUrl = 'https://apps.apple.com/app/self-zk/id6478563710'; - -export const playStoreUrl = - 'https://play.google.com/store/apps/details?id=com.proofofpassportapp'; +export const termsUrl = 'https://self.xyz/terms'; diff --git a/app/src/hooks/useAppUpdates.ts b/app/src/hooks/useAppUpdates.ts index 52ac1a050..67226621c 100644 --- a/app/src/hooks/useAppUpdates.ts +++ b/app/src/hooks/useAppUpdates.ts @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import { useEffect, useState } from 'react'; import { Linking } from 'react-native'; import { checkVersion } from 'react-native-check-version'; @@ -9,6 +8,8 @@ import { AppEvents } from '../consts/analytics'; import analytics from '../utils/analytics'; import { registerModalCallbacks } from '../utils/modalCallbackRegistry'; +import { useNavigation } from '@react-navigation/native'; + const { trackEvent } = analytics(); export const useAppUpdates = (): [boolean, () => void, boolean] => { diff --git a/app/src/hooks/useAppUpdates.web.ts b/app/src/hooks/useAppUpdates.web.ts index 01af69987..fe3d8a38c 100644 --- a/app/src/hooks/useAppUpdates.web.ts +++ b/app/src/hooks/useAppUpdates.web.ts @@ -1,12 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import { useState } from 'react'; import { AppEvents } from '../consts/analytics'; import analytics from '../utils/analytics'; import { registerModalCallbacks } from '../utils/modalCallbackRegistry'; +import { useNavigation } from '@react-navigation/native'; + const { trackEvent } = analytics(); export const useAppUpdates = (): [boolean, () => void, boolean] => { diff --git a/app/src/hooks/useHapticNavigation.ts b/app/src/hooks/useHapticNavigation.ts index e27de8f48..3185a7e00 100644 --- a/app/src/hooks/useHapticNavigation.ts +++ b/app/src/hooks/useHapticNavigation.ts @@ -1,12 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { useCallback } from 'react'; import type { RootStackParamList } from '../navigation/index'; import { impactLight, impactMedium, selectionChange } from '../utils/haptic'; +import { useNavigation } from '@react-navigation/native'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; + type NavigationAction = 'default' | 'cancel' | 'confirm'; const useHapticNavigation = ( diff --git a/app/src/hooks/useModal.ts b/app/src/hooks/useModal.ts index 31aa5d432..e316bfefb 100644 --- a/app/src/hooks/useModal.ts +++ b/app/src/hooks/useModal.ts @@ -1,15 +1,16 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import { useCallback, useRef, useState } from 'react'; -import { ModalParams } from '../screens/misc/ModalScreen'; +import type { ModalParams } from '../screens/misc/ModalScreen'; import { getModalCallbacks, registerModalCallbacks, unregisterModalCallbacks, } from '../utils/modalCallbackRegistry'; +import { useNavigation } from '@react-navigation/native'; + export const useModal = (params: ModalParams) => { const [visible, setVisible] = useState(false); const navigation = useNavigation(); diff --git a/app/src/layouts/AppLayout.tsx b/app/src/layouts/AppLayout.tsx index c2a7e2104..5a60bfb26 100644 --- a/app/src/layouts/AppLayout.tsx +++ b/app/src/layouts/AppLayout.tsx @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import React, { PropsWithChildren } from 'react'; +import type { PropsWithChildren } from 'react'; +import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; interface ConnectedAppLayoutProps extends PropsWithChildren {} diff --git a/app/src/layouts/ExpandableBottomLayout.tsx b/app/src/layouts/ExpandableBottomLayout.tsx index e9b854788..b257ad5ce 100644 --- a/app/src/layouts/ExpandableBottomLayout.tsx +++ b/app/src/layouts/ExpandableBottomLayout.tsx @@ -10,7 +10,8 @@ import { } from 'react-native'; import { SystemBars } from 'react-native-edge-to-edge'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { View, ViewProps } from 'tamagui'; +import type { ViewProps } from 'tamagui'; +import { View } from 'tamagui'; import { black, white } from '../utils/colors'; import { extraYPadding } from '../utils/constants'; diff --git a/app/src/layouts/SimpleScrolledTitleLayout.tsx b/app/src/layouts/SimpleScrolledTitleLayout.tsx index 13ed0b104..1bfaf4ba5 100644 --- a/app/src/layouts/SimpleScrolledTitleLayout.tsx +++ b/app/src/layouts/SimpleScrolledTitleLayout.tsx @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import React, { PropsWithChildren } from 'react'; +import type { PropsWithChildren } from 'react'; +import React from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ScrollView, YStack } from 'tamagui'; diff --git a/app/src/mocks/react-native-gesture-handler.ts b/app/src/mocks/react-native-gesture-handler.ts index 01af87d84..8d161fee0 100644 --- a/app/src/mocks/react-native-gesture-handler.ts +++ b/app/src/mocks/react-native-gesture-handler.ts @@ -4,14 +4,14 @@ * Web-compatible mock for react-native-gesture-handler */ -import React, { createElement } from 'react'; +import type React from 'react'; +import { createElement } from 'react'; -// Mock GestureHandlerRootView as a simple wrapper -export const GestureHandlerRootView: React.FC<{ - children: React.ReactNode; - [key: string]: any; -}> = ({ children, ...props }) => { - return createElement('div', props, children); +export const Directions = { + RIGHT: 1, + LEFT: 2, + UP: 4, + DOWN: 8, }; const returnValue = { @@ -47,6 +47,14 @@ export const GestureDetector: React.FC<{ return createElement('div', {}, children); }; +// Mock GestureHandlerRootView as a simple wrapper +export const GestureHandlerRootView: React.FC<{ + children: React.ReactNode; + [key: string]: any; +}> = ({ children, ...props }) => { + return createElement('div', props, children); +}; + // Mock other commonly used exports export const State = { UNDETERMINED: 0, @@ -57,13 +65,6 @@ export const State = { END: 5, }; -export const Directions = { - RIGHT: 1, - LEFT: 2, - UP: 4, - DOWN: 8, -}; - // Mock the jest setup export const jestSetup = () => {}; diff --git a/app/src/mocks/react-native-safe-area-context.js b/app/src/mocks/react-native-safe-area-context.js index 765d76c97..0ba32e801 100644 --- a/app/src/mocks/react-native-safe-area-context.js +++ b/app/src/mocks/react-native-safe-area-context.js @@ -1,20 +1,21 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import { createContext, createElement, Fragment } from 'react'; -// On web we dont need safe context area since we will be inside another app. (and it doesnt work) +export const SafeAreaContext = createContext(initialWindowMetrics); + +export const SafeAreaInsetsContext = createContext({ + top: 0, + bottom: 0, + left: 0, + right: 0, +}); + +// On web we dont need safe context area since we will be inside another app. (and it doesnt work) export function SafeAreaProvider({ children }) { return createElement(Fragment, null, children); } -export function useSafeAreaInsets() { - return { top: 0, bottom: 0, left: 0, right: 0 }; -} - -export function useSafeAreaFrame() { - return { x: 0, y: 0, width: 0, height: 0 }; -} - export function SafeAreaView(props) { return createElement('div', props, props.children); } @@ -34,11 +35,10 @@ export const initialWindowMetrics = { }, }; -export const SafeAreaContext = createContext(initialWindowMetrics); +export function useSafeAreaFrame() { + return { x: 0, y: 0, width: 0, height: 0 }; +} -export const SafeAreaInsetsContext = createContext({ - top: 0, - bottom: 0, - left: 0, - right: 0, -}); +export function useSafeAreaInsets() { + return { top: 0, bottom: 0, left: 0, right: 0 }; +} diff --git a/app/src/navigation/aesop.ts b/app/src/navigation/aesop.ts index 6c6ebd0c3..36844c288 100644 --- a/app/src/navigation/aesop.ts +++ b/app/src/navigation/aesop.ts @@ -1,15 +1,16 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; import { ProgressNavBar } from '../components/NavBar'; import { shouldShowAesopRedesign } from '../hooks/useAesopRedesign'; +import { white } from '../utils/colors'; + +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; const PassportOnboardingScreen = lazy( () => import('../screens/aesop/PassportOnboardingScreen'), ); -import { white } from '../utils/colors'; const aesopScreens = { PassportOnboarding: { diff --git a/app/src/navigation/dev.ts b/app/src/navigation/dev.ts index f9eb110fc..19d488742 100644 --- a/app/src/navigation/dev.ts +++ b/app/src/navigation/dev.ts @@ -1,8 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; +import { white } from '../utils/colors'; + +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const DevFeatureFlagsScreen = lazy( () => import('../screens/dev/DevFeatureFlagsScreen'), ); @@ -16,7 +19,6 @@ const MockDataScreen = lazy(() => import('../screens/dev/MockDataScreen')); const MockDataScreenDeepLink = lazy( () => import('../screens/dev/MockDataScreenDeepLink'), ); -import { white } from '../utils/colors'; const devScreens = { CreateMock: { diff --git a/app/src/navigation/home.ts b/app/src/navigation/home.ts index 37e2fa947..36ff17c09 100644 --- a/app/src/navigation/home.ts +++ b/app/src/navigation/home.ts @@ -1,9 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; import { HomeNavBar } from '../components/NavBar'; +import { black } from '../utils/colors'; + +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const DisclaimerScreen = lazy(() => import('../screens/home/DisclaimerScreen')); const HomeScreen = lazy(() => import('../screens/home/HomeScreen')); const ProofHistoryDetailScreen = lazy( @@ -12,7 +15,6 @@ const ProofHistoryDetailScreen = lazy( const ProofHistoryScreen = lazy( () => import('../screens/home/ProofHistoryScreen'), ); -import { black } from '../utils/colors'; const homeScreens = { Disclaimer: { screen: DisclaimerScreen, diff --git a/app/src/navigation/index.tsx b/app/src/navigation/index.tsx index 6738d6729..442a43b44 100644 --- a/app/src/navigation/index.tsx +++ b/app/src/navigation/index.tsx @@ -1,11 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { - createNavigationContainerRef, - createStaticNavigation, - StaticParamList, -} from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; import React, { Suspense, useEffect } from 'react'; import { Platform, View } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; @@ -25,6 +19,13 @@ import proveScreens from './prove'; import recoveryScreens from './recovery'; import settingsScreens from './settings'; +import type { StaticParamList } from '@react-navigation/native'; +import { + createNavigationContainerRef, + createStaticNavigation, +} from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + export const navigationScreens = { ...miscScreens, ...passportScreens, @@ -37,6 +38,8 @@ export const navigationScreens = { ...getAesopScreens(), }; +export type RootStackParamList = StaticParamList; + const AppNavigation = createNativeStackNavigator({ id: undefined, initialRouteName: Platform.OS === 'web' ? 'Home' : 'Splash', @@ -48,7 +51,8 @@ const AppNavigation = createNativeStackNavigator({ screens: navigationScreens, }); -export type RootStackParamList = StaticParamList; +// Create a ref that we can use to access the navigation state +export const navigationRef = createNavigationContainerRef(); declare global { namespace ReactNavigation { @@ -56,9 +60,6 @@ declare global { } } -// Create a ref that we can use to access the navigation state -export const navigationRef = createNavigationContainerRef(); - const { trackScreenView } = analytics(); const Navigation = createStaticNavigation(AppNavigation); diff --git a/app/src/navigation/misc.tsx b/app/src/navigation/misc.tsx index 091b8a95a..6e7eb21de 100644 --- a/app/src/navigation/misc.tsx +++ b/app/src/navigation/misc.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import React, { lazy } from 'react'; import { SystemBars } from 'react-native-edge-to-edge'; @@ -10,6 +9,8 @@ import { SystemBars } from 'react-native-edge-to-edge'; import SplashScreen from '../screens/misc/SplashScreen'; import { black } from '../utils/colors'; +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const LaunchScreen = lazy(() => import('../screens/misc/LaunchScreen')); const LoadingScreen = lazy(() => import('../screens/misc/LoadingScreen')); const ModalScreen = lazy(() => import('../screens/misc/ModalScreen')); diff --git a/app/src/navigation/passport.ts b/app/src/navigation/passport.ts index 1cec5acc2..43d085c33 100644 --- a/app/src/navigation/passport.ts +++ b/app/src/navigation/passport.ts @@ -1,8 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const PassportCameraScreen = lazy( () => import('../screens/passport/PassportCameraScreen'), ); diff --git a/app/src/navigation/prove.ts b/app/src/navigation/prove.ts index 5d9008b16..da40d8e59 100644 --- a/app/src/navigation/prove.ts +++ b/app/src/navigation/prove.ts @@ -1,8 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; +import { black, white } from '../utils/colors'; + +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const ConfirmBelongingScreen = lazy( () => import('../screens/prove/ConfirmBelongingScreen'), ); @@ -16,7 +19,6 @@ const QRCodeTroubleScreen = lazy( const QRCodeViewFinderScreen = lazy( () => import('../screens/prove/ViewFinderScreen'), ); -import { black, white } from '../utils/colors'; const proveScreens = { ConfirmBelongingScreen: { diff --git a/app/src/navigation/recovery.ts b/app/src/navigation/recovery.ts index d1aeb05e6..aee658f86 100644 --- a/app/src/navigation/recovery.ts +++ b/app/src/navigation/recovery.ts @@ -1,8 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; +import { black, slate300 } from '../utils/colors'; + +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const AccountRecoveryChoiceScreen = lazy( () => import('../screens/recovery/AccountRecoveryChoiceScreen'), ); @@ -21,7 +24,6 @@ const RecoverWithPhraseScreen = lazy( const SaveRecoveryPhraseScreen = lazy( () => import('../screens/recovery/SaveRecoveryPhraseScreen'), ); -import { black, slate300 } from '../utils/colors'; const recoveryScreens = { AccountRecovery: { diff --git a/app/src/navigation/recovery.web.ts b/app/src/navigation/recovery.web.ts index 3b4b91e4f..ef449f360 100644 --- a/app/src/navigation/recovery.web.ts +++ b/app/src/navigation/recovery.web.ts @@ -1,8 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const PassportDataNotFound = lazy( () => import('../screens/recovery/PassportDataNotFoundScreen'), ); diff --git a/app/src/navigation/settings.ts b/app/src/navigation/settings.ts index 3321389f9..aae2860e7 100644 --- a/app/src/navigation/settings.ts +++ b/app/src/navigation/settings.ts @@ -1,8 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; +import { black, slate300, white } from '../utils/colors'; + +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const CloudBackupScreen = lazy( () => import('../screens/settings/CloudBackupScreen'), ); @@ -16,7 +19,6 @@ const SettingsScreen = lazy(() => import('../screens/settings/SettingsScreen')); const ShowRecoveryPhraseScreen = lazy( () => import('../screens/settings/ShowRecoveryPhraseScreen'), ); -import { black, slate300, white } from '../utils/colors'; const settingsScreens = { CloudBackupSettings: { diff --git a/app/src/navigation/settings.web.ts b/app/src/navigation/settings.web.ts index 971d29d15..0c6eeb0a9 100644 --- a/app/src/navigation/settings.web.ts +++ b/app/src/navigation/settings.web.ts @@ -1,8 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { lazy } from 'react'; +import { black, white } from '../utils/colors'; + +import type { NativeStackNavigationOptions } from '@react-navigation/native-stack'; + const ManageDocumentsScreen = lazy( () => import('../screens/settings/ManageDocumentsScreen'), ); @@ -10,7 +13,6 @@ const PassportDataInfoScreen = lazy( () => import('../screens/settings/PassportDataInfoScreen'), ); const SettingsScreen = lazy(() => import('../screens/settings/SettingsScreen')); -import { black, white } from '../utils/colors'; const settingsScreens = { ManageDocuments: { diff --git a/app/src/providers/authProvider.tsx b/app/src/providers/authProvider.tsx index f71d260f5..b1f016763 100644 --- a/app/src/providers/authProvider.tsx +++ b/app/src/providers/authProvider.tsx @@ -1,9 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import { ethers } from 'ethers'; +import type { PropsWithChildren } from 'react'; import React, { createContext, - PropsWithChildren, useCallback, useContext, useMemo, @@ -14,7 +14,7 @@ import Keychain from 'react-native-keychain'; import { AuthEvents } from '../consts/analytics'; import { useSettingStore } from '../stores/settingStore'; -import { Mnemonic } from '../types/mnemonic'; +import type { Mnemonic } from '../types/mnemonic'; import analytics from '../utils/analytics'; const { trackEvent } = analytics(); @@ -258,15 +258,17 @@ export const AuthProvider = ({ return {children}; }; -export const useAuth = () => { - return useContext(AuthContext); -}; - export async function hasSecretStored() { const seed = await Keychain.getGenericPassword({ service: SERVICE_NAME }); return !!seed; } +export async function unsafe_clearSecrets() { + if (__DEV__) { + await Keychain.resetGenericPassword({ service: SERVICE_NAME }); + } +} + /** * The only reason this is exported without being locked behind user biometrics is to allow `loadPassportDataAndSecret` * to access both the privatekey and the passport data with the user only authenticating once @@ -281,8 +283,6 @@ export async function unsafe_getPrivateKey() { return wallet.privateKey; } -export async function unsafe_clearSecrets() { - if (__DEV__) { - await Keychain.resetGenericPassword({ service: SERVICE_NAME }); - } -} +export const useAuth = () => { + return useContext(AuthContext); +}; diff --git a/app/src/providers/authProvider.web.tsx b/app/src/providers/authProvider.web.tsx index e25e0f649..15fe3cae3 100644 --- a/app/src/providers/authProvider.web.tsx +++ b/app/src/providers/authProvider.web.tsx @@ -5,9 +5,9 @@ * */ +import type { PropsWithChildren } from 'react'; import React, { createContext, - PropsWithChildren, useCallback, useContext, useMemo, @@ -15,7 +15,7 @@ import React, { } from 'react'; import { AuthEvents } from '../consts/analytics'; -import { Mnemonic } from '../types/mnemonic'; +import type { Mnemonic } from '../types/mnemonic'; import analytics from '../utils/analytics'; const { trackEvent } = analytics(); @@ -259,15 +259,18 @@ export const AuthProvider = ({ return {children}; }; -export const useAuth = () => { - return useContext(AuthContext); -}; - export async function hasSecretStored() { // TODO implement a way to check if the private key is stored return true; } +export async function unsafe_clearSecrets() { + if (__DEV__) { + console.warn('unsafe_clearSecrets is not implemented for web'); + // In a real implementation, you would clear any stored secrets here + } +} + /** * The only reason this is exported without being locked behind user biometrics is to allow `loadPassportDataAndSecret` * to access both the privatekey and the passport data with the user only authenticating once @@ -276,9 +279,6 @@ export async function unsafe_getPrivateKey() { return getPrivateKey(); } -export async function unsafe_clearSecrets() { - if (__DEV__) { - console.warn('unsafe_clearSecrets is not implemented for web'); - // In a real implementation, you would clear any stored secrets here - } -} +export const useAuth = () => { + return useContext(AuthContext); +}; diff --git a/app/src/providers/notificationTrackingProvider.tsx b/app/src/providers/notificationTrackingProvider.tsx index c43ff4f95..5defe52bd 100644 --- a/app/src/providers/notificationTrackingProvider.tsx +++ b/app/src/providers/notificationTrackingProvider.tsx @@ -1,11 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import messaging from '@react-native-firebase/messaging'; -import React, { PropsWithChildren, useEffect } from 'react'; +import type { PropsWithChildren } from 'react'; +import React, { useEffect } from 'react'; import { NotificationEvents } from '../consts/analytics'; import analytics from '../utils/analytics'; +import messaging from '@react-native-firebase/messaging'; + const { trackEvent } = analytics(); export const NotificationTrackingProvider: React.FC = ({ diff --git a/app/src/providers/notificationTrackingProvider.web.tsx b/app/src/providers/notificationTrackingProvider.web.tsx index 4715a259c..e8f981949 100644 --- a/app/src/providers/notificationTrackingProvider.web.tsx +++ b/app/src/providers/notificationTrackingProvider.web.tsx @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import React, { PropsWithChildren } from 'react'; +import type { PropsWithChildren } from 'react'; +import React from 'react'; //TODO:WEB Stubbed out for now on web export const NotificationTrackingProvider: React.FC = ({ diff --git a/app/src/providers/passportDataProvider.tsx b/app/src/providers/passportDataProvider.tsx index d743635f2..f508afa84 100644 --- a/app/src/providers/passportDataProvider.tsx +++ b/app/src/providers/passportDataProvider.tsx @@ -38,24 +38,49 @@ * - Display format determined by documentCategory */ +import { sha256 } from 'js-sha256'; +import type { PropsWithChildren } from 'react'; +import React, { createContext, useCallback, useContext, useMemo } from 'react'; +import Keychain from 'react-native-keychain'; + import type { DocumentCategory, PassportData } from '@selfxyz/common/types'; -import { - brutforceSignatureAlgorithmDsc, - parseCertificateSimple, +import type { PublicKeyDetailsECDSA, PublicKeyDetailsRSA, } from '@selfxyz/common/utils'; -import { sha256 } from 'js-sha256'; -import React, { - createContext, - PropsWithChildren, - useCallback, - useContext, - useMemo, -} from 'react'; -import Keychain from 'react-native-keychain'; +import { + brutforceSignatureAlgorithmDsc, + parseCertificateSimple, +} from '@selfxyz/common/utils'; import { unsafe_getPrivateKey, useAuth } from '../providers/authProvider'; + +// Create safe wrapper functions to prevent undefined errors during early initialization +// These need to be declared early to avoid dependency issues +const safeLoadDocumentCatalog = async (): Promise => { + try { + return await loadDocumentCatalog(); + } catch (error) { + console.warn( + 'Error in safeLoadDocumentCatalog, returning empty catalog:', + error, + ); + return { documents: [] }; + } +}; + +const safeGetAllDocuments = async () => { + try { + return await getAllDocuments(); + } catch (error) { + console.warn( + 'Error in safeGetAllDocuments, returning empty object:', + error, + ); + return {}; + } +}; + interface DocumentMetadata { id: string; // contentHash as ID for deduplication documentType: string; // passport, mock_passport, id_card, etc. @@ -104,543 +129,6 @@ function inferDocumentCategory(documentType: string): DocumentCategory { // Global flag to track if native modules are ready let nativeModulesReady = false; -/** - * Global initialization function to wait for native modules to be ready - * Call this once at app startup before any native module operations - */ -export async function initializeNativeModules( - maxRetries: number = 10, - delay: number = 500, -): Promise { - if (nativeModulesReady) { - return true; - } - - console.log('Initializing native modules...'); - - for (let i = 0; i < maxRetries; i++) { - try { - if (typeof Keychain.getGenericPassword === 'function') { - // Test if Keychain is actually available by making a safe call - await Keychain.getGenericPassword({ service: 'test-availability' }); - nativeModulesReady = true; - console.log('Native modules ready!'); - return true; - } - } catch (error) { - // If we get a "requiring unknown module" error, wait and retry - if ( - error instanceof Error && - error.message.includes('Requiring unknown module') - ) { - console.log( - `Waiting for native modules to be ready (attempt ${i + 1}/${maxRetries})`, - ); - await new Promise(resolve => setTimeout(resolve, delay)); - continue; - } - // For other errors (like service not found), assume Keychain is available - nativeModulesReady = true; - console.log('Native modules ready (with minor errors)!'); - return true; - } - } - - console.warn('Native modules not ready after retries'); - return false; -} - -export async function loadDocumentCatalog(): Promise { - try { - // Extra safety check for module initialization - if (typeof Keychain === 'undefined' || !Keychain) { - console.warn( - 'Keychain module not yet initialized, returning empty catalog', - ); - return { documents: [] }; - } - - // Check if native modules are ready (should be initialized at app startup) - if (!nativeModulesReady) { - console.warn('Native modules not ready, returning empty catalog'); - return { documents: [] }; - } - - const catalogCreds = await Keychain.getGenericPassword({ - service: 'documentCatalog', - }); - if (catalogCreds !== false) { - return JSON.parse(catalogCreds.password); - } - } catch (error) { - console.log('Error loading document catalog:', error); - } - - // Return empty catalog if none exists - return { documents: [] }; -} - -export async function saveDocumentCatalog( - catalog: DocumentCatalog, -): Promise { - await Keychain.setGenericPassword('catalog', JSON.stringify(catalog), { - service: 'documentCatalog', - }); -} - -export async function loadDocumentById( - documentId: string, -): Promise { - try { - // Check if native modules are ready - if (!nativeModulesReady) { - console.warn( - `Native modules not ready for loading document ${documentId}, returning null`, - ); - return null; - } - - const documentCreds = await Keychain.getGenericPassword({ - service: `document-${documentId}`, - }); - if (documentCreds !== false) { - return JSON.parse(documentCreds.password); - } - } catch (error) { - console.log(`Error loading document ${documentId}:`, error); - } - return null; -} - -export async function storeDocumentWithDeduplication( - passportData: PassportData, -): Promise { - const contentHash = calculateContentHash(passportData); - const catalog = await loadDocumentCatalog(); - - // Check for existing document with same content - const existing = catalog.documents.find(d => d.id === contentHash); - if (existing) { - // Even if content hash is the same, we should update the document - // in case metadata (like CSCA) has changed - console.log('Document with same content exists, updating stored data'); - - // Update the stored document with potentially new metadata - await Keychain.setGenericPassword( - contentHash, - JSON.stringify(passportData), - { - service: `document-${contentHash}`, - }, - ); - - // Update selected document to this one - catalog.selectedDocumentId = contentHash; - await saveDocumentCatalog(catalog); - return contentHash; - } - - // Store new document using contentHash as service name - await Keychain.setGenericPassword(contentHash, JSON.stringify(passportData), { - service: `document-${contentHash}`, - }); - - // Add to catalog - const metadata: DocumentMetadata = { - id: contentHash, - documentType: passportData.documentType, - documentCategory: - passportData.documentCategory || - inferDocumentCategory(passportData.documentType), - data: passportData.mrz || '', // Store MRZ for passports/IDs, relevant data for aadhaar - mock: passportData.mock || false, - isRegistered: false, - }; - - catalog.documents.push(metadata); - catalog.selectedDocumentId = contentHash; - await saveDocumentCatalog(catalog); - - return contentHash; -} - -export async function loadSelectedDocument(): Promise<{ - data: PassportData; - metadata: DocumentMetadata; -} | null> { - const catalog = await loadDocumentCatalog(); - console.log('Catalog loaded'); - - if (!catalog.selectedDocumentId) { - console.log('No selectedDocumentId found'); - if (catalog.documents.length > 0) { - console.log('Using first document as fallback'); - catalog.selectedDocumentId = catalog.documents[0].id; - await saveDocumentCatalog(catalog); - } else { - console.log('No documents in catalog, returning null'); - return null; - } - } - - const metadata = catalog.documents.find( - d => d.id === catalog.selectedDocumentId, - ); - if (!metadata) { - console.log( - 'Metadata not found for selectedDocumentId:', - catalog.selectedDocumentId, - ); - return null; - } - - const data = await loadDocumentById(catalog.selectedDocumentId); - if (!data) { - console.log('Document data not found for id:', catalog.selectedDocumentId); - return null; - } - - console.log('Successfully loaded document:', metadata.documentType); - return { data, metadata }; -} - -export async function getAllDocuments(): Promise<{ - [documentId: string]: { data: PassportData; metadata: DocumentMetadata }; -}> { - const catalog = await loadDocumentCatalog(); - const allDocs: { - [documentId: string]: { data: PassportData; metadata: DocumentMetadata }; - } = {}; - - for (const metadata of catalog.documents) { - const data = await loadDocumentById(metadata.id); - if (data) { - allDocs[metadata.id] = { data, metadata }; - } - } - - return allDocs; -} - -export async function setSelectedDocument(documentId: string): Promise { - const catalog = await loadDocumentCatalog(); - const metadata = catalog.documents.find(d => d.id === documentId); - - if (metadata) { - catalog.selectedDocumentId = documentId; - await saveDocumentCatalog(catalog); - } -} - -export async function deleteDocument(documentId: string): Promise { - const catalog = await loadDocumentCatalog(); - - // Remove from catalog - catalog.documents = catalog.documents.filter(d => d.id !== documentId); - - // Update selected document if it was deleted - if (catalog.selectedDocumentId === documentId) { - if (catalog.documents.length > 0) { - catalog.selectedDocumentId = catalog.documents[0].id; - } else { - catalog.selectedDocumentId = undefined; - } - } - - await saveDocumentCatalog(catalog); - - // Delete the actual document - try { - await Keychain.resetGenericPassword({ service: `document-${documentId}` }); - } catch (error) { - console.log(`Document ${documentId} not found or already cleared`); - } -} - -export async function getAvailableDocumentTypes(): Promise { - const catalog = await loadDocumentCatalog(); - return [...new Set(catalog.documents.map(d => d.documentType))]; -} - -export async function migrateFromLegacyStorage(): Promise { - console.log('Migrating from legacy storage to new architecture...'); - const catalog = await loadDocumentCatalog(); - - // If catalog already has documents, skip migration - if (catalog.documents.length > 0) { - console.log('Migration already completed'); - return; - } - - const legacyServices = [ - 'passportData', - 'mockPassportData', - 'idCardData', - 'mockIdCardData', - ]; - for (const service of legacyServices) { - try { - const passportDataCreds = await Keychain.getGenericPassword({ service }); - if (passportDataCreds !== false) { - const passportData: PassportData = JSON.parse( - passportDataCreds.password, - ); - await storeDocumentWithDeduplication(passportData); - await Keychain.resetGenericPassword({ service }); - console.log(`Migrated document from ${service}`); - } - } catch (error) { - console.log(`Could not migrate from service ${service}:`, error); - } - } - - console.log('Migration completed'); -} - -// ===== LEGACY WRAPPER FUNCTIONS (for backward compatibility) ===== - -function getServiceNameForDocumentType(documentType: string): string { - // These are now only used for legacy compatibility - switch (documentType) { - case 'passport': - return 'passportData'; - case 'mock_passport': - return 'mockPassportData'; - case 'id_card': - return 'idCardData'; - case 'mock_id_card': - return 'mockIdCardData'; - default: - return 'passportData'; - } -} - -export async function loadPassportData() { - // Try new system first - const selected = await loadSelectedDocument(); - if (selected) { - return JSON.stringify(selected.data); - } - - // Fallback to legacy system and migrate if found - try { - // Check if native modules are ready for legacy migration - if (!nativeModulesReady) { - console.warn( - 'Native modules not ready for legacy passport data migration', - ); - return false; - } - - const services = [ - 'passportData', - 'mockPassportData', - 'idCardData', - 'mockIdCardData', - ]; - for (const service of services) { - const passportDataCreds = await Keychain.getGenericPassword({ service }); - if (passportDataCreds !== false) { - // Migrate this document - const passportData: PassportData = JSON.parse( - passportDataCreds.password, - ); - await storeDocumentWithDeduplication(passportData); - await Keychain.resetGenericPassword({ service }); - return passportDataCreds.password; - } - } - } catch (error) { - console.log('Error in legacy passport data migration:', error); - } - - return false; -} - -export async function loadSelectedPassportData(): Promise { - // Try new system first - const selected = await loadSelectedDocument(); - if (selected) { - return JSON.stringify(selected.data); - } - - // Fallback to legacy system - return await loadPassportData(); -} - -export async function loadSelectedPassportDataAndSecret() { - const passportData = await loadSelectedPassportData(); - const secret = await unsafe_getPrivateKey(); - if (!secret || !passportData) { - return false; - } - return JSON.stringify({ - secret, - passportData: JSON.parse(passportData), - }); -} - -export async function loadAllPassportData(): Promise<{ - [service: string]: PassportData; -}> { - const allDocs = await getAllDocuments(); - const result: { [service: string]: PassportData } = {}; - - // Convert to legacy format for backward compatibility - Object.values(allDocs).forEach(({ data, metadata }) => { - const serviceName = getServiceNameForDocumentType(metadata.documentType); - result[serviceName] = data; - }); - - return result; -} - -export async function setDefaultDocumentTypeIfNeeded() { - const catalog = await loadDocumentCatalog(); - - if (!catalog.selectedDocumentId && catalog.documents.length > 0) { - await setSelectedDocument(catalog.documents[0].id); - } -} - -export async function loadPassportDataAndSecret() { - const passportData = await loadPassportData(); - const secret = await unsafe_getPrivateKey(); - if (!secret || !passportData) { - return false; - } - return JSON.stringify({ - secret, - passportData: JSON.parse(passportData), - }); -} - -export async function storePassportData(passportData: PassportData) { - await storeDocumentWithDeduplication(passportData); -} - -export async function clearPassportData() { - const catalog = await loadDocumentCatalog(); - - // Delete all documents - for (const doc of catalog.documents) { - try { - await Keychain.resetGenericPassword({ service: `document-${doc.id}` }); - } catch (error) { - console.log(`Document ${doc.id} not found or already cleared`); - } - } - - // Clear catalog - await saveDocumentCatalog({ documents: [] }); -} - -export async function clearSpecificPassportData(documentType: string) { - const catalog = await loadDocumentCatalog(); - const docsToDelete = catalog.documents.filter( - d => d.documentType === documentType, - ); - - for (const doc of docsToDelete) { - await deleteDocument(doc.id); - } -} - -export async function clearDocumentCatalogForMigrationTesting() { - console.log('Clearing document catalog for migration testing...'); - const catalog = await loadDocumentCatalog(); - - // Delete all new-style documents - for (const doc of catalog.documents) { - try { - await Keychain.resetGenericPassword({ service: `document-${doc.id}` }); - console.log(`Cleared document: ${doc.id}`); - } catch (error) { - console.log(`Document ${doc.id} not found or already cleared`); - } - } - - // Clear the catalog itself - try { - await Keychain.resetGenericPassword({ service: 'documentCatalog' }); - console.log('Cleared document catalog'); - } catch (error) { - console.log('Document catalog not found or already cleared'); - } - - // Note: We intentionally do NOT clear legacy storage entries - // (passportData, mockPassportData, etc.) so migration can be tested - console.log( - 'Document catalog cleared. Legacy storage preserved for migration testing.', - ); -} - -interface PassportProviderProps extends PropsWithChildren { - authenticationTimeoutinMs?: number; -} -interface IPassportContext { - getData: () => Promise<{ signature: string; data: PassportData } | null>; - getSelectedData: () => Promise<{ - signature: string; - data: PassportData; - } | null>; - getAllData: () => Promise<{ [service: string]: PassportData }>; - getAvailableTypes: () => Promise; - setData: (data: PassportData) => Promise; - getPassportDataAndSecret: () => Promise<{ - data: { passportData: PassportData; secret: string }; - signature: string; - } | null>; - getSelectedPassportDataAndSecret: () => Promise<{ - data: { passportData: PassportData; secret: string }; - signature: string; - } | null>; - clearPassportData: () => Promise; - clearSpecificData: (documentType: string) => Promise; - loadDocumentCatalog: () => Promise; - getAllDocuments: () => Promise<{ - [documentId: string]: { data: PassportData; metadata: DocumentMetadata }; - }>; - setSelectedDocument: (documentId: string) => Promise; - deleteDocument: (documentId: string) => Promise; - migrateFromLegacyStorage: () => Promise; - getCurrentDocumentType: () => Promise; - clearDocumentCatalogForMigrationTesting: () => Promise; - markCurrentDocumentAsRegistered: () => Promise; - updateDocumentRegistrationState: ( - documentId: string, - isRegistered: boolean, - ) => Promise; - checkIfAnyDocumentsNeedMigration: () => Promise; - hasAnyValidRegisteredDocument: () => Promise; - checkAndUpdateRegistrationStates: () => Promise; -} - -// Create safe wrapper functions to prevent undefined errors during early initialization -const safeLoadDocumentCatalog = async (): Promise => { - try { - return await loadDocumentCatalog(); - } catch (error) { - console.warn( - 'Error in safeLoadDocumentCatalog, returning empty catalog:', - error, - ); - return { documents: [] }; - } -}; - -const safeGetAllDocuments = async () => { - try { - return await getAllDocuments(); - } catch (error) { - console.warn( - 'Error in safeGetAllDocuments, returning empty object:', - error, - ); - return {}; - } -}; - export const PassportContext = createContext({ getData: () => Promise.resolve(null), getSelectedData: () => Promise.resolve(null), @@ -743,9 +231,482 @@ export const PassportProvider = ({ children }: PassportProviderProps) => { ); }; -export const usePassport = () => { - return useContext(PassportContext); -}; +export async function checkAndUpdateRegistrationStates(): Promise { + // Lazy import to avoid circular dependency + const { checkAndUpdateRegistrationStates: validateDocCheckAndUpdate } = + await import('../utils/proving/validateDocument'); + return validateDocCheckAndUpdate(); +} + +export async function checkIfAnyDocumentsNeedMigration(): Promise { + try { + const catalog = await loadDocumentCatalog(); + return catalog.documents.some(doc => doc.isRegistered === undefined); + } catch (error) { + console.warn('Error checking if documents need migration:', error); + return false; + } +} + +export async function clearDocumentCatalogForMigrationTesting() { + console.log('Clearing document catalog for migration testing...'); + const catalog = await loadDocumentCatalog(); + + // Delete all new-style documents + for (const doc of catalog.documents) { + try { + await Keychain.resetGenericPassword({ service: `document-${doc.id}` }); + console.log(`Cleared document: ${doc.id}`); + } catch (error) { + console.log(`Document ${doc.id} not found or already cleared`); + } + } + + // Clear the catalog itself + try { + await Keychain.resetGenericPassword({ service: 'documentCatalog' }); + console.log('Cleared document catalog'); + } catch (error) { + console.log('Document catalog not found or already cleared'); + } + + // Note: We intentionally do NOT clear legacy storage entries + // (passportData, mockPassportData, etc.) so migration can be tested + console.log( + 'Document catalog cleared. Legacy storage preserved for migration testing.', + ); +} + +export async function clearPassportData() { + const catalog = await loadDocumentCatalog(); + + // Delete all documents + for (const doc of catalog.documents) { + try { + await Keychain.resetGenericPassword({ service: `document-${doc.id}` }); + } catch (error) { + console.log(`Document ${doc.id} not found or already cleared`); + } + } + + // Clear catalog + await saveDocumentCatalog({ documents: [] }); +} + +export async function clearSpecificPassportData(documentType: string) { + const catalog = await loadDocumentCatalog(); + const docsToDelete = catalog.documents.filter( + d => d.documentType === documentType, + ); + + for (const doc of docsToDelete) { + await deleteDocument(doc.id); + } +} + +export async function deleteDocument(documentId: string): Promise { + const catalog = await loadDocumentCatalog(); + + // Remove from catalog + catalog.documents = catalog.documents.filter(d => d.id !== documentId); + + // Update selected document if it was deleted + if (catalog.selectedDocumentId === documentId) { + if (catalog.documents.length > 0) { + catalog.selectedDocumentId = catalog.documents[0].id; + } else { + catalog.selectedDocumentId = undefined; + } + } + + await saveDocumentCatalog(catalog); + + // Delete the actual document + try { + await Keychain.resetGenericPassword({ service: `document-${documentId}` }); + } catch (error) { + console.log(`Document ${documentId} not found or already cleared`); + } +} + +export async function getAllDocuments(): Promise<{ + [documentId: string]: { data: PassportData; metadata: DocumentMetadata }; +}> { + const catalog = await loadDocumentCatalog(); + const allDocs: { + [documentId: string]: { data: PassportData; metadata: DocumentMetadata }; + } = {}; + + for (const metadata of catalog.documents) { + const data = await loadDocumentById(metadata.id); + if (data) { + allDocs[metadata.id] = { data, metadata }; + } + } + + return allDocs; +} + +export async function getAvailableDocumentTypes(): Promise { + const catalog = await loadDocumentCatalog(); + return [...new Set(catalog.documents.map(d => d.documentType))]; +} + +// Helper function to get current document type from catalog +export async function getCurrentDocumentType(): Promise { + const catalog = await loadDocumentCatalog(); + if (!catalog.selectedDocumentId) return null; + + const metadata = catalog.documents.find( + d => d.id === catalog.selectedDocumentId, + ); + return metadata?.documentType || null; +} + +// ===== LEGACY WRAPPER FUNCTIONS (for backward compatibility) ===== + +function getServiceNameForDocumentType(documentType: string): string { + // These are now only used for legacy compatibility + switch (documentType) { + case 'passport': + return 'passportData'; + case 'mock_passport': + return 'mockPassportData'; + case 'id_card': + return 'idCardData'; + case 'mock_id_card': + return 'mockIdCardData'; + default: + return 'passportData'; + } +} + +export async function hasAnyValidRegisteredDocument(): Promise { + try { + const catalog = await loadDocumentCatalog(); + return catalog.documents.some(doc => doc.isRegistered === true); + } catch (error) { + console.error('Error loading document catalog:', error); + return false; + } +} + +/** + * Global initialization function to wait for native modules to be ready + * Call this once at app startup before any native module operations + */ +export async function initializeNativeModules( + maxRetries: number = 10, + delay: number = 500, +): Promise { + if (nativeModulesReady) { + return true; + } + + console.log('Initializing native modules...'); + + for (let i = 0; i < maxRetries; i++) { + try { + if (typeof Keychain.getGenericPassword === 'function') { + // Test if Keychain is actually available by making a safe call + await Keychain.getGenericPassword({ service: 'test-availability' }); + nativeModulesReady = true; + console.log('Native modules ready!'); + return true; + } + } catch (error) { + // If we get a "requiring unknown module" error, wait and retry + if ( + error instanceof Error && + error.message.includes('Requiring unknown module') + ) { + console.log( + `Waiting for native modules to be ready (attempt ${i + 1}/${maxRetries})`, + ); + await new Promise(resolve => setTimeout(resolve, delay)); + continue; + } + // For other errors (like service not found), assume Keychain is available + nativeModulesReady = true; + console.log('Native modules ready (with minor errors)!'); + return true; + } + } + + console.warn('Native modules not ready after retries'); + return false; +} + +export async function loadAllPassportData(): Promise<{ + [service: string]: PassportData; +}> { + const allDocs = await getAllDocuments(); + const result: { [service: string]: PassportData } = {}; + + // Convert to legacy format for backward compatibility + Object.values(allDocs).forEach(({ data, metadata }) => { + const serviceName = getServiceNameForDocumentType(metadata.documentType); + result[serviceName] = data; + }); + + return result; +} + +export async function loadDocumentById( + documentId: string, +): Promise { + try { + // Check if native modules are ready + if (!nativeModulesReady) { + console.warn( + `Native modules not ready for loading document ${documentId}, returning null`, + ); + return null; + } + + const documentCreds = await Keychain.getGenericPassword({ + service: `document-${documentId}`, + }); + if (documentCreds !== false) { + return JSON.parse(documentCreds.password); + } + } catch (error) { + console.log(`Error loading document ${documentId}:`, error); + } + return null; +} + +export async function loadDocumentCatalog(): Promise { + try { + // Extra safety check for module initialization + if (typeof Keychain === 'undefined' || !Keychain) { + console.warn( + 'Keychain module not yet initialized, returning empty catalog', + ); + return { documents: [] }; + } + + // Check if native modules are ready (should be initialized at app startup) + if (!nativeModulesReady) { + console.warn('Native modules not ready, returning empty catalog'); + return { documents: [] }; + } + + const catalogCreds = await Keychain.getGenericPassword({ + service: 'documentCatalog', + }); + if (catalogCreds !== false) { + return JSON.parse(catalogCreds.password); + } + } catch (error) { + console.log('Error loading document catalog:', error); + } + + // Return empty catalog if none exists + return { documents: [] }; +} + +export async function loadPassportData() { + // Try new system first + const selected = await loadSelectedDocument(); + if (selected) { + return JSON.stringify(selected.data); + } + + // Fallback to legacy system and migrate if found + try { + // Check if native modules are ready for legacy migration + if (!nativeModulesReady) { + console.warn( + 'Native modules not ready for legacy passport data migration', + ); + return false; + } + + const services = [ + 'passportData', + 'mockPassportData', + 'idCardData', + 'mockIdCardData', + ]; + for (const service of services) { + const passportDataCreds = await Keychain.getGenericPassword({ service }); + if (passportDataCreds !== false) { + // Migrate this document + const passportData: PassportData = JSON.parse( + passportDataCreds.password, + ); + await storeDocumentWithDeduplication(passportData); + await Keychain.resetGenericPassword({ service }); + return passportDataCreds.password; + } + } + } catch (error) { + console.log('Error in legacy passport data migration:', error); + } + + return false; +} + +export async function loadPassportDataAndSecret() { + const passportData = await loadPassportData(); + const secret = await unsafe_getPrivateKey(); + if (!secret || !passportData) { + return false; + } + return JSON.stringify({ + secret, + passportData: JSON.parse(passportData), + }); +} + +export async function loadSelectedDocument(): Promise<{ + data: PassportData; + metadata: DocumentMetadata; +} | null> { + const catalog = await loadDocumentCatalog(); + console.log('Catalog loaded'); + + if (!catalog.selectedDocumentId) { + console.log('No selectedDocumentId found'); + if (catalog.documents.length > 0) { + console.log('Using first document as fallback'); + catalog.selectedDocumentId = catalog.documents[0].id; + await saveDocumentCatalog(catalog); + } else { + console.log('No documents in catalog, returning null'); + return null; + } + } + + const metadata = catalog.documents.find( + d => d.id === catalog.selectedDocumentId, + ); + if (!metadata) { + console.log( + 'Metadata not found for selectedDocumentId:', + catalog.selectedDocumentId, + ); + return null; + } + + const data = await loadDocumentById(catalog.selectedDocumentId); + if (!data) { + console.log('Document data not found for id:', catalog.selectedDocumentId); + return null; + } + + console.log('Successfully loaded document:', metadata.documentType); + return { data, metadata }; +} + +export async function loadSelectedPassportData(): Promise { + // Try new system first + const selected = await loadSelectedDocument(); + if (selected) { + return JSON.stringify(selected.data); + } + + // Fallback to legacy system + return await loadPassportData(); +} + +export async function loadSelectedPassportDataAndSecret() { + const passportData = await loadSelectedPassportData(); + const secret = await unsafe_getPrivateKey(); + if (!secret || !passportData) { + return false; + } + return JSON.stringify({ + secret, + passportData: JSON.parse(passportData), + }); +} + +interface PassportProviderProps extends PropsWithChildren { + authenticationTimeoutinMs?: number; +} +interface IPassportContext { + getData: () => Promise<{ signature: string; data: PassportData } | null>; + getSelectedData: () => Promise<{ + signature: string; + data: PassportData; + } | null>; + getAllData: () => Promise<{ [service: string]: PassportData }>; + getAvailableTypes: () => Promise; + setData: (data: PassportData) => Promise; + getPassportDataAndSecret: () => Promise<{ + data: { passportData: PassportData; secret: string }; + signature: string; + } | null>; + getSelectedPassportDataAndSecret: () => Promise<{ + data: { passportData: PassportData; secret: string }; + signature: string; + } | null>; + clearPassportData: () => Promise; + clearSpecificData: (documentType: string) => Promise; + loadDocumentCatalog: () => Promise; + getAllDocuments: () => Promise<{ + [documentId: string]: { data: PassportData; metadata: DocumentMetadata }; + }>; + setSelectedDocument: (documentId: string) => Promise; + deleteDocument: (documentId: string) => Promise; + migrateFromLegacyStorage: () => Promise; + getCurrentDocumentType: () => Promise; + clearDocumentCatalogForMigrationTesting: () => Promise; + markCurrentDocumentAsRegistered: () => Promise; + updateDocumentRegistrationState: ( + documentId: string, + isRegistered: boolean, + ) => Promise; + checkIfAnyDocumentsNeedMigration: () => Promise; + hasAnyValidRegisteredDocument: () => Promise; + checkAndUpdateRegistrationStates: () => Promise; +} + +export async function markCurrentDocumentAsRegistered(): Promise { + const catalog = await loadDocumentCatalog(); + if (catalog.selectedDocumentId) { + await updateDocumentRegistrationState(catalog.selectedDocumentId, true); + } else { + console.warn('No selected document to mark as registered'); + } +} + +export async function migrateFromLegacyStorage(): Promise { + console.log('Migrating from legacy storage to new architecture...'); + const catalog = await loadDocumentCatalog(); + + // If catalog already has documents, skip migration + if (catalog.documents.length > 0) { + console.log('Migration already completed'); + return; + } + + const legacyServices = [ + 'passportData', + 'mockPassportData', + 'idCardData', + 'mockIdCardData', + ]; + for (const service of legacyServices) { + try { + const passportDataCreds = await Keychain.getGenericPassword({ service }); + if (passportDataCreds !== false) { + const passportData: PassportData = JSON.parse( + passportDataCreds.password, + ); + await storeDocumentWithDeduplication(passportData); + await Keychain.resetGenericPassword({ service }); + console.log(`Migrated document from ${service}`); + } + } catch (error) { + console.log(`Could not migrate from service ${service}:`, error); + } + } + + console.log('Migration completed'); +} export async function reStorePassportDataWithRightCSCA( passportData: PassportData, @@ -791,15 +752,86 @@ export async function reStorePassportDataWithRightCSCA( } } -// Helper function to get current document type from catalog -export async function getCurrentDocumentType(): Promise { - const catalog = await loadDocumentCatalog(); - if (!catalog.selectedDocumentId) return null; +export async function saveDocumentCatalog( + catalog: DocumentCatalog, +): Promise { + await Keychain.setGenericPassword('catalog', JSON.stringify(catalog), { + service: 'documentCatalog', + }); +} - const metadata = catalog.documents.find( - d => d.id === catalog.selectedDocumentId, - ); - return metadata?.documentType || null; +export async function setDefaultDocumentTypeIfNeeded() { + const catalog = await loadDocumentCatalog(); + + if (!catalog.selectedDocumentId && catalog.documents.length > 0) { + await setSelectedDocument(catalog.documents[0].id); + } +} + +export async function setSelectedDocument(documentId: string): Promise { + const catalog = await loadDocumentCatalog(); + const metadata = catalog.documents.find(d => d.id === documentId); + + if (metadata) { + catalog.selectedDocumentId = documentId; + await saveDocumentCatalog(catalog); + } +} + +export async function storeDocumentWithDeduplication( + passportData: PassportData, +): Promise { + const contentHash = calculateContentHash(passportData); + const catalog = await loadDocumentCatalog(); + + // Check for existing document with same content + const existing = catalog.documents.find(d => d.id === contentHash); + if (existing) { + // Even if content hash is the same, we should update the document + // in case metadata (like CSCA) has changed + console.log('Document with same content exists, updating stored data'); + + // Update the stored document with potentially new metadata + await Keychain.setGenericPassword( + contentHash, + JSON.stringify(passportData), + { + service: `document-${contentHash}`, + }, + ); + + // Update selected document to this one + catalog.selectedDocumentId = contentHash; + await saveDocumentCatalog(catalog); + return contentHash; + } + + // Store new document using contentHash as service name + await Keychain.setGenericPassword(contentHash, JSON.stringify(passportData), { + service: `document-${contentHash}`, + }); + + // Add to catalog + const metadata: DocumentMetadata = { + id: contentHash, + documentType: passportData.documentType, + documentCategory: + passportData.documentCategory || + inferDocumentCategory(passportData.documentType), + data: passportData.mrz || '', // Store MRZ for passports/IDs, relevant data for aadhaar + mock: passportData.mock || false, + isRegistered: false, + }; + + catalog.documents.push(metadata); + catalog.selectedDocumentId = contentHash; + await saveDocumentCatalog(catalog); + + return contentHash; +} + +export async function storePassportData(passportData: PassportData) { + await storeDocumentWithDeduplication(passportData); } export async function updateDocumentRegistrationState( @@ -820,38 +852,6 @@ export async function updateDocumentRegistrationState( } } -export async function hasAnyValidRegisteredDocument(): Promise { - try { - const catalog = await loadDocumentCatalog(); - return catalog.documents.some(doc => doc.isRegistered === true); - } catch (error) { - console.error('Error loading document catalog:', error); - return false; - } -} - -export async function checkAndUpdateRegistrationStates(): Promise { - // Lazy import to avoid circular dependency - const { checkAndUpdateRegistrationStates: validateDocCheckAndUpdate } = - await import('../utils/proving/validateDocument'); - return validateDocCheckAndUpdate(); -} - -export async function markCurrentDocumentAsRegistered(): Promise { - const catalog = await loadDocumentCatalog(); - if (catalog.selectedDocumentId) { - await updateDocumentRegistrationState(catalog.selectedDocumentId, true); - } else { - console.warn('No selected document to mark as registered'); - } -} - -export async function checkIfAnyDocumentsNeedMigration(): Promise { - try { - const catalog = await loadDocumentCatalog(); - return catalog.documents.some(doc => doc.isRegistered === undefined); - } catch (error) { - console.warn('Error checking if documents need migration:', error); - return false; - } -} +export const usePassport = () => { + return useContext(PassportContext); +}; diff --git a/app/src/providers/remoteConfigProvider.tsx b/app/src/providers/remoteConfigProvider.tsx index c3b52c98c..f3b5bc81d 100644 --- a/app/src/providers/remoteConfigProvider.tsx +++ b/app/src/providers/remoteConfigProvider.tsx @@ -14,8 +14,6 @@ const RemoteConfigContext = createContext({ error: null, }); -export const useRemoteConfig = () => useContext(RemoteConfigContext); - export const RemoteConfigProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { @@ -44,3 +42,5 @@ export const RemoteConfigProvider: React.FC<{ children: React.ReactNode }> = ({ ); }; + +export const useRemoteConfig = () => useContext(RemoteConfigContext); diff --git a/app/src/screens/dev/DevFeatureFlagsScreen.tsx b/app/src/screens/dev/DevFeatureFlagsScreen.tsx index f69c47c45..d1ed07b6a 100644 --- a/app/src/screens/dev/DevFeatureFlagsScreen.tsx +++ b/app/src/screens/dev/DevFeatureFlagsScreen.tsx @@ -11,9 +11,9 @@ import { YStack, } from 'tamagui'; +import type { FeatureFlagValue } from '../../RemoteConfig'; import { clearAllLocalOverrides, - FeatureFlagValue, getAllFeatureFlags, refreshRemoteConfig, setLocalOverride, diff --git a/app/src/screens/dev/DevSettingsScreen.tsx b/app/src/screens/dev/DevSettingsScreen.tsx index 2ed9e8678..48c6f6e56 100644 --- a/app/src/screens/dev/DevSettingsScreen.tsx +++ b/app/src/screens/dev/DevSettingsScreen.tsx @@ -1,24 +1,22 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { Check, ChevronDown, Eraser } from '@tamagui/lucide-icons'; -import React, { - PropsWithChildren, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; -import { Alert, Platform, StyleProp, TextInput } from 'react-native'; +import type { PropsWithChildren } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import type { StyleProp } from 'react-native'; +import { Alert, Platform, TextInput } from 'react-native'; import { Adapt, Button, Select, Sheet, Text, XStack, YStack } from 'tamagui'; -import { RootStackParamList } from '../../navigation'; +import type { RootStackParamList } from '../../navigation'; import { unsafe_clearSecrets, unsafe_getPrivateKey, } from '../../providers/authProvider'; import { usePassport } from '../../providers/passportDataProvider'; import { textBlack } from '../../utils/colors'; + +import { useNavigation } from '@react-navigation/native'; +import { Check, ChevronDown, Eraser } from '@tamagui/lucide-icons'; + interface DevSettingsScreenProps extends PropsWithChildren { color?: string; width?: number; diff --git a/app/src/screens/dev/MockDataScreen.tsx b/app/src/screens/dev/MockDataScreen.tsx index 8ec0205ec..7c7c9c503 100644 --- a/app/src/screens/dev/MockDataScreen.tsx +++ b/app/src/screens/dev/MockDataScreen.tsx @@ -1,15 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { countryCodes } from '@selfxyz/common/constants/core'; -import type { IdDocInput } from '@selfxyz/common/utils'; -import { getSKIPEM } from '@selfxyz/common/utils/csca'; -import { - generateMockDSC, - genMockIdDoc, - initPassportDataParsing, -} from '@selfxyz/common/utils/passports'; -import { ChevronDown, Minus, Plus, X } from '@tamagui/lucide-icons'; import { flag } from 'country-emoji'; import getCountryISO2 from 'country-iso-3-to-2'; import React, { useCallback, useState } from 'react'; @@ -29,6 +19,15 @@ import { YStack, } from 'tamagui'; +import { countryCodes } from '@selfxyz/common/constants'; +import type { IdDocInput } from '@selfxyz/common/utils'; +import { getSKIPEM } from '@selfxyz/common/utils/csca'; +import { + generateMockDSC, + genMockIdDoc, + initPassportDataParsing, +} from '@selfxyz/common/utils/passports'; + import { PrimaryButton } from '../../components/buttons/PrimaryButton'; import { SecondaryButton } from '../../components/buttons/SecondaryButton'; import ButtonsContainer from '../../components/ButtonsContainer'; @@ -46,6 +45,9 @@ import { import { extraYPadding } from '../../utils/constants'; import { buttonTap, selectionChange } from '../../utils/haptic'; +import { useNavigation } from '@react-navigation/native'; +import { ChevronDown, Minus, Plus, X } from '@tamagui/lucide-icons'; + const { trackEvent } = analytics(); interface MockDataScreenProps {} diff --git a/app/src/screens/dev/MockDataScreenDeepLink.tsx b/app/src/screens/dev/MockDataScreenDeepLink.tsx index 4a46caf2c..9e89ddf90 100644 --- a/app/src/screens/dev/MockDataScreenDeepLink.tsx +++ b/app/src/screens/dev/MockDataScreenDeepLink.tsx @@ -1,9 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { countryCodes } from '@selfxyz/common/constants/core'; -import type { IdDocInput } from '@selfxyz/common/utils'; -import { genMockIdDocAndInitDataParsing } from '@selfxyz/common/utils/passports'; import { flag } from 'country-emoji'; import getCountryISO2 from 'country-iso-3-to-2'; import React, { useCallback, useEffect, useState } from 'react'; @@ -11,6 +7,10 @@ import { ActivityIndicator, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ScrollView, Text, XStack, YStack } from 'tamagui'; +import { countryCodes } from '@selfxyz/common/constants'; +import type { IdDocInput } from '@selfxyz/common/utils'; +import { genMockIdDocAndInitDataParsing } from '@selfxyz/common/utils/passports'; + import { PrimaryButton } from '../../components/buttons/PrimaryButton'; import ButtonsContainer from '../../components/ButtonsContainer'; import { BodyText } from '../../components/typography/BodyText'; @@ -22,6 +22,8 @@ import useUserStore from '../../stores/userStore'; import { black, borderColor, white } from '../../utils/colors'; import { extraYPadding } from '../../utils/constants'; +import { useNavigation } from '@react-navigation/native'; + const MockDataScreenDeepLink: React.FC = () => { const navigation = useNavigation(); diff --git a/app/src/screens/home/DisclaimerScreen.tsx b/app/src/screens/home/DisclaimerScreen.tsx index 247199855..6f4e87d69 100644 --- a/app/src/screens/home/DisclaimerScreen.tsx +++ b/app/src/screens/home/DisclaimerScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import LottieView from 'lottie-react-native'; import React, { useEffect } from 'react'; import { StyleSheet } from 'react-native'; @@ -16,6 +15,8 @@ import { useSettingStore } from '../../stores/settingStore'; import { black, white } from '../../utils/colors'; import { confirmTap, notificationWarning } from '../../utils/haptic'; +import { useNavigation } from '@react-navigation/native'; + const DisclaimerScreen: React.FC = () => { const navigation = useNavigation(); const { dismissPrivacyNote } = useSettingStore(); diff --git a/app/src/screens/home/HomeScreen.tsx b/app/src/screens/home/HomeScreen.tsx index 9f68608b7..774850efa 100644 --- a/app/src/screens/home/HomeScreen.tsx +++ b/app/src/screens/home/HomeScreen.tsx @@ -1,10 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { - useFocusEffect, - useNavigation, - usePreventRemove, -} from '@react-navigation/native'; import React, { useCallback } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Button, styled, YStack } from 'tamagui'; @@ -28,6 +23,13 @@ import { white, } from '../../utils/colors'; import { extraYPadding } from '../../utils/constants'; + +import { + useFocusEffect, + useNavigation, + usePreventRemove, +} from '@react-navigation/native'; + const ScanButton = styled(Button, { borderRadius: 20, width: 90, diff --git a/app/src/screens/home/ProofHistoryDetailScreen.tsx b/app/src/screens/home/ProofHistoryDetailScreen.tsx index d6738e3e9..983b207bb 100644 --- a/app/src/screens/home/ProofHistoryDetailScreen.tsx +++ b/app/src/screens/home/ProofHistoryDetailScreen.tsx @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { CheckSquare2, Info, Wallet } from '@tamagui/lucide-icons'; import React, { useMemo } from 'react'; import { ScrollView, StyleSheet } from 'react-native'; import { Card, Image, Text, XStack, YStack } from 'tamagui'; -import { ProofHistory, ProofStatus } from '../../stores/proof-types'; +import type { ProofHistory } from '../../stores/proof-types'; +import { ProofStatus } from '../../stores/proof-types'; import { black, blue100, @@ -21,6 +21,8 @@ import { } from '../../utils/colors'; import { advercase, dinot, plexMono } from '../../utils/fonts'; +import { CheckSquare2, Info, Wallet } from '@tamagui/lucide-icons'; + type ProofHistoryDetailScreenProps = { route: { params: { diff --git a/app/src/screens/home/ProofHistoryScreen.tsx b/app/src/screens/home/ProofHistoryScreen.tsx index b408d8e6e..c2575cf7a 100644 --- a/app/src/screens/home/ProofHistoryScreen.tsx +++ b/app/src/screens/home/ProofHistoryScreen.tsx @@ -1,7 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { CheckSquare2, Wallet, XCircle } from '@tamagui/lucide-icons'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, @@ -13,7 +11,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Card, Image, Text, View, XStack, YStack } from 'tamagui'; import { BodyText } from '../../components/typography/BodyText'; -import { ProofHistory, ProofStatus } from '../../stores/proof-types'; +import type { ProofHistory } from '../../stores/proof-types'; +import { ProofStatus } from '../../stores/proof-types'; import { useProofHistoryStore } from '../../stores/proofHistoryStore'; import { black, @@ -29,6 +28,9 @@ import { import { extraYPadding } from '../../utils/constants'; import { dinot } from '../../utils/fonts'; +import { useNavigation } from '@react-navigation/native'; +import { CheckSquare2, Wallet, XCircle } from '@tamagui/lucide-icons'; + type Section = { title: string; data: ProofHistory[]; diff --git a/app/src/screens/misc/LoadingScreen.tsx b/app/src/screens/misc/LoadingScreen.tsx index c0cdf41ed..cb2af7640 100644 --- a/app/src/screens/misc/LoadingScreen.tsx +++ b/app/src/screens/misc/LoadingScreen.tsx @@ -1,13 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { StaticScreenProps, useIsFocused } from '@react-navigation/native'; -import type { PassportData } from '@selfxyz/common/types'; import LottieView from 'lottie-react-native'; import React, { useEffect, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Text, YStack } from 'tamagui'; +import type { PassportData } from '@selfxyz/common/types'; + import failAnimation from '../../assets/animations/loading/fail.json'; import proveLoadingAnimation from '../../assets/animations/loading/prove.json'; import successAnimation from '../../assets/animations/loading/success.json'; @@ -19,10 +19,11 @@ import { advercase, dinot } from '../../utils/fonts'; import { loadingScreenProgress } from '../../utils/haptic'; import { setupNotifications } from '../../utils/notifications/notificationService'; import { getLoadingScreenText } from '../../utils/proving/loadingScreenStateText'; -import { - ProvingStateType, - useProvingStore, -} from '../../utils/proving/provingMachine'; +import type { ProvingStateType } from '../../utils/proving/provingMachine'; +import { useProvingStore } from '../../utils/proving/provingMachine'; + +import type { StaticScreenProps } from '@react-navigation/native'; +import { useIsFocused } from '@react-navigation/native'; type LoadingScreenProps = StaticScreenProps<{}>; diff --git a/app/src/screens/misc/ModalScreen.tsx b/app/src/screens/misc/ModalScreen.tsx index 2275a3593..df750f170 100644 --- a/app/src/screens/misc/ModalScreen.tsx +++ b/app/src/screens/misc/ModalScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { StaticScreenProps, useNavigation } from '@react-navigation/native'; import React, { useCallback } from 'react'; import { styled, View, XStack, YStack } from 'tamagui'; @@ -17,6 +16,9 @@ import { unregisterModalCallbacks, } from '../../utils/modalCallbackRegistry'; +import type { StaticScreenProps } from '@react-navigation/native'; +import { useNavigation } from '@react-navigation/native'; + const ModalBackDrop = styled(View, { display: 'flex', alignItems: 'center', @@ -29,6 +31,11 @@ const ModalBackDrop = styled(View, { height: '100%', }); +export interface ModalNavigationParams + extends Omit { + callbackId: number; +} + export interface ModalParams extends Record { titleText: string; bodyText: string; @@ -39,11 +46,6 @@ export interface ModalParams extends Record { preventDismiss?: boolean; } -export interface ModalNavigationParams - extends Omit { - callbackId: number; -} - interface ModalScreenProps extends StaticScreenProps {} const ModalScreen: React.FC = ({ route: { params } }) => { diff --git a/app/src/screens/misc/SplashScreen.tsx b/app/src/screens/misc/SplashScreen.tsx index 7a0be9441..353d676ec 100644 --- a/app/src/screens/misc/SplashScreen.tsx +++ b/app/src/screens/misc/SplashScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import LottieView from 'lottie-react-native'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { StyleSheet } from 'react-native'; @@ -18,6 +17,8 @@ import { useSettingStore } from '../../stores/settingStore'; import { black } from '../../utils/colors'; import { impactLight } from '../../utils/haptic'; +import { useNavigation } from '@react-navigation/native'; + const SplashScreen: React.FC = ({}) => { const navigation = useNavigation(); const { checkBiometricsAvailable } = useAuth(); diff --git a/app/src/screens/passport/NFCMethodSelectionScreen.tsx b/app/src/screens/passport/NFCMethodSelectionScreen.tsx index e00d42fe7..00f9f3f61 100644 --- a/app/src/screens/passport/NFCMethodSelectionScreen.tsx +++ b/app/src/screens/passport/NFCMethodSelectionScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import React, { useState } from 'react'; import { Platform, ScrollView } from 'react-native'; import { Input, YStack } from 'tamagui'; @@ -15,6 +14,8 @@ import { ExpandableBottomLayout } from '../../layouts/ExpandableBottomLayout'; import useUserStore from '../../stores/userStore'; import { white } from '../../utils/colors'; +import { useNavigation } from '@react-navigation/native'; + type NFCParams = { skipPACE?: boolean; canNumber?: string; diff --git a/app/src/screens/passport/PassportCameraScreen.tsx b/app/src/screens/passport/PassportCameraScreen.tsx index b2d69a678..ddb40ab98 100644 --- a/app/src/screens/passport/PassportCameraScreen.tsx +++ b/app/src/screens/passport/PassportCameraScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useIsFocused, useNavigation } from '@react-navigation/native'; import LottieView from 'lottie-react-native'; import React, { useCallback, useRef } from 'react'; import { Platform, StyleSheet } from 'react-native'; @@ -8,10 +7,8 @@ import { View, XStack, YStack } from 'tamagui'; import passportScanAnimation from '../../assets/animations/passport_scan.json'; import { SecondaryButton } from '../../components/buttons/SecondaryButton'; -import { - PassportCamera, - PassportCameraProps, -} from '../../components/native/PassportCamera'; +import type { PassportCameraProps } from '../../components/native/PassportCamera'; +import { PassportCamera } from '../../components/native/PassportCamera'; import Additional from '../../components/typography/Additional'; import Description from '../../components/typography/Description'; import { Title } from '../../components/typography/Title'; @@ -26,6 +23,8 @@ import { dinot } from '../../utils/fonts'; import { hasAnyValidRegisteredDocument } from '../../utils/proving/validateDocument'; import { checkScannedInfo, formatDateToYYMMDD } from '../../utils/utils'; +import { useIsFocused, useNavigation } from '@react-navigation/native'; + interface PassportNFCScanScreen {} const { trackEvent } = analytics(); diff --git a/app/src/screens/passport/PassportCameraTroubleScreen.tsx b/app/src/screens/passport/PassportCameraTroubleScreen.tsx index bf0619ddd..e9e10fafb 100644 --- a/app/src/screens/passport/PassportCameraTroubleScreen.tsx +++ b/app/src/screens/passport/PassportCameraTroubleScreen.tsx @@ -2,7 +2,8 @@ import React, { useEffect } from 'react'; -import Tips, { TipProps } from '../../components/Tips'; +import type { TipProps } from '../../components/Tips'; +import Tips from '../../components/Tips'; import { Caption } from '../../components/typography/Caption'; import useHapticNavigation from '../../hooks/useHapticNavigation'; import Activity from '../../images/icons/activity.svg'; diff --git a/app/src/screens/passport/PassportNFCScanScreen.tsx b/app/src/screens/passport/PassportNFCScanScreen.tsx index ba21fa893..1bb16dd6d 100644 --- a/app/src/screens/passport/PassportNFCScanScreen.tsx +++ b/app/src/screens/passport/PassportNFCScanScreen.tsx @@ -1,14 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { - useFocusEffect, - useNavigation, - useRoute, -} from '@react-navigation/native'; -import type { PassportData } from '@selfxyz/common/types'; -import { getSKIPEM } from '@selfxyz/common/utils/csca'; -import { initPassportDataParsing } from '@selfxyz/common/utils/passports'; -import { CircleHelp } from '@tamagui/lucide-icons'; import LottieView from 'lottie-react-native'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { @@ -22,6 +13,10 @@ import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import NfcManager from 'react-native-nfc-manager'; import { Button, Image, XStack } from 'tamagui'; +import type { PassportData } from '@selfxyz/common/types'; +import { getSKIPEM } from '@selfxyz/common/utils/csca'; +import { initPassportDataParsing } from '@selfxyz/common/utils/passports'; + import passportVerifyAnimation from '../../assets/animations/passport_verify.json'; import { PrimaryButton } from '../../components/buttons/PrimaryButton'; import { SecondaryButton } from '../../components/buttons/SecondaryButton'; @@ -48,6 +43,13 @@ import { registerModalCallbacks } from '../../utils/modalCallbackRegistry'; import { parseScanResponse, scan } from '../../utils/nfcScanner'; import { hasAnyValidRegisteredDocument } from '../../utils/proving/validateDocument'; +import { + useFocusEffect, + useNavigation, + useRoute, +} from '@react-navigation/native'; +import { CircleHelp } from '@tamagui/lucide-icons'; + const { trackEvent } = analytics(); interface PassportNFCScanScreenProps {} diff --git a/app/src/screens/passport/PassportNFCTroubleScreen.tsx b/app/src/screens/passport/PassportNFCTroubleScreen.tsx index 7849a7dfb..0b20c0d66 100644 --- a/app/src/screens/passport/PassportNFCTroubleScreen.tsx +++ b/app/src/screens/passport/PassportNFCTroubleScreen.tsx @@ -4,7 +4,8 @@ import React, { useEffect } from 'react'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { YStack } from 'tamagui'; -import Tips, { TipProps } from '../../components/Tips'; +import type { TipProps } from '../../components/Tips'; +import Tips from '../../components/Tips'; import { Caption } from '../../components/typography/Caption'; import useHapticNavigation from '../../hooks/useHapticNavigation'; import SimpleScrolledTitleLayout from '../../layouts/SimpleScrolledTitleLayout'; diff --git a/app/src/screens/passport/PassportOnboardingScreen.tsx b/app/src/screens/passport/PassportOnboardingScreen.tsx index 519550b20..644dbd4c0 100644 --- a/app/src/screens/passport/PassportOnboardingScreen.tsx +++ b/app/src/screens/passport/PassportOnboardingScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import LottieView from 'lottie-react-native'; import React, { useEffect, useRef } from 'react'; import { StyleSheet } from 'react-native'; @@ -20,6 +19,8 @@ import { ExpandableBottomLayout } from '../../layouts/ExpandableBottomLayout'; import { black, slate100, white } from '../../utils/colors'; import { impactLight } from '../../utils/haptic'; +import { useNavigation } from '@react-navigation/native'; + interface PassportOnboardingScreenProps {} const PassportOnboardingScreen: React.FC< diff --git a/app/src/screens/prove/ConfirmBelongingScreen.tsx b/app/src/screens/prove/ConfirmBelongingScreen.tsx index 10c59e9e7..bea285c73 100644 --- a/app/src/screens/prove/ConfirmBelongingScreen.tsx +++ b/app/src/screens/prove/ConfirmBelongingScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { StaticScreenProps, usePreventRemove } from '@react-navigation/native'; import LottieView from 'lottie-react-native'; import React, { useEffect, useState } from 'react'; import { ActivityIndicator, View } from 'react-native'; @@ -22,6 +21,9 @@ import { import { useProvingStore } from '../../utils/proving/provingMachine'; import { styles } from './ProofRequestStatusScreen'; +import type { StaticScreenProps } from '@react-navigation/native'; +import { usePreventRemove } from '@react-navigation/native'; + type ConfirmBelongingScreenProps = StaticScreenProps<{}>; const { trackEvent } = analytics(); diff --git a/app/src/screens/prove/ProofRequestStatusScreen.tsx b/app/src/screens/prove/ProofRequestStatusScreen.tsx index a802d0ae6..908dd5c0d 100644 --- a/app/src/screens/prove/ProofRequestStatusScreen.tsx +++ b/app/src/screens/prove/ProofRequestStatusScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useIsFocused } from '@react-navigation/native'; import LottieView from 'lottie-react-native'; import React, { useEffect, useState } from 'react'; import { Linking, StyleSheet, View } from 'react-native'; @@ -30,6 +29,8 @@ import { } from '../../utils/haptic'; import { useProvingStore } from '../../utils/proving/provingMachine'; +import { useIsFocused } from '@react-navigation/native'; + const { trackEvent } = analytics(); const SuccessScreen: React.FC = () => { diff --git a/app/src/screens/prove/ProveScreen.tsx b/app/src/screens/prove/ProveScreen.tsx index 29ebd014a..3be999461 100644 --- a/app/src/screens/prove/ProveScreen.tsx +++ b/app/src/screens/prove/ProveScreen.tsx @@ -1,9 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useIsFocused, useNavigation } from '@react-navigation/native'; -import type { SelfAppDisclosureConfig } from '@selfxyz/common/utils/appType'; -import { formatEndpoint } from '@selfxyz/common/utils/scope'; -import { Eye, EyeOff } from '@tamagui/lucide-icons'; import LottieView from 'lottie-react-native'; import React, { useCallback, @@ -12,16 +8,17 @@ import React, { useRef, useState, } from 'react'; -import { +import type { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, - ScrollView, - StyleSheet, - TouchableOpacity, } from 'react-native'; +import { ScrollView, StyleSheet, TouchableOpacity } from 'react-native'; import { Image, Text, View, XStack, YStack } from 'tamagui'; +import type { SelfAppDisclosureConfig } from '@selfxyz/common/utils/appType'; +import { formatEndpoint } from '@selfxyz/common/utils/scope'; + import miscAnimation from '../../assets/animations/loading/misc.json'; import { HeldPrimaryButtonProveScreen } from '../../components/buttons/HeldPrimaryButtonProveScreen'; import Disclosures from '../../components/Disclosures'; @@ -39,6 +36,9 @@ import { formatUserId } from '../../utils/formatUserId'; import { buttonTap } from '../../utils/haptic'; import { useProvingStore } from '../../utils/proving/provingMachine'; +import { useIsFocused, useNavigation } from '@react-navigation/native'; +import { Eye, EyeOff } from '@tamagui/lucide-icons'; + const { trackEvent } = analytics(); const ProveScreen: React.FC = () => { diff --git a/app/src/screens/prove/QRCodeTroubleScreen.tsx b/app/src/screens/prove/QRCodeTroubleScreen.tsx index 1aa8f85b8..acebfcc96 100644 --- a/app/src/screens/prove/QRCodeTroubleScreen.tsx +++ b/app/src/screens/prove/QRCodeTroubleScreen.tsx @@ -2,7 +2,8 @@ import React, { useEffect } from 'react'; -import Tips, { TipProps } from '../../components/Tips'; +import type { TipProps } from '../../components/Tips'; +import Tips from '../../components/Tips'; import { Caption } from '../../components/typography/Caption'; import useHapticNavigation from '../../hooks/useHapticNavigation'; import SimpleScrolledTitleLayout from '../../layouts/SimpleScrolledTitleLayout'; diff --git a/app/src/screens/prove/ViewFinderScreen.tsx b/app/src/screens/prove/ViewFinderScreen.tsx index 083cc750b..2dc80c7a3 100644 --- a/app/src/screens/prove/ViewFinderScreen.tsx +++ b/app/src/screens/prove/ViewFinderScreen.tsx @@ -1,10 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { - useFocusEffect, - useIsFocused, - useNavigation, -} from '@react-navigation/native'; import LottieView from 'lottie-react-native'; import React, { useCallback, useState } from 'react'; import { StyleSheet } from 'react-native'; @@ -12,10 +7,8 @@ import { View, XStack, YStack } from 'tamagui'; import qrScanAnimation from '../../assets/animations/qr_scan.json'; import { SecondaryButton } from '../../components/buttons/SecondaryButton'; -import { - QRCodeScannerView, - QRCodeScannerViewProps, -} from '../../components/native/QRCodeScanner'; +import type { QRCodeScannerViewProps } from '../../components/native/QRCodeScanner'; +import { QRCodeScannerView } from '../../components/native/QRCodeScanner'; import Additional from '../../components/typography/Additional'; import Description from '../../components/typography/Description'; import { Title } from '../../components/typography/Title'; @@ -29,6 +22,12 @@ import analytics from '../../utils/analytics'; import { black, slate800, white } from '../../utils/colors'; import { parseAndValidateUrlParams } from '../../utils/deeplinks'; +import { + useFocusEffect, + useIsFocused, + useNavigation, +} from '@react-navigation/native'; + interface QRCodeViewFinderScreenProps {} const { trackEvent } = analytics(); diff --git a/app/src/screens/recovery/AccountRecoveryChoiceScreen.tsx b/app/src/screens/recovery/AccountRecoveryChoiceScreen.tsx index c370a5bf4..3fb654a17 100644 --- a/app/src/screens/recovery/AccountRecoveryChoiceScreen.tsx +++ b/app/src/screens/recovery/AccountRecoveryChoiceScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import React, { useCallback, useState } from 'react'; import { Separator, View, XStack, YStack } from 'tamagui'; @@ -25,6 +24,8 @@ import { STORAGE_NAME, useBackupMnemonic } from '../../utils/cloudBackup'; import { black, slate500, slate600, white } from '../../utils/colors'; import { isUserRegisteredWithAlternativeCSCA } from '../../utils/proving/validateDocument'; +import { useNavigation } from '@react-navigation/native'; + const { trackEvent } = analytics(); interface AccountRecoveryChoiceScreenProps {} diff --git a/app/src/screens/recovery/AccountVerifiedSuccessScreen.tsx b/app/src/screens/recovery/AccountVerifiedSuccessScreen.tsx index aa3e33d3f..ddd55ec9a 100644 --- a/app/src/screens/recovery/AccountVerifiedSuccessScreen.tsx +++ b/app/src/screens/recovery/AccountVerifiedSuccessScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; import LottieView from 'lottie-react-native'; import React from 'react'; import { YStack } from 'tamagui'; @@ -15,6 +14,8 @@ import { black, white } from '../../utils/colors'; import { buttonTap } from '../../utils/haptic'; import { styles } from '../prove/ProofRequestStatusScreen'; +import { useNavigation } from '@react-navigation/native'; + const AccountVerifiedSuccessScreen: React.FC = ({}) => { const navigation = useNavigation(); diff --git a/app/src/screens/recovery/RecoverWithPhraseScreen.tsx b/app/src/screens/recovery/RecoverWithPhraseScreen.tsx index 46363e6ee..acefe7b62 100644 --- a/app/src/screens/recovery/RecoverWithPhraseScreen.tsx +++ b/app/src/screens/recovery/RecoverWithPhraseScreen.tsx @@ -1,7 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import Clipboard from '@react-native-clipboard/clipboard'; -import { useNavigation } from '@react-navigation/native'; import { ethers } from 'ethers'; import React, { useCallback, useState } from 'react'; import { Keyboard, StyleSheet } from 'react-native'; @@ -27,6 +25,9 @@ import { } from '../../utils/colors'; import { isUserRegisteredWithAlternativeCSCA } from '../../utils/proving/validateDocument'; +import Clipboard from '@react-native-clipboard/clipboard'; +import { useNavigation } from '@react-navigation/native'; + interface RecoverWithPhraseScreenProps {} const RecoverWithPhraseScreen: React.FC< diff --git a/app/src/screens/settings/CloudBackupScreen.tsx b/app/src/screens/settings/CloudBackupScreen.tsx index c3d65a28d..3edefefef 100644 --- a/app/src/screens/settings/CloudBackupScreen.tsx +++ b/app/src/screens/settings/CloudBackupScreen.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { StaticScreenProps, useNavigation } from '@react-navigation/native'; import React, { useCallback, useMemo, useState } from 'react'; import { YStack } from 'tamagui'; @@ -14,7 +13,7 @@ import { BackupEvents } from '../../consts/analytics'; import { useModal } from '../../hooks/useModal'; import Cloud from '../../images/icons/logo_cloud_backup.svg'; import { ExpandableBottomLayout } from '../../layouts/ExpandableBottomLayout'; -import { RootStackParamList } from '../../navigation'; +import type { RootStackParamList } from '../../navigation'; import { useAuth } from '../../providers/authProvider'; import { useSettingStore } from '../../stores/settingStore'; import analytics from '../../utils/analytics'; @@ -22,6 +21,9 @@ import { STORAGE_NAME, useBackupMnemonic } from '../../utils/cloudBackup'; import { black, white } from '../../utils/colors'; import { buttonTap, confirmTap } from '../../utils/haptic'; +import type { StaticScreenProps } from '@react-navigation/native'; +import { useNavigation } from '@react-navigation/native'; + const { trackEvent } = analytics(); type NextScreen = keyof Pick; diff --git a/app/src/screens/settings/ManageDocumentsScreen.tsx b/app/src/screens/settings/ManageDocumentsScreen.tsx index c9c44ee55..4727c585c 100644 --- a/app/src/screens/settings/ManageDocumentsScreen.tsx +++ b/app/src/screens/settings/ManageDocumentsScreen.tsx @@ -1,7 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { Check, Eraser } from '@tamagui/lucide-icons'; import React, { useCallback, useEffect, useState } from 'react'; import { Alert } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -17,6 +15,9 @@ import { borderColor, textBlack, white } from '../../utils/colors'; import { extraYPadding } from '../../utils/constants'; import { impactLight } from '../../utils/haptic'; +import { useNavigation } from '@react-navigation/native'; +import { Check, Eraser } from '@tamagui/lucide-icons'; + const { trackEvent } = analytics(); interface ManageDocumentsScreenProps {} diff --git a/app/src/screens/settings/PassportDataInfoScreen.tsx b/app/src/screens/settings/PassportDataInfoScreen.tsx index 9c6503d06..ebc14c113 100644 --- a/app/src/screens/settings/PassportDataInfoScreen.tsx +++ b/app/src/screens/settings/PassportDataInfoScreen.tsx @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useFocusEffect } from '@react-navigation/native'; -import type { PassportMetadata } from '@selfxyz/common/types'; import React, { useCallback, useState } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ScrollView, Separator, XStack, YStack } from 'tamagui'; +import type { PassportMetadata } from '@selfxyz/common/types'; + import { Caption } from '../../components/typography/Caption'; import { DocumentEvents } from '../../consts/analytics'; import { usePassport } from '../../providers/passportDataProvider'; @@ -13,6 +13,8 @@ import analytics from '../../utils/analytics'; import { black, slate200, white } from '../../utils/colors'; import { extraYPadding } from '../../utils/constants'; +import { useFocusEffect } from '@react-navigation/native'; + const { trackEvent } = analytics(); // TODO clarify if we need more/less keys to be displayed diff --git a/app/src/screens/settings/SettingsScreen.tsx b/app/src/screens/settings/SettingsScreen.tsx index 684795279..29f2ff818 100644 --- a/app/src/screens/settings/SettingsScreen.tsx +++ b/app/src/screens/settings/SettingsScreen.tsx @@ -1,12 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { Bug, FileText } from '@tamagui/lucide-icons'; -import React, { PropsWithChildren, useCallback, useMemo } from 'react'; +import type { PropsWithChildren } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Linking, Platform, Share } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { SvgProps } from 'react-native-svg'; +import type { SvgProps } from 'react-native-svg'; import { Button, ScrollView, View, XStack, YStack } from 'tamagui'; import { version } from '../../../package.json'; @@ -28,7 +27,7 @@ import ShareIcon from '../../images/icons/share.svg'; import Star from '../../images/icons/star.svg'; import Telegram from '../../images/icons/telegram.svg'; import Web from '../../images/icons/webpage.svg'; -import { RootStackParamList } from '../../navigation'; +import type { RootStackParamList } from '../../navigation'; import { useSettingStore } from '../../stores/settingStore'; import { amber500, @@ -41,6 +40,9 @@ import { extraYPadding } from '../../utils/constants'; import { impactLight } from '../../utils/haptic'; import { getCountry, getLocales, getTimeZone } from '../../utils/locale'; +import { useNavigation } from '@react-navigation/native'; +import { Bug, FileText } from '@tamagui/lucide-icons'; + interface SettingsScreenProps {} interface MenuButtonProps extends PropsWithChildren { Icon: React.FC; diff --git a/app/src/stores/database.ts b/app/src/stores/database.ts index d771e5bcc..59d447627 100644 --- a/app/src/stores/database.ts +++ b/app/src/stores/database.ts @@ -2,12 +2,8 @@ import SQLite from 'react-native-sqlite-storage'; -import { - ProofDB, - ProofDBResult, - ProofHistory, - ProofStatus, -} from './proof-types'; +import type { ProofDB, ProofDBResult, ProofHistory } from './proof-types'; +import { ProofStatus } from './proof-types'; const PAGE_SIZE = 20; const DB_NAME = 'proof_history.db'; diff --git a/app/src/stores/database.web.ts b/app/src/stores/database.web.ts index 2bbd192fd..926047412 100644 --- a/app/src/stores/database.web.ts +++ b/app/src/stores/database.web.ts @@ -1,11 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { - ProofDB, - ProofDBResult, - ProofHistory, - ProofStatus, -} from './proof-types'; +import type { ProofDB, ProofDBResult, ProofHistory } from './proof-types'; +import { ProofStatus } from './proof-types'; export const DB_NAME = 'proof_history_db'; const STORE_NAME = 'proof_history'; diff --git a/app/src/stores/proof-types.ts b/app/src/stores/proof-types.ts index 403bdd6ba..89b3a1286 100644 --- a/app/src/stores/proof-types.ts +++ b/app/src/stores/proof-types.ts @@ -2,6 +2,31 @@ import type { EndpointType, UserIdType } from '@selfxyz/common/utils'; +export interface ProofDB { + updateStaleProofs: ( + updateProofStatus: (id: string, status: ProofStatus) => Promise, + ) => Promise; + getPendingProofs: () => Promise; + getHistory: (page?: number) => Promise; + init: () => Promise; + insertProof: ( + proof: Omit, + ) => Promise<{ id: string; timestamp: number; rowsAffected: number }>; + updateProofStatus: ( + status: ProofStatus, + errorCode: string | undefined, + errorReason: string | undefined, + sessionId: string, + ) => Promise; +} + +export interface ProofDBResult { + rows: ProofHistory[]; + rowsAffected?: number; + insertId?: string; + total_count?: number; +} + export interface ProofHistory { id: string; appName: string; @@ -22,28 +47,3 @@ export enum ProofStatus { SUCCESS = 'success', FAILURE = 'failure', } - -export interface ProofDBResult { - rows: ProofHistory[]; - rowsAffected?: number; - insertId?: string; - total_count?: number; -} - -export interface ProofDB { - updateStaleProofs: ( - updateProofStatus: (id: string, status: ProofStatus) => Promise, - ) => Promise; - getPendingProofs: () => Promise; - getHistory: (page?: number) => Promise; - init: () => Promise; - insertProof: ( - proof: Omit, - ) => Promise<{ id: string; timestamp: number; rowsAffected: number }>; - updateProofStatus: ( - status: ProofStatus, - errorCode: string | undefined, - errorReason: string | undefined, - sessionId: string, - ) => Promise; -} diff --git a/app/src/stores/proofHistoryStore.ts b/app/src/stores/proofHistoryStore.ts index d655e95a3..c9d834cd0 100644 --- a/app/src/stores/proofHistoryStore.ts +++ b/app/src/stores/proofHistoryStore.ts @@ -1,11 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { WS_DB_RELAYER } from '@selfxyz/common/constants'; import { io } from 'socket.io-client'; import { create } from 'zustand'; +import { WS_DB_RELAYER } from '@selfxyz/common/constants'; + import { database } from './database'; -import { ProofHistory, ProofStatus } from './proof-types'; +import type { ProofHistory } from './proof-types'; +import { ProofStatus } from './proof-types'; interface ProofHistoryState { proofHistory: ProofHistory[]; diff --git a/app/src/stores/protocolStore.ts b/app/src/stores/protocolStore.ts index c2d9807f9..2090864f4 100644 --- a/app/src/stores/protocolStore.ts +++ b/app/src/stores/protocolStore.ts @@ -1,5 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import { create } from 'zustand'; + import { API_URL, API_URL_STAGING, @@ -16,7 +18,6 @@ import { IDENTITY_TREE_URL_STAGING, IDENTITY_TREE_URL_STAGING_ID_CARD, } from '@selfxyz/common/constants'; -import { create } from 'zustand'; import { fetchOfacTrees } from '../utils/ofac'; diff --git a/app/src/stores/selfAppStore.tsx b/app/src/stores/selfAppStore.tsx index 3df5e8a6a..92c75520a 100644 --- a/app/src/stores/selfAppStore.tsx +++ b/app/src/stores/selfAppStore.tsx @@ -1,10 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { WS_DB_RELAYER } from '@selfxyz/common/constants/core'; -import type { SelfApp } from '@selfxyz/common/utils/appType'; -import socketIo, { Socket } from 'socket.io-client'; +import type { Socket } from 'socket.io-client'; +import socketIo from 'socket.io-client'; import { create } from 'zustand'; +import { WS_DB_RELAYER } from '@selfxyz/common/constants'; +import type { SelfApp } from '@selfxyz/common/utils/appType'; + interface SelfAppState { selfApp: SelfApp | null; sessionId: string | null; diff --git a/app/src/stores/settingStore.ts b/app/src/stores/settingStore.ts index 4cd51fdcf..8dcb2bc6d 100644 --- a/app/src/stores/settingStore.ts +++ b/app/src/stores/settingStore.ts @@ -1,9 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import AsyncStorage from '@react-native-async-storage/async-storage'; import { create } from 'zustand'; import { createJSONStorage, persist } from 'zustand/middleware'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + interface PersistedSettingsState { hasPrivacyNoteBeenDismissed: boolean; dismissPrivacyNote: () => void; diff --git a/app/src/stores/userStore.ts b/app/src/stores/userStore.ts index c41602f14..24ede8caa 100644 --- a/app/src/stores/userStore.ts +++ b/app/src/stores/userStore.ts @@ -1,8 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { DEFAULT_DOB, DEFAULT_DOE, DEFAULT_PNUMBER } from '@env'; import { create } from 'zustand'; +import { DEFAULT_DOB, DEFAULT_DOE, DEFAULT_PNUMBER } from '@env'; + interface UserState { documentType: string; countryCode: string; diff --git a/app/src/utils/cloudBackup/google.ts b/app/src/utils/cloudBackup/google.ts index 30bc42841..b19321d66 100644 --- a/app/src/utils/cloudBackup/google.ts +++ b/app/src/utils/cloudBackup/google.ts @@ -1,12 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import type { AuthConfiguration, AuthorizeResult } from 'react-native-app-auth'; +import { authorize } from 'react-native-app-auth'; + import { GOOGLE_SIGNIN_ANDROID_CLIENT_ID } from '@env'; import { GDrive } from '@robinbobin/react-native-google-drive-api-wrapper'; -import { - AuthConfiguration, - authorize, - AuthorizeResult, -} from 'react-native-app-auth'; // Ensure the client ID is available at runtime (skip in test environment) const isTestEnvironment = @@ -31,15 +29,6 @@ const config: AuthConfiguration = { additionalParameters: { access_type: 'offline', prompt: 'consent' as const }, }; -export async function googleSignIn(): Promise { - try { - return await authorize(config); - } catch (error) { - console.error(error); - return null; - } -} - export async function createGDrive() { const response = await googleSignIn(); if (!response) { @@ -50,3 +39,12 @@ export async function createGDrive() { gdrive.accessToken = response.accessToken; return gdrive; } + +export async function googleSignIn(): Promise { + try { + return await authorize(config); + } catch (error) { + console.error(error); + return null; + } +} diff --git a/app/src/utils/cloudBackup/helpers.ts b/app/src/utils/cloudBackup/helpers.ts index eb083f31b..715509788 100644 --- a/app/src/utils/cloudBackup/helpers.ts +++ b/app/src/utils/cloudBackup/helpers.ts @@ -5,7 +5,7 @@ import { Platform } from 'react-native'; import { CloudStorage, CloudStorageScope } from 'react-native-cloud-storage'; import { name } from '../../../package.json'; -import { Mnemonic } from '../../types/mnemonic'; +import type { Mnemonic } from '../../types/mnemonic'; export const FOLDER = `/${name}`; export const ENCRYPTED_FILE_PATH = `/${FOLDER}/encrypted-private-key`; diff --git a/app/src/utils/cloudBackup/index.ts b/app/src/utils/cloudBackup/index.ts index 5cf7ecff8..9beb40d5e 100644 --- a/app/src/utils/cloudBackup/index.ts +++ b/app/src/utils/cloudBackup/index.ts @@ -1,52 +1,40 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import { useMemo } from 'react'; +import { Platform } from 'react-native'; + +import type { Mnemonic } from '../../types/mnemonic'; +import { createGDrive } from './google'; +import { FILE_NAME, parseMnemonic, withRetries } from './helpers'; +import * as ios from './ios'; + import { APP_DATA_FOLDER_ID, MIME_TYPES, } from '@robinbobin/react-native-google-drive-api-wrapper'; -import { useMemo } from 'react'; -import { Platform } from 'react-native'; - -import { Mnemonic } from '../../types/mnemonic'; -import { createGDrive } from './google'; -import { FILE_NAME, parseMnemonic, withRetries } from './helpers'; -import * as ios from './ios'; export const STORAGE_NAME = Platform.OS === 'ios' ? 'iCloud' : 'Google Drive'; -export function useBackupMnemonic() { - return useMemo( - () => ({ - upload, - download, - disableBackup, - }), - [], - ); -} - -export async function upload(mnemonic: Mnemonic) { - if (!mnemonic || !mnemonic.phrase) { - throw new Error( - 'Mnemonic not set yet. Did the user see the recovery phrase?', - ); - } +export async function disableBackup() { if (Platform.OS === 'ios') { - await ios.upload(mnemonic); - } else { - const gdrive = await createGDrive(); - if (!gdrive) { - throw new Error('User canceled Google sign-in'); - } - await withRetries(() => - gdrive.files - .newMultipartUploader() - .setData(JSON.stringify(mnemonic)) - .setDataMimeType(MIME_TYPES.application.json) - .setRequestBody({ name: FILE_NAME, parents: [APP_DATA_FOLDER_ID] }) - .execute(), - ); + await ios.disableBackup(); + return; } + const gdrive = await createGDrive(); + if (!gdrive) { + // User canceled Google sign-in; skip disabling backup gracefully. + return; + } + const { files } = await gdrive.files.list({ + spaces: APP_DATA_FOLDER_ID, + q: `name = '${FILE_NAME}'`, + }); + await Promise.all( + files.map((f: any) => { + const id = f.id as string; + return id ? gdrive.files.delete(id) : Promise.resolve(); + }), + ); } export async function download() { @@ -82,24 +70,37 @@ export async function download() { } } -export async function disableBackup() { +export async function upload(mnemonic: Mnemonic) { + if (!mnemonic || !mnemonic.phrase) { + throw new Error( + 'Mnemonic not set yet. Did the user see the recovery phrase?', + ); + } if (Platform.OS === 'ios') { - await ios.disableBackup(); - return; + await ios.upload(mnemonic); + } else { + const gdrive = await createGDrive(); + if (!gdrive) { + throw new Error('User canceled Google sign-in'); + } + await withRetries(() => + gdrive.files + .newMultipartUploader() + .setData(JSON.stringify(mnemonic)) + .setDataMimeType(MIME_TYPES.application.json) + .setRequestBody({ name: FILE_NAME, parents: [APP_DATA_FOLDER_ID] }) + .execute(), + ); } - const gdrive = await createGDrive(); - if (!gdrive) { - // User canceled Google sign-in; skip disabling backup gracefully. - return; - } - const { files } = await gdrive.files.list({ - spaces: APP_DATA_FOLDER_ID, - q: `name = '${FILE_NAME}'`, - }); - await Promise.all( - files.map((f: any) => { - const id = f.id as string; - return id ? gdrive.files.delete(id) : Promise.resolve(); +} + +export function useBackupMnemonic() { + return useMemo( + () => ({ + upload, + download, + disableBackup, }), + [], ); } diff --git a/app/src/utils/cloudBackup/ios.ts b/app/src/utils/cloudBackup/ios.ts index d4e10eda0..62c99b6a1 100644 --- a/app/src/utils/cloudBackup/ios.ts +++ b/app/src/utils/cloudBackup/ios.ts @@ -2,7 +2,7 @@ import { CloudStorage } from 'react-native-cloud-storage'; -import { Mnemonic } from '../../types/mnemonic'; +import type { Mnemonic } from '../../types/mnemonic'; import { ENCRYPTED_FILE_PATH, FOLDER, @@ -10,18 +10,8 @@ import { withRetries, } from './helpers'; -export async function upload(mnemonic: Mnemonic) { - try { - await CloudStorage.mkdir(FOLDER); - } catch (e) { - console.error(e); - if (!(e as Error).message.includes('already')) { - throw e; - } - } - await withRetries(() => - CloudStorage.writeFile(ENCRYPTED_FILE_PATH, JSON.stringify(mnemonic)), - ); +export async function disableBackup() { + await withRetries(() => CloudStorage.rmdir(FOLDER, { recursive: true })); } export async function download() { @@ -43,6 +33,16 @@ export async function download() { ); } -export async function disableBackup() { - await withRetries(() => CloudStorage.rmdir(FOLDER, { recursive: true })); +export async function upload(mnemonic: Mnemonic) { + try { + await CloudStorage.mkdir(FOLDER); + } catch (e) { + console.error(e); + if (!(e as Error).message.includes('already')) { + throw e; + } + } + await withRetries(() => + CloudStorage.writeFile(ENCRYPTED_FILE_PATH, JSON.stringify(mnemonic)), + ); } diff --git a/app/src/utils/colors.ts b/app/src/utils/colors.ts index 7a0318f08..e25bc20a1 100644 --- a/app/src/utils/colors.ts +++ b/app/src/utils/colors.ts @@ -4,37 +4,60 @@ export const amber50 = '#FFFBEB'; export const amber500 = '#F2E3C8'; export const black = '#000000'; -export const white = '#ffffff'; -export const slate50 = '#F8FAFC'; -export const slate100 = '#F1F5F9'; -export const slate200 = '#E2E8F0'; -export const slate300 = '#CBD5E1'; -export const slate400 = '#94A3B8'; -export const slate500 = '#64748B'; -export const slate600 = '#475569'; -export const slate700 = '#334155'; -export const slate800 = '#1E293B'; -export const slate900 = '#0F172A'; -export const sky500 = '#0EA5E9'; -export const green500 = '#22C55E'; -export const red500 = '#EF4444'; -export const cyan300 = '#67E8F9'; -export const teal300 = '#5EEAD4'; -export const teal500 = '#5EEAD4'; -export const neutral400 = '#A3A3A3'; -export const neutral700 = '#404040'; - -export const zinc400 = '#A1A1AA'; -export const zinc500 = '#71717A'; -export const zinc800 = '#27272A'; -export const zinc900 = '#18181B'; export const blue100 = '#DBEAFE'; export const blue600 = '#2563EB'; export const blue700 = '#1D4ED8'; -export const yellow500 = '#FDE047'; -export const emerald500 = '#10B981'; - // OLD export const borderColor = '#343434'; -export const textBlack = '#333333'; + +export const cyan300 = '#67E8F9'; + +export const emerald500 = '#10B981'; + +export const green500 = '#22C55E'; + +export const neutral400 = '#A3A3A3'; + +export const neutral700 = '#404040'; + +export const red500 = '#EF4444'; + export const separatorColor = '#E0E0E0'; + +export const sky500 = '#0EA5E9'; + +export const slate100 = '#F1F5F9'; + +export const slate200 = '#E2E8F0'; + +export const slate300 = '#CBD5E1'; + +export const slate400 = '#94A3B8'; + +export const slate50 = '#F8FAFC'; + +export const slate500 = '#64748B'; + +export const slate600 = '#475569'; + +export const slate700 = '#334155'; + +export const slate800 = '#1E293B'; + +export const slate900 = '#0F172A'; + +export const teal300 = '#5EEAD4'; + +export const teal500 = '#5EEAD4'; + +export const textBlack = '#333333'; + +export const white = '#ffffff'; + +export const yellow500 = '#FDE047'; + +export const zinc400 = '#A1A1AA'; + +export const zinc500 = '#71717A'; +export const zinc800 = '#27272A'; +export const zinc900 = '#18181B'; diff --git a/app/src/utils/deeplinks.ts b/app/src/utils/deeplinks.ts index 244c377a7..786ab9b97 100644 --- a/app/src/utils/deeplinks.ts +++ b/app/src/utils/deeplinks.ts @@ -58,34 +58,6 @@ const validateAndSanitizeParam = ( return decodedValue; }; -/** - * Parses and validates query parameters from a URL - * @param uri - The URL to parse - * @returns Validated and sanitized parameters - */ -export const parseAndValidateUrlParams = (uri: string): ValidatedParams => { - // Parse the URL directly without pre-decoding to avoid issues with fragment separators - const parsed = parseUrl(uri); - const query = parsed.query || {}; - - const validatedParams: ValidatedParams = {}; - - // Only process expected parameters and validate them - for (const [key, value] of Object.entries(query)) { - if (key in VALIDATION_PATTERNS && typeof value === 'string') { - const sanitizedValue = validateAndSanitizeParam(key, value); - if (sanitizedValue !== undefined) { - validatedParams[key as keyof ValidatedParams] = sanitizedValue; - } - } else if (typeof __DEV__ !== 'undefined' && __DEV__) { - // Log unexpected parameters in development - console.warn(`Unexpected or invalid parameter ignored: ${key}`); - } - } - - return validatedParams; -}; - export const handleUrl = (uri: string) => { const validatedParams = parseAndValidateUrlParams(uri); const { sessionId, selfApp: selfAppStr, mock_passport } = validatedParams; @@ -146,6 +118,34 @@ export const handleUrl = (uri: string) => { } }; +/** + * Parses and validates query parameters from a URL + * @param uri - The URL to parse + * @returns Validated and sanitized parameters + */ +export const parseAndValidateUrlParams = (uri: string): ValidatedParams => { + // Parse the URL directly without pre-decoding to avoid issues with fragment separators + const parsed = parseUrl(uri); + const query = parsed.query || {}; + + const validatedParams: ValidatedParams = {}; + + // Only process expected parameters and validate them + for (const [key, value] of Object.entries(query)) { + if (key in VALIDATION_PATTERNS && typeof value === 'string') { + const sanitizedValue = validateAndSanitizeParam(key, value); + if (sanitizedValue !== undefined) { + validatedParams[key as keyof ValidatedParams] = sanitizedValue; + } + } else if (typeof __DEV__ !== 'undefined' && __DEV__) { + // Log unexpected parameters in development + console.warn(`Unexpected or invalid parameter ignored: ${key}`); + } + } + + return validatedParams; +}; + export const setupUniversalLinkListenerInNavigation = () => { const handleNavigation = (url: string) => { handleUrl(url); diff --git a/app/src/utils/ethers.ts b/app/src/utils/ethers.ts index c8adec8a8..0d06651d7 100644 --- a/app/src/utils/ethers.ts +++ b/app/src/utils/ethers.ts @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 // https://docs.ethers.org/v6/cookbook/react-native/ -// eslint-disable-next-line simple-import-sort/imports import { ethers } from 'ethers'; + import { hmac } from '@noble/hashes/hmac'; import { pbkdf2 as noblePbkdf2 } from '@noble/hashes/pbkdf2'; import { sha256 as nobleSha256 } from '@noble/hashes/sha256'; diff --git a/app/src/utils/haptic/index.ts b/app/src/utils/haptic/index.ts index 8b19ff9bb..5f654cda5 100644 --- a/app/src/utils/haptic/index.ts +++ b/app/src/utils/haptic/index.ts @@ -5,72 +5,18 @@ import { Platform, Vibration } from 'react-native'; import { triggerFeedback } from './trigger'; // Keep track of the loading screen interval -let loadingScreenInterval: NodeJS.Timeout | null = null; +let loadingScreenInterval: ReturnType | null = null; -/** - * Haptic actions - */ +// Define the base functions first export const impactLight = () => triggerFeedback('impactLight'); export const impactMedium = () => triggerFeedback('impactMedium'); -export const notificationError = () => triggerFeedback('notificationError'); -export const notificationSuccess = () => triggerFeedback('notificationSuccess'); -export const notificationWarning = () => triggerFeedback('notificationWarning'); export const selectionChange = () => triggerFeedback('selection'); + +// Then define the aliases export const buttonTap = impactLight; export const cancelTap = selectionChange; export const confirmTap = impactMedium; -// Custom feedback events - -export const loadingScreenProgress = (shouldVibrate: boolean = true) => { - // Clear any existing interval - if (loadingScreenInterval) { - clearInterval(loadingScreenInterval); - loadingScreenInterval = null; - } - - // If we shouldn't vibrate, just stop here - if (!shouldVibrate) { - Vibration.cancel(); - return; - } - - // Function to trigger the haptic feedback - const triggerHaptic = () => { - if (Platform.OS === 'android') { - // Pattern: [delay, duration, delay, duration, ...] - // First heavy impact at 500ms - // Then three light impacts at 750ms intervals - triggerFeedback('custom', { - pattern: [ - 500, - 100, // Heavy impact - 750, - 50, // First light impact - 750, - 50, // Second light impact - 750, - 50, // Third light impact - ], - }); - } else { - setTimeout(() => { - triggerFeedback('impactHeavy'); - }, 750); - setTimeout(() => { - feedbackProgress(); - }, 750); - } - }; - - // Trigger immediately - triggerHaptic(); - - // Set up interval for continuous feedback - // Total pattern duration (2950ms) + 1 second pause (1000ms) = 3950ms - loadingScreenInterval = setInterval(triggerHaptic, 4000); -}; - // consistent light feedback at a steady interval export const feedbackProgress = () => { if (Platform.OS === 'android') { @@ -159,4 +105,64 @@ export const feedbackUnsuccessful = () => { }, 1000); }; +/** + * Haptic actions + */ + +// Custom feedback events +export const loadingScreenProgress = (shouldVibrate: boolean = true) => { + // Clear any existing interval + if (loadingScreenInterval) { + clearInterval(loadingScreenInterval); + loadingScreenInterval = null; + } + + // If we shouldn't vibrate, just stop here + if (!shouldVibrate) { + Vibration.cancel(); + return; + } + + // Function to trigger the haptic feedback + const triggerHaptic = () => { + if (Platform.OS === 'android') { + // Pattern: [delay, duration, delay, duration, ...] + // First heavy impact at 500ms + // Then three light impacts at 750ms intervals + triggerFeedback('custom', { + pattern: [ + 500, + 100, // Heavy impact + 750, + 50, // First light impact + 750, + 50, // Second light impact + 750, + 50, // Third light impact + ], + }); + } else { + setTimeout(() => { + triggerFeedback('impactHeavy'); + }, 750); + setTimeout(() => { + feedbackProgress(); + }, 750); + } + }; + + // Trigger immediately + triggerHaptic(); + + // Set up interval for continuous feedback + // Total pattern duration (2950ms) + 1 second pause (1000ms) = 3950ms + loadingScreenInterval = setInterval(triggerHaptic, 4000); +}; + +export const notificationError = () => triggerFeedback('notificationError'); + +export const notificationSuccess = () => triggerFeedback('notificationSuccess'); + +export const notificationWarning = () => triggerFeedback('notificationWarning'); + export { triggerFeedback } from './trigger'; diff --git a/app/src/utils/haptic/shared.ts b/app/src/utils/haptic/shared.ts index 5ba016650..b5f4170d9 100644 --- a/app/src/utils/haptic/shared.ts +++ b/app/src/utils/haptic/shared.ts @@ -1,5 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +export type HapticOptions = { + enableVibrateFallback?: boolean; + ignoreAndroidSystemSettings?: boolean; + pattern?: number[]; + increaseIosIntensity?: boolean; +}; + export type HapticType = | 'selection' | 'impactLight' @@ -9,13 +16,6 @@ export type HapticType = | 'notificationWarning' | 'notificationError'; -export type HapticOptions = { - enableVibrateFallback?: boolean; - ignoreAndroidSystemSettings?: boolean; - pattern?: number[]; - increaseIosIntensity?: boolean; -}; - export const defaultOptions: HapticOptions = { enableVibrateFallback: true, ignoreAndroidSystemSettings: false, diff --git a/app/src/utils/haptic/trigger.ts b/app/src/utils/haptic/trigger.ts index 4938800cb..877bc78c2 100644 --- a/app/src/utils/haptic/trigger.ts +++ b/app/src/utils/haptic/trigger.ts @@ -3,7 +3,8 @@ import { Platform, Vibration } from 'react-native'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -import { defaultOptions, HapticOptions, HapticType } from './shared'; +import type { HapticOptions, HapticType } from './shared'; +import { defaultOptions } from './shared'; /** * Triggers haptic feedback or vibration based on platform. * @param type - The haptic feedback type. diff --git a/app/src/utils/haptic/trigger.web.ts b/app/src/utils/haptic/trigger.web.ts index 1d1a5bb0f..ca23ecf31 100644 --- a/app/src/utils/haptic/trigger.web.ts +++ b/app/src/utils/haptic/trigger.web.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { defaultOptions, HapticOptions, HapticType } from './shared'; +import type { HapticOptions, HapticType } from './shared'; +import { defaultOptions } from './shared'; /** * Triggers haptic feedback or vibration based on platform. diff --git a/app/src/utils/modalCallbackRegistry.ts b/app/src/utils/modalCallbackRegistry.ts index 33c28dfe9..5b4a934b8 100644 --- a/app/src/utils/modalCallbackRegistry.ts +++ b/app/src/utils/modalCallbackRegistry.ts @@ -8,16 +8,16 @@ export type ModalCallbacks = { let currentId = 0; const callbackMap = new Map(); +export function getModalCallbacks(id: number): ModalCallbacks | undefined { + return callbackMap.get(id); +} + export function registerModalCallbacks(callbacks: ModalCallbacks): number { const id = ++currentId; callbackMap.set(id, callbacks); return id; } -export function getModalCallbacks(id: number): ModalCallbacks | undefined { - return callbackMap.get(id); -} - export function unregisterModalCallbacks(id: number): void { callbackMap.delete(id); } diff --git a/app/src/utils/nfcScanner.ts b/app/src/utils/nfcScanner.ts index bf9e94fe6..64c50c41a 100644 --- a/app/src/utils/nfcScanner.ts +++ b/app/src/utils/nfcScanner.ts @@ -1,12 +1,14 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 /* eslint-disable @typescript-eslint/no-unused-vars */ -import { ENABLE_DEBUG_LOGS, MIXPANEL_NFC_PROJECT_TOKEN } from '@env'; -import type { PassportData } from '@selfxyz/common/types'; import { Buffer } from 'buffer'; import { NativeModules, Platform } from 'react-native'; import PassportReader from 'react-native-passport-reader'; +import type { PassportData } from '@selfxyz/common/types'; + +import { ENABLE_DEBUG_LOGS, MIXPANEL_NFC_PROJECT_TOKEN } from '@env'; + interface Inputs { passportNumber: string; dateOfBirth: string; @@ -19,21 +21,10 @@ interface Inputs { usePacePolling?: boolean; } -export const scan = async (inputs: Inputs) => { - if (MIXPANEL_NFC_PROJECT_TOKEN) { - if (Platform.OS === 'ios') { - const enableDebugLogs = JSON.parse(String(ENABLE_DEBUG_LOGS)); - NativeModules.PassportReader.configure( - MIXPANEL_NFC_PROJECT_TOKEN, - enableDebugLogs, - ); - } else { - } - } - +export const parseScanResponse = (response: any) => { return Platform.OS === 'android' - ? await scanAndroid(inputs) - : await scanIOS(inputs); + ? handleResponseAndroid(response) + : handleResponseIOS(response); }; const scanAndroid = async (inputs: Inputs) => { @@ -61,10 +52,21 @@ const scanIOS = async (inputs: Inputs) => { ); }; -export const parseScanResponse = (response: any) => { +export const scan = async (inputs: Inputs) => { + if (MIXPANEL_NFC_PROJECT_TOKEN) { + if (Platform.OS === 'ios') { + const enableDebugLogs = JSON.parse(String(ENABLE_DEBUG_LOGS)); + NativeModules.PassportReader.configure( + MIXPANEL_NFC_PROJECT_TOKEN, + enableDebugLogs, + ); + } else { + } + } + return Platform.OS === 'android' - ? handleResponseAndroid(response) - : handleResponseIOS(response); + ? await scanAndroid(inputs) + : await scanIOS(inputs); }; const handleResponseIOS = (response: any) => { diff --git a/app/src/utils/notifications/notificationService.shared.ts b/app/src/utils/notifications/notificationService.shared.ts index f33316e4f..ba8fe9074 100644 --- a/app/src/utils/notifications/notificationService.shared.ts +++ b/app/src/utils/notifications/notificationService.shared.ts @@ -1,5 +1,24 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +export interface DeviceTokenRegistration { + session_id: string; + device_token: string; + platform: 'ios' | 'android' | 'web'; +} + +export interface RemoteMessage { + messageId?: string; + data?: { [key: string]: string | object }; + notification?: { + title?: string; + body?: string; + }; + [key: string]: any; +} + +export const API_URL = 'https://notification.self.xyz'; + +export const API_URL_STAGING = 'https://notification.staging.self.xyz'; export const getStateMessage = (state: string): string => { switch (state) { case 'idle': @@ -32,22 +51,3 @@ export const getStateMessage = (state: string): string => { return 'Processing...'; } }; - -export interface RemoteMessage { - messageId?: string; - data?: { [key: string]: string | object }; - notification?: { - title?: string; - body?: string; - }; - [key: string]: any; -} - -export interface DeviceTokenRegistration { - session_id: string; - device_token: string; - platform: 'ios' | 'android' | 'web'; -} - -export const API_URL = 'https://notification.self.xyz'; -export const API_URL_STAGING = 'https://notification.staging.self.xyz'; diff --git a/app/src/utils/notifications/notificationService.ts b/app/src/utils/notifications/notificationService.ts index d97b867a2..ee465727a 100644 --- a/app/src/utils/notifications/notificationService.ts +++ b/app/src/utils/notifications/notificationService.ts @@ -1,52 +1,18 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import messaging from '@react-native-firebase/messaging'; import { PermissionsAndroid, Platform } from 'react-native'; +import type { + DeviceTokenRegistration, + RemoteMessage, +} from './notificationService.shared'; import { API_URL, API_URL_STAGING, - DeviceTokenRegistration, getStateMessage, - RemoteMessage, } from './notificationService.shared'; -export { getStateMessage }; -// Determine if running in test environment -const isTestEnv = process.env.NODE_ENV === 'test'; -const log = (...args: any[]) => { - if (!isTestEnv) console.log(...args); -}; -const error = (...args: any[]) => { - if (!isTestEnv) console.error(...args); -}; - -export async function requestNotificationPermission(): Promise { - try { - if (Platform.OS === 'android') { - if (Platform.Version >= 33) { - const permission = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS, - ); - if (permission !== PermissionsAndroid.RESULTS.GRANTED) { - log('Notification permission denied'); - return false; - } - } - } - const authStatus = await messaging().requestPermission(); - const enabled = - authStatus === messaging.AuthorizationStatus.AUTHORIZED || - authStatus === messaging.AuthorizationStatus.PROVISIONAL; - - log('Notification permission status:', enabled); - - return enabled; - } catch (err) { - error('Failed to request notification permission:', err); - return false; - } -} +import messaging from '@react-native-firebase/messaging'; export async function getFCMToken(): Promise { try { @@ -61,6 +27,16 @@ export async function getFCMToken(): Promise { return null; } } +// Determine if running in test environment +const isTestEnv = process.env.NODE_ENV === 'test'; +const log = (...args: any[]) => { + if (!isTestEnv) console.log(...args); +}; +const error = (...args: any[]) => { + if (!isTestEnv) console.error(...args); +}; + +export { getStateMessage }; export async function registerDeviceToken( sessionId: string, @@ -115,6 +91,33 @@ export async function registerDeviceToken( } } +export async function requestNotificationPermission(): Promise { + try { + if (Platform.OS === 'android') { + if (Platform.Version >= 33) { + const permission = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS, + ); + if (permission !== PermissionsAndroid.RESULTS.GRANTED) { + log('Notification permission denied'); + return false; + } + } + } + const authStatus = await messaging().requestPermission(); + const enabled = + authStatus === messaging.AuthorizationStatus.AUTHORIZED || + authStatus === messaging.AuthorizationStatus.PROVISIONAL; + + log('Notification permission status:', enabled); + + return enabled; + } catch (err) { + error('Failed to request notification permission:', err); + return false; + } +} + export function setupNotifications(): () => void { messaging().setBackgroundMessageHandler( async (remoteMessage: RemoteMessage) => { diff --git a/app/src/utils/notifications/notificationService.web.ts b/app/src/utils/notifications/notificationService.web.ts index a66d95a02..c88c37241 100644 --- a/app/src/utils/notifications/notificationService.web.ts +++ b/app/src/utils/notifications/notificationService.web.ts @@ -1,44 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import type { DeviceTokenRegistration } from './notificationService.shared'; import { API_URL, API_URL_STAGING, - DeviceTokenRegistration, getStateMessage, } from './notificationService.shared'; -// TODO: web handle notifications better. this file is more or less a fancy placeholder - -export { getStateMessage }; - -export async function requestNotificationPermission(): Promise { - try { - if (!('Notification' in window)) { - console.log('This browser does not support notifications'); - return false; - } - - if (Notification.permission === 'granted') { - console.log('Notification permission already granted'); - return true; - } - - if (Notification.permission === 'denied') { - console.log('Notification permission denied'); - return false; - } - - const permission = await Notification.requestPermission(); - const enabled = permission === 'granted'; - - console.log('Notification permission status:', enabled); - return enabled; - } catch (error) { - console.error('Failed to request notification permission:', error); - return false; - } -} - export async function getFCMToken(): Promise { try { // For web, we'll generate a simple token or use a service worker registration @@ -65,6 +33,9 @@ export async function getFCMToken(): Promise { } } +// TODO: web handle notifications better. this file is more or less a fancy placeholder +export { getStateMessage }; + export async function registerDeviceToken( sessionId: string, deviceToken?: string, @@ -126,6 +97,34 @@ export async function registerDeviceToken( } } +export async function requestNotificationPermission(): Promise { + try { + if (!('Notification' in window)) { + console.log('This browser does not support notifications'); + return false; + } + + if (Notification.permission === 'granted') { + console.log('Notification permission already granted'); + return true; + } + + if (Notification.permission === 'denied') { + console.log('Notification permission denied'); + return false; + } + + const permission = await Notification.requestPermission(); + const enabled = permission === 'granted'; + + console.log('Notification permission status:', enabled); + return enabled; + } catch (error) { + console.error('Failed to request notification permission:', error); + return false; + } +} + export function setupNotifications(): () => void { // For web, we'll set up service worker for background notifications // and handle foreground notifications directly diff --git a/app/src/utils/ofac.ts b/app/src/utils/ofac.ts index c8a415a05..8e8a9da09 100644 --- a/app/src/utils/ofac.ts +++ b/app/src/utils/ofac.ts @@ -2,14 +2,14 @@ import { TREE_URL, TREE_URL_STAGING } from '@selfxyz/common/constants'; -export type OfacVariant = 'passport' | 'id_card'; - export interface OfacTrees { passportNoAndNationality: any; nameAndDob: any; nameAndYob: any; } +export type OfacVariant = 'passport' | 'id_card'; + // Generic helper to fetch a single OFAC tree and validate the response shape. const fetchTree = async (url: string): Promise => { const res = await fetch(url); diff --git a/app/src/utils/proving/attest.ts b/app/src/utils/proving/attest.ts index ca76d5088..b974f3aed 100644 --- a/app/src/utils/proving/attest.ts +++ b/app/src/utils/proving/attest.ts @@ -1,8 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { X509Certificate } from '@peculiar/x509'; -import { PCR0_MANAGER_ADDRESS, RPC_URL } from '@selfxyz/common/constants'; -import { decode } from '@stablelib/cbor'; import { fromBER } from 'asn1js'; import { Buffer } from 'buffer'; import { ec as ellipticEc } from 'elliptic'; @@ -10,9 +7,14 @@ import { ethers } from 'ethers'; import { sha384 } from 'js-sha512'; import { Certificate } from 'pkijs'; +import { PCR0_MANAGER_ADDRESS, RPC_URL } from '@selfxyz/common/constants'; + import { AWS_ROOT_PEM } from './awsRootPem'; import cose from './cose'; +import { X509Certificate } from '@peculiar/x509'; +import { decode } from '@stablelib/cbor'; + /** * @notice An array specifying the required fields for a valid attestation. */ @@ -25,6 +27,86 @@ const requiredFields = [ 'cabundle', ]; +/** + * @notice Queries the PCR0Manager contract to verify that the PCR0 value extracted from the attestation + * is mapped to true. + * @param attestation An array of numbers representing the COSE_Sign1 encoded attestation document. + * @return A promise that resolves to true if the PCR0 value is set in the contract, or false otherwise. + */ +export async function checkPCR0Mapping( + attestation: Array, +): Promise { + // Obtain the PCR0 image hash from the attestation + const imageHashHex = getImageHash(attestation); + console.log('imageHash', imageHashHex); + // The getImageHash function returns a hex string (without the "0x" prefix) + // For a SHA384 hash, we expect 96 hex characters (48 bytes) + if (imageHashHex.length !== 96) { + throw new Error( + `Invalid PCR0 hash length: expected 96 hex characters, got ${imageHashHex.length}`, + ); + } + + // Convert the PCR0 hash from hex to a byte array, ensuring proper "0x" prefix + const pcr0Bytes = ethers.getBytes(`0x${imageHashHex}`); + if (pcr0Bytes.length !== 48) { + throw new Error( + `Invalid PCR0 bytes length: expected 48, got ${pcr0Bytes.length}`, + ); + } + + const celoProvider = new ethers.JsonRpcProvider(RPC_URL); + + // Create a contract instance for the PCR0Manager + const pcr0Manager = new ethers.Contract( + PCR0_MANAGER_ADDRESS, + PCR0ManagerABI, + celoProvider, + ); + + try { + // Query the contract: isPCR0Set returns true if the given PCR0 value is set + return await pcr0Manager.isPCR0Set(pcr0Bytes); + } catch (error) { + console.error('Error checking PCR0 mapping:', error); + throw error; + } +} + +// Add a helper function to validate and format PCR0 values +export function formatPCR0Value(pcr0: string): Uint8Array { + // Remove "0x" prefix if present + const cleanHex = pcr0.startsWith('0x') ? pcr0.slice(2) : pcr0; + + // Validate hex string length (96 characters for 48 bytes) + if (cleanHex.length !== 96) { + throw new Error( + `Invalid PCR0 length: expected 96 hex characters, got ${cleanHex.length}`, + ); + } + + // Validate hex string format + if (!/^[0-9a-fA-F]+$/.test(cleanHex)) { + throw new Error('Invalid hex string: contains non-hex characters'); + } + + // Convert to bytes + return ethers.getBytes(`0x${cleanHex}`); +} + +/** + * @notice Extracts the public key from a TEE attestation document. + * @param attestation An array of numbers representing the COSE_Sign1 encoded attestation document. + * @return The public key as a string. + */ +export function getPublicKey(attestation: Array) { + const coseSign1 = decode(Buffer.from(attestation)); + const [_protectedHeaderBytes, _unprotectedHeader, payload, _signature] = + coseSign1; + const attestationDoc = decode(payload) as AttestationDoc; + return attestationDoc.public_key; +} + /** * @notice Utility function to check if a number is within (start, end] range. * @param start The start of the range (exclusive). @@ -40,177 +122,6 @@ export const numberInRange = ( return value > start && value <= end; }; -/** - * @notice Verifies a certificate chain against a provided trusted root certificate. - * @param rootPem The trusted root certificate in PEM format. - * @param certChainStr An array of certificates in PEM format, ordered from leaf to root. - * @return True if the certificate chain is valid, false otherwise. - */ -export const verifyCertChain = async ( - rootPem: string, - certChainStr: string[], -): Promise => { - try { - // Parse all certificates - const certChain = certChainStr.map(cert => new X509Certificate(cert)); - - // Verify the chain from leaf to root - // certChain[0] is the root, we use the hardcoded rootPem - for (let i = 1; i < certChain.length; i++) { - const currentCert = certChain[i]; - // Verify certificate validity period - const now = new Date(); - if (now < currentCert.notBefore || now > currentCert.notAfter) { - console.error('Certificate is not within its validity period'); - return false; - } - - // Verify signature - try { - const isValid = verifyCertificateSignature( - certChainStr[i], - i === 1 ? rootPem : certChainStr[i - 1], - ); - if (!isValid) { - console.error(`Certificate at index ${i} has invalid signature`); - return false; - } - } catch (e) { - console.error(`Error verifying signature at index ${i}:`, e); - return false; - } - } - console.log('Certificate chain verified'); - return true; - } catch (error) { - console.error('Certificate chain verification error:', error); - return false; - } -}; - -/** - * @notice Verifies a TEE attestation document encoded as a COSE_Sign1 structure. - * @param attestation An array of numbers representing the COSE_Sign1 encoded attestation document. - * @return A promise that resolves to true if the attestation is verified successfully. - * @throws Error if the attestation document is improperly formatted or missing required fields. - */ -export const verifyAttestation = async (attestation: Array) => { - const coseSign1 = await decode(Buffer.from(attestation)); - - if (!Array.isArray(coseSign1) || coseSign1.length !== 4) { - throw new Error('Invalid COSE_Sign1 format'); - } - - const [_protectedHeaderBytes, _unprotectedHeader, payload, _signature] = - coseSign1; - - const attestationDoc = (await decode(payload)) as AttestationDoc; - - for (const field of requiredFields) { - //@ts-ignore - if (!attestationDoc[field]) { - throw new Error(`Missing required field: ${field}`); - } - } - - if (!(attestationDoc.module_id.length > 0)) { - throw new Error('Invalid module_id'); - } - if (!(attestationDoc.digest === 'SHA384')) { - throw new Error('Invalid digest'); - } - - if (!(attestationDoc.timestamp > 0)) { - throw new Error('Invalid timestamp'); - } - - // for each key, value in pcrs - for (const [key, value] of Object.entries(attestationDoc.pcrs)) { - if (parseInt(key, 10) < 0 || parseInt(key, 10) >= 32) { - throw new Error('Invalid pcr index'); - } - - if (![32, 48, 64].includes(value.length)) { - throw new Error('Invalid pcr value length at: ' + key); - } - } - - if (!(attestationDoc.cabundle.length > 0)) { - throw new Error('Invalid cabundle'); - } - - for (let i = 0; i < attestationDoc.cabundle.length; i++) { - if (!numberInRange(0, 1024, attestationDoc.cabundle[i].length)) { - throw new Error('Invalid cabundle'); - } - } - - if (attestationDoc.public_key) { - if (!numberInRange(0, 1024, attestationDoc.public_key.length)) { - throw new Error('Invalid public_key'); - } - } - - if (attestationDoc.user_data) { - if (!numberInRange(-1, 512, attestationDoc.user_data.length)) { - throw new Error('Invalid user_data'); - } - } - - if (attestationDoc.nonce) { - if (!numberInRange(-1, 512, attestationDoc.nonce.length)) { - throw new Error('Invalid nonce'); - } - } - - const certChain = attestationDoc.cabundle.map((cert: Buffer) => - derToPem(cert), - ); - - const cert = derToPem(attestationDoc.certificate); - const isPCR0Set = await checkPCR0Mapping(attestation); - console.log('isPCR0Set', isPCR0Set); - if (!isPCR0Set && !__DEV__) { - throw new Error('Invalid image hash'); - } - if (__DEV__ && !isPCR0Set) { - console.warn('\x1b[31m%s\x1b[0m', 'āš ļø WARNING: PCR0 CHECK SKIPPED āš ļø'); - } - console.log('TEE image hash verified'); - - if (!(await verifyCertChain(AWS_ROOT_PEM, [...certChain, cert]))) { - throw new Error('Invalid certificate chain'); - } - - const { x, y, curve } = getPublicKeyFromPem(cert); - - const verifier = { - key: { - x, - y, - curve, - }, - }; - console.log('verifier', verifier); - await cose.sign.verify(Buffer.from(attestation), verifier, { - defaultType: 18, - }); - return true; -}; - -/** - * @notice Extracts the public key from a TEE attestation document. - * @param attestation An array of numbers representing the COSE_Sign1 encoded attestation document. - * @return The public key as a string. - */ -export function getPublicKey(attestation: Array) { - const coseSign1 = decode(Buffer.from(attestation)); - const [_protectedHeaderBytes, _unprotectedHeader, payload, _signature] = - coseSign1; - const attestationDoc = decode(payload) as AttestationDoc; - return attestationDoc.public_key; -} - /** * @notice Converts a DER-encoded certificate to PEM format. * @param der A Buffer containing the DER-encoded certificate. @@ -374,68 +285,159 @@ const PCR0ManagerABI = [ ]; /** - * @notice Queries the PCR0Manager contract to verify that the PCR0 value extracted from the attestation - * is mapped to true. + * @notice Verifies a TEE attestation document encoded as a COSE_Sign1 structure. * @param attestation An array of numbers representing the COSE_Sign1 encoded attestation document. - * @return A promise that resolves to true if the PCR0 value is set in the contract, or false otherwise. + * @return A promise that resolves to true if the attestation is verified successfully. + * @throws Error if the attestation document is improperly formatted or missing required fields. */ -export async function checkPCR0Mapping( - attestation: Array, -): Promise { - // Obtain the PCR0 image hash from the attestation - const imageHashHex = getImageHash(attestation); - console.log('imageHash', imageHashHex); - // The getImageHash function returns a hex string (without the "0x" prefix) - // For a SHA384 hash, we expect 96 hex characters (48 bytes) - if (imageHashHex.length !== 96) { - throw new Error( - `Invalid PCR0 hash length: expected 96 hex characters, got ${imageHashHex.length}`, - ); +export const verifyAttestation = async (attestation: Array) => { + const coseSign1 = await decode(Buffer.from(attestation)); + + if (!Array.isArray(coseSign1) || coseSign1.length !== 4) { + throw new Error('Invalid COSE_Sign1 format'); } - // Convert the PCR0 hash from hex to a byte array, ensuring proper "0x" prefix - const pcr0Bytes = ethers.getBytes(`0x${imageHashHex}`); - if (pcr0Bytes.length !== 48) { - throw new Error( - `Invalid PCR0 bytes length: expected 48, got ${pcr0Bytes.length}`, - ); + const [_protectedHeaderBytes, _unprotectedHeader, payload, _signature] = + coseSign1; + + const attestationDoc = (await decode(payload)) as AttestationDoc; + + for (const field of requiredFields) { + //@ts-ignore + if (!attestationDoc[field]) { + throw new Error(`Missing required field: ${field}`); + } } - const celoProvider = new ethers.JsonRpcProvider(RPC_URL); + if (!(attestationDoc.module_id.length > 0)) { + throw new Error('Invalid module_id'); + } + if (!(attestationDoc.digest === 'SHA384')) { + throw new Error('Invalid digest'); + } - // Create a contract instance for the PCR0Manager - const pcr0Manager = new ethers.Contract( - PCR0_MANAGER_ADDRESS, - PCR0ManagerABI, - celoProvider, + if (!(attestationDoc.timestamp > 0)) { + throw new Error('Invalid timestamp'); + } + + // for each key, value in pcrs + for (const [key, value] of Object.entries(attestationDoc.pcrs)) { + if (parseInt(key, 10) < 0 || parseInt(key, 10) >= 32) { + throw new Error('Invalid pcr index'); + } + + if (![32, 48, 64].includes(value.length)) { + throw new Error('Invalid pcr value length at: ' + key); + } + } + + if (!(attestationDoc.cabundle.length > 0)) { + throw new Error('Invalid cabundle'); + } + + for (let i = 0; i < attestationDoc.cabundle.length; i++) { + if (!numberInRange(0, 1024, attestationDoc.cabundle[i].length)) { + throw new Error('Invalid cabundle'); + } + } + + if (attestationDoc.public_key) { + if (!numberInRange(0, 1024, attestationDoc.public_key.length)) { + throw new Error('Invalid public_key'); + } + } + + if (attestationDoc.user_data) { + if (!numberInRange(-1, 512, attestationDoc.user_data.length)) { + throw new Error('Invalid user_data'); + } + } + + if (attestationDoc.nonce) { + if (!numberInRange(-1, 512, attestationDoc.nonce.length)) { + throw new Error('Invalid nonce'); + } + } + + const certChain = attestationDoc.cabundle.map((cert: Buffer) => + derToPem(cert), ); + const cert = derToPem(attestationDoc.certificate); + const isPCR0Set = await checkPCR0Mapping(attestation); + console.log('isPCR0Set', isPCR0Set); + if (!isPCR0Set && !__DEV__) { + throw new Error('Invalid image hash'); + } + if (__DEV__ && !isPCR0Set) { + console.warn('\x1b[31m%s\x1b[0m', 'āš ļø WARNING: PCR0 CHECK SKIPPED āš ļø'); + } + console.log('TEE image hash verified'); + + if (!(await verifyCertChain(AWS_ROOT_PEM, [...certChain, cert]))) { + throw new Error('Invalid certificate chain'); + } + + const { x, y, curve } = getPublicKeyFromPem(cert); + + const verifier = { + key: { + x, + y, + curve, + }, + }; + console.log('verifier', verifier); + await cose.sign.verify(Buffer.from(attestation), verifier, { + defaultType: 18, + }); + return true; +}; + +/** + * @notice Verifies a certificate chain against a provided trusted root certificate. + * @param rootPem The trusted root certificate in PEM format. + * @param certChainStr An array of certificates in PEM format, ordered from leaf to root. + * @return True if the certificate chain is valid, false otherwise. + */ +export const verifyCertChain = async ( + rootPem: string, + certChainStr: string[], +): Promise => { try { - // Query the contract: isPCR0Set returns true if the given PCR0 value is set - return await pcr0Manager.isPCR0Set(pcr0Bytes); + // Parse all certificates + const certChain = certChainStr.map(cert => new X509Certificate(cert)); + + // Verify the chain from leaf to root + // certChain[0] is the root, we use the hardcoded rootPem + for (let i = 1; i < certChain.length; i++) { + const currentCert = certChain[i]; + // Verify certificate validity period + const now = new Date(); + if (now < currentCert.notBefore || now > currentCert.notAfter) { + console.error('Certificate is not within its validity period'); + return false; + } + + // Verify signature + try { + const isValid = verifyCertificateSignature( + certChainStr[i], + i === 1 ? rootPem : certChainStr[i - 1], + ); + if (!isValid) { + console.error(`Certificate at index ${i} has invalid signature`); + return false; + } + } catch (e) { + console.error(`Error verifying signature at index ${i}:`, e); + return false; + } + } + console.log('Certificate chain verified'); + return true; } catch (error) { - console.error('Error checking PCR0 mapping:', error); - throw error; + console.error('Certificate chain verification error:', error); + return false; } -} - -// Add a helper function to validate and format PCR0 values -export function formatPCR0Value(pcr0: string): Uint8Array { - // Remove "0x" prefix if present - const cleanHex = pcr0.startsWith('0x') ? pcr0.slice(2) : pcr0; - - // Validate hex string length (96 characters for 48 bytes) - if (cleanHex.length !== 96) { - throw new Error( - `Invalid PCR0 length: expected 96 hex characters, got ${cleanHex.length}`, - ); - } - - // Validate hex string format - if (!/^[0-9a-fA-F]+$/.test(cleanHex)) { - throw new Error('Invalid hex string: contains non-hex characters'); - } - - // Convert to bytes - return ethers.getBytes(`0x${cleanHex}`); -} +}; diff --git a/app/src/utils/proving/cose.ts b/app/src/utils/proving/cose.ts index b630e1fc1..0b72ef362 100644 --- a/app/src/utils/proving/cose.ts +++ b/app/src/utils/proving/cose.ts @@ -1,10 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { decode, encode } from '@stablelib/cbor'; import { Buffer } from 'buffer'; import { ec as EC } from 'elliptic'; import { sha384 } from 'js-sha512'; +import { decode, encode } from '@stablelib/cbor'; + /** * @notice Verifies a COSE_Sign1 message signature against the provided ECDSA public key. * @param data A Buffer containing the COSE_Sign1 encoded message. diff --git a/app/src/utils/proving/index.ts b/app/src/utils/proving/index.ts index b4516082e..f3e270198 100644 --- a/app/src/utils/proving/index.ts +++ b/app/src/utils/proving/index.ts @@ -5,18 +5,18 @@ // From provingMachine - used in screens and tests export { type ProvingStateType, useProvingStore } from './provingMachine'; -// From validateDocument - used in recovery and splash screens -export { - hasAnyValidRegisteredDocument, - isUserRegisteredWithAlternativeCSCA, -} from './validateDocument'; - -// From loadingScreenStateText - used in loading screen -export { getLoadingScreenText } from './loadingScreenStateText'; - // From provingUtils - used in tests (keeping these for testing purposes) export { encryptAES256GCM, getPayload, getWSDbRelayerUrl, } from './provingUtils'; + +// From loadingScreenStateText - used in loading screen +export { getLoadingScreenText } from './loadingScreenStateText'; + +// From validateDocument - used in recovery and splash screens +export { + hasAnyValidRegisteredDocument, + isUserRegisteredWithAlternativeCSCA, +} from './validateDocument'; diff --git a/app/src/utils/proving/loadingScreenStateText.ts b/app/src/utils/proving/loadingScreenStateText.ts index 56e1b9652..363a00f6f 100644 --- a/app/src/utils/proving/loadingScreenStateText.ts +++ b/app/src/utils/proving/loadingScreenStateText.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { ProvingStateType } from './provingMachine'; +import type { ProvingStateType } from './provingMachine'; interface LoadingScreenText { actionText: string; @@ -12,41 +12,6 @@ export interface PassportMetadata { curveOrExponent: string; } -export function getProvingTimeEstimate( - metadata: PassportMetadata | undefined, - type: 'dsc' | 'register', -): string { - if (!metadata) return '30 - 90 SECONDS'; - - const algorithm = metadata.signatureAlgorithm?.toLowerCase(); - const curveOrExponent = metadata.curveOrExponent; - - // RSA algorithms - if (algorithm?.includes('rsa')) { - if (algorithm?.includes('pss')) { - return type === 'dsc' ? '3 SECONDS' : '6 SECONDS'; - } - return type === 'dsc' ? '2 SECONDS' : '4 SECONDS'; - } - - // ECDSA algorithms - if (algorithm?.includes('ecdsa')) { - // Check bit size from curve name - if (curveOrExponent?.includes('224') || curveOrExponent?.includes('256')) { - return type === 'dsc' ? '25 SECONDS' : '50 SECONDS'; - } - if (curveOrExponent?.includes('384')) { - return type === 'dsc' ? '45 SECONDS' : '90 SECONDS'; - } - if (curveOrExponent?.includes('512') || curveOrExponent?.includes('521')) { - return type === 'dsc' ? '100 SECONDS' : '200 SECONDS'; - } - } - - // Default case - return '30 - 90 SECONDS'; -} - export function getLoadingScreenText( state: ProvingStateType, metadata: PassportMetadata, @@ -142,3 +107,38 @@ export function getLoadingScreenText( }; } } + +export function getProvingTimeEstimate( + metadata: PassportMetadata | undefined, + type: 'dsc' | 'register', +): string { + if (!metadata) return '30 - 90 SECONDS'; + + const algorithm = metadata.signatureAlgorithm?.toLowerCase(); + const curveOrExponent = metadata.curveOrExponent; + + // RSA algorithms + if (algorithm?.includes('rsa')) { + if (algorithm?.includes('pss')) { + return type === 'dsc' ? '3 SECONDS' : '6 SECONDS'; + } + return type === 'dsc' ? '2 SECONDS' : '4 SECONDS'; + } + + // ECDSA algorithms + if (algorithm?.includes('ecdsa')) { + // Check bit size from curve name + if (curveOrExponent?.includes('224') || curveOrExponent?.includes('256')) { + return type === 'dsc' ? '25 SECONDS' : '50 SECONDS'; + } + if (curveOrExponent?.includes('384')) { + return type === 'dsc' ? '45 SECONDS' : '90 SECONDS'; + } + if (curveOrExponent?.includes('512') || curveOrExponent?.includes('521')) { + return type === 'dsc' ? '100 SECONDS' : '200 SECONDS'; + } + } + + // Default case + return '30 - 90 SECONDS'; +} diff --git a/app/src/utils/proving/provingInputs.ts b/app/src/utils/proving/provingInputs.ts index ecac5840c..d358450ae 100644 --- a/app/src/utils/proving/provingInputs.ts +++ b/app/src/utils/proving/provingInputs.ts @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; -import { SMT } from '@openpassport/zk-kit-smt'; +import { poseidon2 } from 'poseidon-lite'; + import { attributeToPosition, attributeToPosition_ID, @@ -19,22 +19,11 @@ import { getCircuitNameFromPassportData, hashEndpointWithScope, } from '@selfxyz/common/utils'; -import { poseidon2 } from 'poseidon-lite'; import { useProtocolStore } from '../../stores/protocolStore'; -export function generateTEEInputsRegister( - secret: string, - passportData: PassportData, - dscTree: string, - env: 'prod' | 'stg', -) { - const inputs = generateCircuitInputsRegister(secret, passportData, dscTree); - const circuitName = getCircuitNameFromPassportData(passportData, 'register'); - const endpointType = env === 'stg' ? 'staging_celo' : 'celo'; - const endpoint = 'https://self.xyz'; - return { inputs, circuitName, endpointType, endpoint }; -} +import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; +import { SMT } from '@openpassport/zk-kit-smt'; export function generateTEEInputsDSC( passportData: PassportData, @@ -115,6 +104,19 @@ export function generateTEEInputsDisclose( }; } +export function generateTEEInputsRegister( + secret: string, + passportData: PassportData, + dscTree: string, + env: 'prod' | 'stg', +) { + const inputs = generateCircuitInputsRegister(secret, passportData, dscTree); + const circuitName = getCircuitNameFromPassportData(passportData, 'register'); + const endpointType = env === 'stg' ? 'staging_celo' : 'celo'; + const endpoint = 'https://self.xyz'; + return { inputs, circuitName, endpointType, endpoint }; +} + /*** DISCLOSURE ***/ function getSelectorDg1( diff --git a/app/src/utils/proving/provingMachine.ts b/app/src/utils/proving/provingMachine.ts index af79057c8..714b2ef2a 100644 --- a/app/src/utils/proving/provingMachine.ts +++ b/app/src/utils/proving/provingMachine.ts @@ -1,16 +1,19 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import forge from 'node-forge'; +import type { Socket } from 'socket.io-client'; +import socketIo from 'socket.io-client'; +import { v4 } from 'uuid'; +import type { AnyActorRef } from 'xstate'; +import { createActor, createMachine } from 'xstate'; +import { create } from 'zustand'; + import type { DocumentCategory, PassportData } from '@selfxyz/common/types'; import type { EndpointType, SelfApp } from '@selfxyz/common/utils'; import { getCircuitNameFromPassportData, getSolidityPackedUserContextData, } from '@selfxyz/common/utils'; -import forge from 'node-forge'; -import socketIo, { Socket } from 'socket.io-client'; -import { v4 } from 'uuid'; -import { AnyActorRef, createActor, createMachine } from 'xstate'; -import { create } from 'zustand'; import { PassportEvents, ProofEvents } from '../../consts/analytics'; import { navigationRef } from '../../navigation'; @@ -49,12 +52,29 @@ import { const { trackEvent } = analytics(); -export const getPostVerificationRoute = () => { - return 'AccountVerifiedSuccess'; - // disable for now - // const { cloudBackupEnabled } = useSettingStore.getState(); - // return cloudBackupEnabled ? 'AccountVerifiedSuccess' : 'SaveRecoveryPhrase'; -}; +export type ProvingStateType = + // Initial states + | 'idle' + | undefined + // Data preparation states + | 'fetching_data' + | 'validating_document' + // Connection states + | 'init_tee_connexion' + | 'listening_for_status' + // Proving states + | 'ready_to_prove' + | 'proving' + | 'post_proving' + // Success state + | 'completed' + // Error states + | 'error' + | 'failure' + // Special case states + | 'passport_not_supported' + | 'account_recovery_choice' + | 'passport_data_not_found'; const provingMachine = createMachine({ id: 'proving', @@ -131,29 +151,12 @@ const provingMachine = createMachine({ export type provingMachineCircuitType = 'register' | 'dsc' | 'disclose'; -export type ProvingStateType = - // Initial states - | 'idle' - | undefined - // Data preparation states - | 'fetching_data' - | 'validating_document' - // Connection states - | 'init_tee_connexion' - | 'listening_for_status' - // Proving states - | 'ready_to_prove' - | 'proving' - | 'post_proving' - // Success state - | 'completed' - // Error states - | 'error' - | 'failure' - // Special case states - | 'passport_not_supported' - | 'account_recovery_choice' - | 'passport_data_not_found'; +export const getPostVerificationRoute = () => { + return 'AccountVerifiedSuccess'; + // disable for now + // const { cloudBackupEnabled } = useSettingStore.getState(); + // return cloudBackupEnabled ? 'AccountVerifiedSuccess' : 'SaveRecoveryPhrase'; +}; interface ProvingState { currentState: ProvingStateType; diff --git a/app/src/utils/proving/provingUtils.ts b/app/src/utils/proving/provingUtils.ts index 1c8a051c8..cfac5f4f1 100644 --- a/app/src/utils/proving/provingUtils.ts +++ b/app/src/utils/proving/provingUtils.ts @@ -1,17 +1,51 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import forge from 'node-forge'; + import { WS_DB_RELAYER, WS_DB_RELAYER_STAGING, } from '@selfxyz/common/constants'; import type { EndpointType } from '@selfxyz/common/utils'; import { initElliptic } from '@selfxyz/common/utils'; -import forge from 'node-forge'; const elliptic = initElliptic(); const { ec: EC } = elliptic; +// Use a consistent client keypair for the session +export type TEEPayload = TEEPayloadBase & { + type: RegisterProofType | DscProofType; + onchain: true; +}; + +export type TEEPayloadBase = { + endpointType: EndpointType; + circuit: { + name: string; + inputs: string; + }; +}; +export type TEEPayloadDisclose = TEEPayloadBase & { + type: DiscloseProofType; + onchain: boolean; + endpoint: string; + userDefinedData: string; + version: number; +}; + export const ec = new EC('p256'); -export const clientKey = ec.genKeyPair(); // Use a consistent client keypair for the session + +export const clientKey = ec.genKeyPair(); + +type RegisterSuffixes = '' | '_id'; +type DscSuffixes = '' | '_id'; +type DiscloseSuffixes = '' | '_id'; +type ProofTypes = 'register' | 'dsc' | 'disclose'; +type RegisterProofType = + `${Extract}${RegisterSuffixes}`; +type DscProofType = `${Extract}${DscSuffixes}`; +type DiscloseProofType = + `${Extract}${DiscloseSuffixes}`; + export const clientPublicKeyHex = clientKey.getPublic().getX().toString('hex').padStart(64, '0') + clientKey.getPublic().getY().toString('hex').padStart(64, '0'); @@ -34,37 +68,6 @@ export function encryptAES256GCM( }; } -type RegisterSuffixes = '' | '_id'; -type DscSuffixes = '' | '_id'; -type DiscloseSuffixes = '' | '_id'; -type ProofTypes = 'register' | 'dsc' | 'disclose'; -type RegisterProofType = - `${Extract}${RegisterSuffixes}`; -type DscProofType = `${Extract}${DscSuffixes}`; -type DiscloseProofType = - `${Extract}${DiscloseSuffixes}`; - -export type TEEPayloadBase = { - endpointType: EndpointType; - circuit: { - name: string; - inputs: string; - }; -}; - -export type TEEPayload = TEEPayloadBase & { - type: RegisterProofType | DscProofType; - onchain: true; -}; - -export type TEEPayloadDisclose = TEEPayloadBase & { - type: DiscloseProofType; - onchain: boolean; - endpoint: string; - userDefinedData: string; - version: number; -}; - export function getPayload( inputs: any, circuitType: RegisterProofType | DscProofType | DiscloseProofType, diff --git a/app/src/utils/proving/validateDocument.ts b/app/src/utils/proving/validateDocument.ts index 55f84f384..4ba1ed413 100644 --- a/app/src/utils/proving/validateDocument.ts +++ b/app/src/utils/proving/validateDocument.ts @@ -1,12 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; +import { poseidon2, poseidon5 } from 'poseidon-lite'; + import { API_URL, API_URL_STAGING, ID_CARD_ATTESTATION_ID, PASSPORT_ATTESTATION_ID, -} from '@selfxyz/common/constants/core'; +} from '@selfxyz/common/constants'; import type { DocumentCategory, PassportData } from '@selfxyz/common/types'; import { parseCertificateSimple } from '@selfxyz/common/utils/certificates/parseSimple'; import { getCircuitNameFromPassportData } from '@selfxyz/common/utils/circuitNames'; @@ -18,7 +19,6 @@ import { generateNullifier, } from '@selfxyz/common/utils/passports'; import { getLeafDscTree } from '@selfxyz/common/utils/trees'; -import { poseidon2, poseidon5 } from 'poseidon-lite'; import { DocumentEvents } from '../../consts/analytics'; import { @@ -33,6 +33,8 @@ import { import { useProtocolStore } from '../../stores/protocolStore'; import analytics from '../../utils/analytics'; +import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; + const { trackEvent } = analytics(); export type PassportSupportStatus = @@ -41,301 +43,6 @@ export type PassportSupportStatus = | 'registration_circuit_not_supported' | 'dsc_circuit_not_supported' | 'passport_supported'; -export async function checkPassportSupported( - passportData: PassportData, -): Promise<{ - status: PassportSupportStatus; - details: string; -}> { - const passportMetadata = passportData.passportMetadata; - const document: DocumentCategory = passportData.documentCategory; - if (!passportMetadata) { - console.log('Passport metadata is null'); - return { status: 'passport_metadata_missing', details: passportData.dsc }; - } - if (!passportMetadata.cscaFound) { - console.log('CSCA not found'); - return { status: 'csca_not_found', details: passportData.dsc }; - } - const circuitNameRegister = getCircuitNameFromPassportData( - passportData, - 'register', - ); - const deployedCircuits = - useProtocolStore.getState()[document].deployed_circuits; // change this to the document type - if ( - !circuitNameRegister || - !( - deployedCircuits.REGISTER.includes(circuitNameRegister) || - deployedCircuits.REGISTER_ID.includes(circuitNameRegister) - ) - ) { - return { - status: 'registration_circuit_not_supported', - details: circuitNameRegister, - }; - } - const circuitNameDsc = getCircuitNameFromPassportData(passportData, 'dsc'); - if ( - !circuitNameDsc || - !( - deployedCircuits.DSC.includes(circuitNameDsc) || - deployedCircuits.DSC_ID.includes(circuitNameDsc) - ) - ) { - console.log('DSC circuit not supported:', circuitNameDsc); - return { status: 'dsc_circuit_not_supported', details: circuitNameDsc }; - } - console.log('Passport supported'); - return { status: 'passport_supported', details: 'null' }; -} - -export async function isUserRegistered( - passportData: PassportData, - secret: string, -) { - if (!passportData) { - return false; - } - const attestationId = - passportData.documentCategory === 'passport' - ? PASSPORT_ATTESTATION_ID - : ID_CARD_ATTESTATION_ID; - const commitment = generateCommitment(secret, attestationId, passportData); - const document: DocumentCategory = passportData.documentCategory; - const serializedTree = useProtocolStore.getState()[document].commitment_tree; - const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serializedTree); - const index = tree.indexOf(BigInt(commitment)); - return index !== -1; -} - -export async function isUserRegisteredWithAlternativeCSCA( - passportData: PassportData, - secret: string, -): Promise<{ isRegistered: boolean; csca: string | null }> { - if (!passportData) { - console.error('Passport data is null'); - return { isRegistered: false, csca: null }; - } - const document: DocumentCategory = passportData.documentCategory; - const alternativeCSCA = - useProtocolStore.getState()[document].alternative_csca; - console.log('alternativeCSCA: ', alternativeCSCA); - const { commitment_list, csca_list } = generateCommitmentInApp( - secret, - document === 'passport' ? PASSPORT_ATTESTATION_ID : ID_CARD_ATTESTATION_ID, - passportData, - alternativeCSCA, - ); - - if (commitment_list.length === 0) { - console.error( - 'No valid CSCA certificates could be parsed from alternativeCSCA', - ); - return { isRegistered: false, csca: null }; - } - - const serializedTree = useProtocolStore.getState()[document].commitment_tree; - const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serializedTree); - for (let i = 0; i < commitment_list.length; i++) { - const commitment = commitment_list[i]; - const index = tree.indexOf(BigInt(commitment)); - if (index !== -1) { - return { isRegistered: true, csca: csca_list[i] }; - } - } - console.warn( - 'None of the following CSCA correspond to the commitment:', - csca_list, - ); - return { isRegistered: false, csca: null }; -} -export async function isDocumentNullified(passportData: PassportData) { - const nullifier = generateNullifier(passportData); - const nullifierHex = `0x${BigInt(nullifier).toString(16)}`; - const attestationId = - passportData.documentCategory === 'passport' - ? '0x0000000000000000000000000000000000000000000000000000000000000001' - : '0x0000000000000000000000000000000000000000000000000000000000000002'; - console.log('checking for nullifier', nullifierHex, attestationId); - const baseUrl = passportData.mock === false ? API_URL : API_URL_STAGING; - const response = await fetch( - `${baseUrl}/is-nullifier-onchain-with-attestation-id`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - nullifier: nullifierHex, - attestation_id: attestationId, - }), - }, - ); - const data = await response.json(); - console.log('isDocumentNullified', data); - return data.data; -} - -export async function checkIfPassportDscIsInTree( - passportData: PassportData, - dscTree: string, -): Promise { - const hashFunction = (a: any, b: any) => poseidon2([a, b]); - const tree = LeanIMT.import(hashFunction, dscTree); - const leaf = getLeafDscTree( - passportData.dsc_parsed!, - passportData.csca_parsed!, - ); - const index = tree.indexOf(BigInt(leaf)); - if (index === -1) { - console.log('DSC not found in the tree'); - return false; - } else { - console.log('DSC found in the tree'); - return true; - } -} - -export function generateCommitmentInApp( - secret: string, - attestation_id: string, - passportData: PassportData, - alternativeCSCA: Record, -) { - const dg1_packed_hash = packBytesAndPoseidon(formatMrz(passportData.mrz)); - const eContent_packed_hash = packBytesAndPoseidon( - ( - hash( - passportData.passportMetadata!.eContentHashFunction, - Array.from(passportData.eContent), - 'bytes', - ) as number[] - ) - // eslint-disable-next-line no-bitwise - .map(byte => byte & 0xff), - ); - - const csca_list: string[] = []; - const commitment_list: string[] = []; - - for (const [cscaKey, cscaValue] of Object.entries(alternativeCSCA)) { - try { - const formattedCsca = formatCSCAPem(cscaValue); - const cscaParsed = parseCertificateSimple(formattedCsca); - - const commitment = poseidon5([ - secret, - attestation_id, - dg1_packed_hash, - eContent_packed_hash, - getLeafDscTree(passportData.dsc_parsed!, cscaParsed), - ]).toString(); - - csca_list.push(formatCSCAPem(cscaValue)); - commitment_list.push(commitment); - } catch (error) { - console.warn( - `Failed to parse CSCA certificate for key ${cscaKey}:`, - error, - ); - } - } - - if (commitment_list.length === 0) { - console.error('No valid CSCA certificates found in alternativeCSCA'); - } - - return { commitment_list, csca_list }; -} - -function formatCSCAPem(cscaPem: string): string { - let cleanedPem = cscaPem.trim(); - - if (!cleanedPem.includes('-----BEGIN CERTIFICATE-----')) { - cleanedPem = cleanedPem.replace(/[^A-Za-z0-9+/=]/g, ''); - try { - Buffer.from(cleanedPem, 'base64'); - } catch (error) { - throw new Error(`Invalid base64 certificate data: ${error}`); - } - cleanedPem = `-----BEGIN CERTIFICATE-----\n${cleanedPem}\n-----END CERTIFICATE-----`; - } - return cleanedPem; -} - -export function isPassportDataValid(passportData: PassportData) { - if (!passportData) { - trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { - error: 'Passport data is null', - }); - return false; - } - if (!passportData.passportMetadata) { - trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { - error: 'Passport metadata is null', - }); - return false; - } - if (!passportData.passportMetadata.dg1HashFunction) { - trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { - mock: passportData.mock, - dsc: passportData.dsc, - error: 'DG1 hash function is null', - }); - return false; - } - if (!passportData.passportMetadata.eContentHashFunction) { - trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { - mock: passportData.mock, - dsc: passportData.dsc, - documentCategory: passportData.documentCategory, - error: 'EContent hash function is null', - }); - return false; - } - if (!passportData.passportMetadata.signedAttrHashFunction) { - trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { - mock: passportData.mock, - dsc: passportData.dsc, - documentCategory: passportData.documentCategory, - error: 'Signed attribute hash function is null', - }); - return false; - } - return true; -} - -export function migratePassportData(passportData: PassportData): PassportData { - const migratedData = { ...passportData } as any; - if (!('documentCategory' in migratedData) || !('mock' in migratedData)) { - if ('documentType' in migratedData && migratedData.documentType) { - migratedData.mock = migratedData.documentType.startsWith('mock'); - migratedData.documentCategory = migratedData.documentType.includes( - 'passport', - ) - ? 'passport' - : 'id_card'; - } else { - migratedData.documentType = 'passport'; - migratedData.documentCategory = 'passport'; - migratedData.mock = false; - } - // console.log('Migrated passport data:', migratedData); - } - return migratedData as PassportData; -} - -export async function hasAnyValidRegisteredDocument(): Promise { - try { - const catalog = await loadDocumentCatalog(); - return catalog.documents.some(doc => doc.isRegistered === true); - } catch (error) { - console.error('Error loading document catalog:', error); - return false; - } -} - /** * This function checks and updates registration states for all documents and updates the `isRegistered`. */ @@ -417,3 +124,299 @@ export async function checkAndUpdateRegistrationStates(): Promise { console.log('Registration state check and update completed'); } + +export async function checkIfPassportDscIsInTree( + passportData: PassportData, + dscTree: string, +): Promise { + const hashFunction = (a: any, b: any) => poseidon2([a, b]); + const tree = LeanIMT.import(hashFunction, dscTree); + const leaf = getLeafDscTree( + passportData.dsc_parsed!, + passportData.csca_parsed!, + ); + const index = tree.indexOf(BigInt(leaf)); + if (index === -1) { + console.log('DSC not found in the tree'); + return false; + } else { + console.log('DSC found in the tree'); + return true; + } +} + +export async function checkPassportSupported( + passportData: PassportData, +): Promise<{ + status: PassportSupportStatus; + details: string; +}> { + const passportMetadata = passportData.passportMetadata; + const document: DocumentCategory = passportData.documentCategory; + if (!passportMetadata) { + console.log('Passport metadata is null'); + return { status: 'passport_metadata_missing', details: passportData.dsc }; + } + if (!passportMetadata.cscaFound) { + console.log('CSCA not found'); + return { status: 'csca_not_found', details: passportData.dsc }; + } + const circuitNameRegister = getCircuitNameFromPassportData( + passportData, + 'register', + ); + const deployedCircuits = + useProtocolStore.getState()[document].deployed_circuits; // change this to the document type + if ( + !circuitNameRegister || + !( + deployedCircuits.REGISTER.includes(circuitNameRegister) || + deployedCircuits.REGISTER_ID.includes(circuitNameRegister) + ) + ) { + return { + status: 'registration_circuit_not_supported', + details: circuitNameRegister, + }; + } + const circuitNameDsc = getCircuitNameFromPassportData(passportData, 'dsc'); + if ( + !circuitNameDsc || + !( + deployedCircuits.DSC.includes(circuitNameDsc) || + deployedCircuits.DSC_ID.includes(circuitNameDsc) + ) + ) { + console.log('DSC circuit not supported:', circuitNameDsc); + return { status: 'dsc_circuit_not_supported', details: circuitNameDsc }; + } + console.log('Passport supported'); + return { status: 'passport_supported', details: 'null' }; +} + +export function generateCommitmentInApp( + secret: string, + attestation_id: string, + passportData: PassportData, + alternativeCSCA: Record, +) { + const dg1_packed_hash = packBytesAndPoseidon(formatMrz(passportData.mrz)); + const eContent_packed_hash = packBytesAndPoseidon( + ( + hash( + passportData.passportMetadata!.eContentHashFunction, + Array.from(passportData.eContent), + 'bytes', + ) as number[] + ) + // eslint-disable-next-line no-bitwise + .map(byte => byte & 0xff), + ); + + const csca_list: string[] = []; + const commitment_list: string[] = []; + + for (const [cscaKey, cscaValue] of Object.entries(alternativeCSCA)) { + try { + const formattedCsca = formatCSCAPem(cscaValue); + const cscaParsed = parseCertificateSimple(formattedCsca); + + const commitment = poseidon5([ + secret, + attestation_id, + dg1_packed_hash, + eContent_packed_hash, + getLeafDscTree(passportData.dsc_parsed!, cscaParsed), + ]).toString(); + + csca_list.push(formatCSCAPem(cscaValue)); + commitment_list.push(commitment); + } catch (error) { + console.warn( + `Failed to parse CSCA certificate for key ${cscaKey}:`, + error, + ); + } + } + + if (commitment_list.length === 0) { + console.error('No valid CSCA certificates found in alternativeCSCA'); + } + + return { commitment_list, csca_list }; +} + +export async function hasAnyValidRegisteredDocument(): Promise { + try { + const catalog = await loadDocumentCatalog(); + return catalog.documents.some(doc => doc.isRegistered === true); + } catch (error) { + console.error('Error loading document catalog:', error); + return false; + } +} + +export async function isDocumentNullified(passportData: PassportData) { + const nullifier = generateNullifier(passportData); + const nullifierHex = `0x${BigInt(nullifier).toString(16)}`; + const attestationId = + passportData.documentCategory === 'passport' + ? '0x0000000000000000000000000000000000000000000000000000000000000001' + : '0x0000000000000000000000000000000000000000000000000000000000000002'; + console.log('checking for nullifier', nullifierHex, attestationId); + const baseUrl = passportData.mock === false ? API_URL : API_URL_STAGING; + const response = await fetch( + `${baseUrl}/is-nullifier-onchain-with-attestation-id`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + nullifier: nullifierHex, + attestation_id: attestationId, + }), + }, + ); + const data = await response.json(); + console.log('isDocumentNullified', data); + return data.data; +} + +function formatCSCAPem(cscaPem: string): string { + let cleanedPem = cscaPem.trim(); + + if (!cleanedPem.includes('-----BEGIN CERTIFICATE-----')) { + cleanedPem = cleanedPem.replace(/[^A-Za-z0-9+/=]/g, ''); + try { + Buffer.from(cleanedPem, 'base64'); + } catch (error) { + throw new Error(`Invalid base64 certificate data: ${error}`); + } + cleanedPem = `-----BEGIN CERTIFICATE-----\n${cleanedPem}\n-----END CERTIFICATE-----`; + } + return cleanedPem; +} + +export function isPassportDataValid(passportData: PassportData) { + if (!passportData) { + trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { + error: 'Passport data is null', + }); + return false; + } + if (!passportData.passportMetadata) { + trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { + error: 'Passport metadata is null', + }); + return false; + } + if (!passportData.passportMetadata.dg1HashFunction) { + trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { + mock: passportData.mock, + dsc: passportData.dsc, + error: 'DG1 hash function is null', + }); + return false; + } + if (!passportData.passportMetadata.eContentHashFunction) { + trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { + mock: passportData.mock, + dsc: passportData.dsc, + documentCategory: passportData.documentCategory, + error: 'EContent hash function is null', + }); + return false; + } + if (!passportData.passportMetadata.signedAttrHashFunction) { + trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { + mock: passportData.mock, + dsc: passportData.dsc, + documentCategory: passportData.documentCategory, + error: 'Signed attribute hash function is null', + }); + return false; + } + return true; +} + +export async function isUserRegistered( + passportData: PassportData, + secret: string, +) { + if (!passportData) { + return false; + } + const attestationId = + passportData.documentCategory === 'passport' + ? PASSPORT_ATTESTATION_ID + : ID_CARD_ATTESTATION_ID; + const commitment = generateCommitment(secret, attestationId, passportData); + const document: DocumentCategory = passportData.documentCategory; + const serializedTree = useProtocolStore.getState()[document].commitment_tree; + const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serializedTree); + const index = tree.indexOf(BigInt(commitment)); + return index !== -1; +} + +export async function isUserRegisteredWithAlternativeCSCA( + passportData: PassportData, + secret: string, +): Promise<{ isRegistered: boolean; csca: string | null }> { + if (!passportData) { + console.error('Passport data is null'); + return { isRegistered: false, csca: null }; + } + const document: DocumentCategory = passportData.documentCategory; + const alternativeCSCA = + useProtocolStore.getState()[document].alternative_csca; + console.log('alternativeCSCA: ', alternativeCSCA); + const { commitment_list, csca_list } = generateCommitmentInApp( + secret, + document === 'passport' ? PASSPORT_ATTESTATION_ID : ID_CARD_ATTESTATION_ID, + passportData, + alternativeCSCA, + ); + + if (commitment_list.length === 0) { + console.error( + 'No valid CSCA certificates could be parsed from alternativeCSCA', + ); + return { isRegistered: false, csca: null }; + } + + const serializedTree = useProtocolStore.getState()[document].commitment_tree; + const tree = LeanIMT.import((a, b) => poseidon2([a, b]), serializedTree); + for (let i = 0; i < commitment_list.length; i++) { + const commitment = commitment_list[i]; + const index = tree.indexOf(BigInt(commitment)); + if (index !== -1) { + return { isRegistered: true, csca: csca_list[i] }; + } + } + console.warn( + 'None of the following CSCA correspond to the commitment:', + csca_list, + ); + return { isRegistered: false, csca: null }; +} + +export function migratePassportData(passportData: PassportData): PassportData { + const migratedData = { ...passportData } as any; + if (!('documentCategory' in migratedData) || !('mock' in migratedData)) { + if ('documentType' in migratedData && migratedData.documentType) { + migratedData.mock = migratedData.documentType.startsWith('mock'); + migratedData.documentCategory = migratedData.documentType.includes( + 'passport', + ) + ? 'passport' + : 'id_card'; + } else { + migratedData.documentType = 'passport'; + migratedData.documentCategory = 'passport'; + migratedData.mock = false; + } + // console.log('Migrated passport data:', migratedData); + } + return migratedData as PassportData; +} diff --git a/app/src/utils/utils.ts b/app/src/utils/utils.ts index 24eb492f7..27906df19 100644 --- a/app/src/utils/utils.ts +++ b/app/src/utils/utils.ts @@ -1,5 +1,22 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +export function checkScannedInfo( + passportNumber: string, + dateOfBirth: string, + dateOfExpiry: string, +): boolean { + if (passportNumber.length > 9) { + return false; + } + if (dateOfBirth.length !== 6) { + return false; + } + if (dateOfExpiry.length !== 6) { + return false; + } + return true; +} + // Handles both TD1 (3 lines, 30 chars each) and TD3 (2 lines, 44 chars each) formats export function extractMRZInfo(mrzString: string) { const mrzLines = mrzString.split('\n'); @@ -56,20 +73,3 @@ export function formatDateToYYMMDD(inputDate: string) { // Concatenate components into YYMMDD format return year + month + day; } - -export function checkScannedInfo( - passportNumber: string, - dateOfBirth: string, - dateOfExpiry: string, -): boolean { - if (passportNumber.length > 9) { - return false; - } - if (dateOfBirth.length !== 6) { - return false; - } - if (dateOfExpiry.length !== 6) { - return false; - } - return true; -} diff --git a/app/tamagui.config.ts b/app/tamagui.config.ts index 8143ad433..01a050bec 100644 --- a/app/tamagui.config.ts +++ b/app/tamagui.config.ts @@ -1,7 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { config } from '@tamagui/config/v3'; import { createFont, createTamagui } from 'tamagui'; + +import { config } from '@tamagui/config/v3'; + const commonSizes = { 1: 12, 2: 14, diff --git a/app/tests/__setup__/@env.js b/app/tests/__setup__/@env.js index 70a82078a..c3c5d4356 100644 --- a/app/tests/__setup__/@env.js +++ b/app/tests/__setup__/@env.js @@ -1,12 +1,18 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -export const IS_TEST_BUILD = false; -export const GOOGLE_SIGNIN_ANDROID_CLIENT_ID = 'mock-google-client-id'; -export const GOOGLE_SIGNIN_WEB_CLIENT_ID = 'mock-google-web-client-id'; -export const SENTRY_DSN = 'mock-sentry-dsn'; -export const SEGMENT_KEY = 'mock-segment-key'; -export const ENABLE_DEBUG_LOGS = false; -export const DEFAULT_PNUMBER = undefined; export const DEFAULT_DOB = undefined; + export const DEFAULT_DOE = undefined; + +export const DEFAULT_PNUMBER = undefined; + +export const ENABLE_DEBUG_LOGS = false; + +export const GOOGLE_SIGNIN_ANDROID_CLIENT_ID = 'mock-google-client-id'; + +export const GOOGLE_SIGNIN_WEB_CLIENT_ID = 'mock-google-web-client-id'; + +export const IS_TEST_BUILD = false; export const MIXPANEL_NFC_PROJECT_TOKEN = undefined; +export const SEGMENT_KEY = 'mock-segment-key'; +export const SENTRY_DSN = 'mock-sentry-dsn'; diff --git a/app/tests/__setup__/notificationServiceMock.js b/app/tests/__setup__/notificationServiceMock.js index 0468aa48c..3ea2c5d1a 100644 --- a/app/tests/__setup__/notificationServiceMock.js +++ b/app/tests/__setup__/notificationServiceMock.js @@ -1,18 +1,17 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -/* global jest */ +export const RemoteMessage = {}; +export const getFCMToken = jest.fn().mockResolvedValue('mock-fcm-token'); + +/* global jest */ // Mock for notificationService.ts export const getStateMessage = jest.fn().mockImplementation(() => { return 'Mock state message'; }); -export const requestNotificationPermission = jest.fn().mockResolvedValue(true); - -export const getFCMToken = jest.fn().mockResolvedValue('mock-fcm-token'); - export const registerDeviceToken = jest.fn().mockResolvedValue(); -export const setupNotifications = jest.fn().mockReturnValue(jest.fn()); +export const requestNotificationPermission = jest.fn().mockResolvedValue(true); -export const RemoteMessage = {}; +export const setupNotifications = jest.fn().mockReturnValue(jest.fn()); diff --git a/app/tests/src/RemoteConfig.test.ts b/app/tests/src/RemoteConfig.test.ts index eec13cff5..538ec6d37 100644 --- a/app/tests/src/RemoteConfig.test.ts +++ b/app/tests/src/RemoteConfig.test.ts @@ -1,6 +1,17 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { jest } from '@jest/globals'; +// Now import the module under test +import { + clearAllLocalOverrides, + clearLocalOverride, + getAllFeatureFlags, + getFeatureFlag, + getLocalOverrides, + setLocalOverride, +} from '../../src/RemoteConfig'; + +// Import the mocked AsyncStorage for test controls +import AsyncStorage from '@react-native-async-storage/async-storage'; // Mock AsyncStorage with a default export jest.mock('@react-native-async-storage/async-storage', () => ({ @@ -26,25 +37,12 @@ jest.mock('@react-native-firebase/remote-config', () => ({ default: () => mockRemoteConfigInstance, })); -// Import the mocked AsyncStorage for test controls -import AsyncStorage from '@react-native-async-storage/async-storage'; - // Get the mock instances const mockAsyncStorage = AsyncStorage as jest.Mocked; const mockRemoteConfig = mockRemoteConfigInstance as jest.Mocked< typeof mockRemoteConfigInstance >; -// Now import the module under test -import { - clearAllLocalOverrides, - clearLocalOverride, - getAllFeatureFlags, - getFeatureFlag, - getLocalOverrides, - setLocalOverride, -} from '../../src/RemoteConfig'; - describe('RemoteConfig', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/app/tests/src/hooks/useAesopRedesign.test.ts b/app/tests/src/hooks/useAesopRedesign.test.ts index fa6a8dd6f..cc99e2820 100644 --- a/app/tests/src/hooks/useAesopRedesign.test.ts +++ b/app/tests/src/hooks/useAesopRedesign.test.ts @@ -1,12 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { renderHook } from '@testing-library/react-native'; - import { shouldShowAesopRedesign, useAesopRedesign, } from '../../../src/hooks/useAesopRedesign'; +import { renderHook } from '@testing-library/react-native'; + describe('useAesopRedesign', () => { describe('shouldShowAesopRedesign', () => { it('should return false when IS_TEST_BUILD is false', () => { diff --git a/app/tests/src/hooks/useAppUpdates.test.ts b/app/tests/src/hooks/useAppUpdates.test.ts index df542f21c..e13982199 100644 --- a/app/tests/src/hooks/useAppUpdates.test.ts +++ b/app/tests/src/hooks/useAppUpdates.test.ts @@ -1,5 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import { checkVersion } from 'react-native-check-version'; + +import { useAppUpdates } from '../../../src/hooks/useAppUpdates'; +import { registerModalCallbacks } from '../../../src/utils/modalCallbackRegistry'; + import { useNavigation } from '@react-navigation/native'; import { act, renderHook, waitFor } from '@testing-library/react-native'; @@ -19,11 +24,6 @@ jest.mock('../../../src/utils/analytics', () => () => ({ trackEvent: jest.fn(), })); -import { checkVersion } from 'react-native-check-version'; - -import { useAppUpdates } from '../../../src/hooks/useAppUpdates'; -import { registerModalCallbacks } from '../../../src/utils/modalCallbackRegistry'; - const navigate = jest.fn(); (useNavigation as jest.Mock).mockReturnValue({ navigate }); diff --git a/app/tests/src/hooks/useConnectionModal.test.ts b/app/tests/src/hooks/useConnectionModal.test.ts index c84a70f87..38e25e617 100644 --- a/app/tests/src/hooks/useConnectionModal.test.ts +++ b/app/tests/src/hooks/useConnectionModal.test.ts @@ -1,5 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import useConnectionModal from '../../../src/hooks/useConnectionModal'; +import { useModal } from '../../../src/hooks/useModal'; + import { act, renderHook } from '@testing-library/react-native'; jest.useFakeTimers(); @@ -15,9 +18,6 @@ jest.mock('@react-native-community/netinfo', () => ({ .mockReturnValue({ isConnected: false, isInternetReachable: false }), })); -import useConnectionModal from '../../../src/hooks/useConnectionModal'; -import { useModal } from '../../../src/hooks/useModal'; - const showModal = jest.fn(); const dismissModal = jest.fn(); (useModal as jest.Mock).mockReturnValue({ diff --git a/app/tests/src/hooks/useHapticNavigation.test.ts b/app/tests/src/hooks/useHapticNavigation.test.ts index a7df0ce63..c334e5694 100644 --- a/app/tests/src/hooks/useHapticNavigation.test.ts +++ b/app/tests/src/hooks/useHapticNavigation.test.ts @@ -1,14 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { act, renderHook } from '@testing-library/react-native'; - +import useHapticNavigation from '../../../src/hooks/useHapticNavigation'; import { impactLight, impactMedium, selectionChange, } from '../../../src/utils/haptic'; +import { useNavigation } from '@react-navigation/native'; +import { act, renderHook } from '@testing-library/react-native'; + jest.mock('@react-navigation/native', () => ({ useNavigation: jest.fn(), })); @@ -23,8 +24,6 @@ const navigate = jest.fn(); const popTo = jest.fn(); (useNavigation as jest.Mock).mockReturnValue({ navigate, popTo }); -import useHapticNavigation from '../../../src/hooks/useHapticNavigation'; - describe('useHapticNavigation', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/app/tests/src/hooks/useMnemonic.test.ts b/app/tests/src/hooks/useMnemonic.test.ts index f2fb97c79..4477dab9c 100644 --- a/app/tests/src/hooks/useMnemonic.test.ts +++ b/app/tests/src/hooks/useMnemonic.test.ts @@ -1,14 +1,14 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import useMnemonic from '../../../src/hooks/useMnemonic'; +import { useAuth } from '../../../src/providers/authProvider'; + import { act, renderHook } from '@testing-library/react-native'; jest.mock('../../../src/providers/authProvider', () => ({ useAuth: jest.fn(), })); -import useMnemonic from '../../../src/hooks/useMnemonic'; -import { useAuth } from '../../../src/providers/authProvider'; - jest.mock('ethers', () => ({ ethers: { Mnemonic: { diff --git a/app/tests/src/hooks/useModal.test.ts b/app/tests/src/hooks/useModal.test.ts index 2ab81e5fb..5c5a5f255 100644 --- a/app/tests/src/hooks/useModal.test.ts +++ b/app/tests/src/hooks/useModal.test.ts @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { useNavigation } from '@react-navigation/native'; -import { act, renderHook } from '@testing-library/react-native'; - import { useModal } from '../../../src/hooks/useModal'; import { getModalCallbacks } from '../../../src/utils/modalCallbackRegistry'; +import { useNavigation } from '@react-navigation/native'; +import { act, renderHook } from '@testing-library/react-native'; + jest.mock('@react-navigation/native', () => ({ useNavigation: jest.fn(), })); diff --git a/app/tests/src/hooks/useRecoveryPrompts.test.ts b/app/tests/src/hooks/useRecoveryPrompts.test.ts index dd6f725c5..6353d9b2f 100644 --- a/app/tests/src/hooks/useRecoveryPrompts.test.ts +++ b/app/tests/src/hooks/useRecoveryPrompts.test.ts @@ -1,12 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { act, renderHook, waitFor } from '@testing-library/react-native'; - import { useModal } from '../../../src/hooks/useModal'; import useRecoveryPrompts from '../../../src/hooks/useRecoveryPrompts'; import { usePassport } from '../../../src/providers/passportDataProvider'; import { useSettingStore } from '../../../src/stores/settingStore'; +import { act, renderHook, waitFor } from '@testing-library/react-native'; + jest.mock('../../../src/hooks/useModal'); jest.mock('../../../src/providers/passportDataProvider'); jest.mock('../../../src/navigation', () => ({ diff --git a/app/tests/src/providers/remoteConfigProvider.test.tsx b/app/tests/src/providers/remoteConfigProvider.test.tsx index 494ea7987..bdf5e56fd 100644 --- a/app/tests/src/providers/remoteConfigProvider.test.tsx +++ b/app/tests/src/providers/remoteConfigProvider.test.tsx @@ -1,6 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { render, waitFor } from '@testing-library/react-native'; import React from 'react'; import { Text } from 'react-native'; @@ -8,14 +7,15 @@ import { RemoteConfigProvider, useRemoteConfig, } from '../../../src/providers/remoteConfigProvider'; +import { initRemoteConfig } from '../../../src/RemoteConfig'; + +import { render, waitFor } from '@testing-library/react-native'; // Mock the RemoteConfig module jest.mock('../../../src/RemoteConfig', () => ({ initRemoteConfig: jest.fn(), })); -import { initRemoteConfig } from '../../../src/RemoteConfig'; - const mockInitRemoteConfig = initRemoteConfig as jest.MockedFunction< typeof initRemoteConfig >; diff --git a/app/tests/src/stores/database.test.ts b/app/tests/src/stores/database.test.ts index a10f04a6c..5f440601a 100644 --- a/app/tests/src/stores/database.test.ts +++ b/app/tests/src/stores/database.test.ts @@ -16,6 +16,15 @@ const mockSQLite = SQLite as any; describe('database (SQLite)', () => { let mockDb: any; + // Suppress console errors during testing to avoid cluttering output + beforeAll(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + beforeEach(() => { // Reset all mocks jest.clearAllMocks(); diff --git a/app/tests/src/stores/proofHistoryStore.test.ts b/app/tests/src/stores/proofHistoryStore.test.ts index 667abf064..78fd90e00 100644 --- a/app/tests/src/stores/proofHistoryStore.test.ts +++ b/app/tests/src/stores/proofHistoryStore.test.ts @@ -1,12 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { act } from '@testing-library/react-native'; import { io } from 'socket.io-client'; import { database } from '../../../src/stores/database'; import { ProofStatus } from '../../../src/stores/proof-types'; import { useProofHistoryStore } from '../../../src/stores/proofHistoryStore'; +import { act } from '@testing-library/react-native'; + // Mock socket.io-client jest.mock('socket.io-client', () => ({ io: jest.fn(), @@ -30,6 +31,15 @@ const mockIo = io as any; describe('proofHistoryStore', () => { let mockSocket: any; + // Suppress console errors during testing to avoid cluttering output + beforeAll(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + beforeEach(() => { jest.clearAllMocks(); diff --git a/app/tests/src/stores/settingStore.test.ts b/app/tests/src/stores/settingStore.test.ts index 1c9783c5b..76e5976a8 100644 --- a/app/tests/src/stores/settingStore.test.ts +++ b/app/tests/src/stores/settingStore.test.ts @@ -1,9 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { act } from '@testing-library/react-native'; - import { useSettingStore } from '../../../src/stores/settingStore'; +import { act } from '@testing-library/react-native'; + describe('settingStore', () => { beforeEach(() => { act(() => { diff --git a/app/tests/utils/cloudBackup.test.ts b/app/tests/utils/cloudBackup.test.ts index c6605121e..26f77be14 100644 --- a/app/tests/utils/cloudBackup.test.ts +++ b/app/tests/utils/cloudBackup.test.ts @@ -1,7 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { renderHook } from '@testing-library/react-native'; +import { ethers } from 'ethers'; import { Platform } from 'react-native'; +import { CloudStorage } from 'react-native-cloud-storage'; + +import { useBackupMnemonic } from '../../src/utils/cloudBackup'; +import { createGDrive } from '../../src/utils/cloudBackup/google'; + +// Import after mocks +import { GDrive } from '@robinbobin/react-native-google-drive-api-wrapper'; +import { renderHook } from '@testing-library/react-native'; // Mock dependencies jest.mock('react-native-cloud-storage', () => ({ @@ -46,14 +54,6 @@ jest.mock('ethers', () => ({ }, })); -// Import after mocks -import { GDrive } from '@robinbobin/react-native-google-drive-api-wrapper'; -import { ethers } from 'ethers'; -import { CloudStorage } from 'react-native-cloud-storage'; - -import { useBackupMnemonic } from '../../src/utils/cloudBackup'; -import { createGDrive } from '../../src/utils/cloudBackup/google'; - // Mock implementations const mockGDriveInstance = { accessToken: '', diff --git a/app/tests/utils/ethers.test.ts b/app/tests/utils/ethers.test.ts index 29f4f7ffb..fee732352 100644 --- a/app/tests/utils/ethers.test.ts +++ b/app/tests/utils/ethers.test.ts @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 // Register crypto polyfills -// eslint-disable-next-line simple-import-sort/imports import { ethers } from 'ethers'; import '../../src/utils/ethers'; diff --git a/app/tests/utils/notificationService.test.ts b/app/tests/utils/notificationService.test.ts index fa31c6542..526723c5e 100644 --- a/app/tests/utils/notificationService.test.ts +++ b/app/tests/utils/notificationService.test.ts @@ -1,26 +1,26 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -jest.unmock('../../src/utils/notifications/notificationService'); - import { PermissionsAndroid, Platform } from 'react-native'; +jest.unmock('../../src/utils/notifications/notificationService'); + jest.mock('@react-native-firebase/messaging', () => { const instance = { requestPermission: jest.fn(), getToken: jest.fn(), }; - const mockFn: any = () => instance; + const mockFn = () => instance; mockFn._instance = instance; mockFn.AuthorizationStatus = { AUTHORIZED: 1, PROVISIONAL: 2 }; return { __esModule: true, default: mockFn }; }); -let messagingMock: any; +let messagingMock: ReturnType; global.fetch = jest.fn(); describe('notificationService', () => { - let service: typeof import('../../src/utils/notifications/notificationService'); + let service: any; // Using any here since we're dynamically requiring the module in tests beforeEach(() => { jest.resetModules(); @@ -46,8 +46,12 @@ describe('notificationService', () => { writable: true, }); PermissionsAndroid.request = jest.fn().mockResolvedValue('granted'); - PermissionsAndroid.PERMISSIONS = { POST_NOTIFICATIONS: 'post' } as any; - PermissionsAndroid.RESULTS = { GRANTED: 'granted' } as any; + PermissionsAndroid.PERMISSIONS = { + POST_NOTIFICATIONS: 'post', + } as typeof PermissionsAndroid.PERMISSIONS; + PermissionsAndroid.RESULTS = { + GRANTED: 'granted', + } as typeof PermissionsAndroid.RESULTS; const result = await service.requestNotificationPermission(); expect(result).toBe(true); @@ -64,11 +68,13 @@ describe('notificationService', () => { writable: true, }); PermissionsAndroid.request = jest.fn().mockResolvedValue('denied'); - PermissionsAndroid.PERMISSIONS = { POST_NOTIFICATIONS: 'post' } as any; + PermissionsAndroid.PERMISSIONS = { + POST_NOTIFICATIONS: 'post', + } as typeof PermissionsAndroid.PERMISSIONS; PermissionsAndroid.RESULTS = { GRANTED: 'granted', DENIED: 'denied', - } as any; + } as typeof PermissionsAndroid.RESULTS; const result = await service.requestNotificationPermission(); expect(result).toBe(false); @@ -86,11 +92,13 @@ describe('notificationService', () => { PermissionsAndroid.request = jest .fn() .mockResolvedValue('never_ask_again'); - PermissionsAndroid.PERMISSIONS = { POST_NOTIFICATIONS: 'post' } as any; + PermissionsAndroid.PERMISSIONS = { + POST_NOTIFICATIONS: 'post', + } as typeof PermissionsAndroid.PERMISSIONS; PermissionsAndroid.RESULTS = { GRANTED: 'granted', NEVER_ASK_AGAIN: 'never_ask_again', - } as any; + } as typeof PermissionsAndroid.RESULTS; const result = await service.requestNotificationPermission(); expect(result).toBe(false); diff --git a/app/tests/utils/proving/coseVerify.test.ts b/app/tests/utils/proving/coseVerify.test.ts index c02ef0656..bfbbc3d0a 100644 --- a/app/tests/utils/proving/coseVerify.test.ts +++ b/app/tests/utils/proving/coseVerify.test.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 import { Buffer } from 'buffer'; + const { decode, encode } = require('@stablelib/cbor'); const cose = require('../../../src/utils/proving/cose').default; diff --git a/app/tests/utils/proving/loadingScreenStateText.test.ts b/app/tests/utils/proving/loadingScreenStateText.test.ts index 5b87b1b0c..87c87a25e 100644 --- a/app/tests/utils/proving/loadingScreenStateText.test.ts +++ b/app/tests/utils/proving/loadingScreenStateText.test.ts @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 +import type { PassportMetadata } from '../../../src/utils/proving/loadingScreenStateText'; import { getLoadingScreenText, getProvingTimeEstimate, - PassportMetadata, } from '../../../src/utils/proving/loadingScreenStateText'; -import { ProvingStateType } from '../../../src/utils/proving/provingMachine'; +import type { ProvingStateType } from '../../../src/utils/proving/provingMachine'; describe('stateLoadingScreenText', () => { // Default metadata for basic tests diff --git a/app/tests/utils/proving/provingMachine.generatePayload.test.ts b/app/tests/utils/proving/provingMachine.generatePayload.test.ts index 2930aa622..6b7f185a4 100644 --- a/app/tests/utils/proving/provingMachine.generatePayload.test.ts +++ b/app/tests/utils/proving/provingMachine.generatePayload.test.ts @@ -1,7 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { jest } from '@jest/globals'; - import { useProtocolStore } from '../../../src/stores/protocolStore'; import { useSelfAppStore } from '../../../src/stores/selfAppStore'; import { useProvingStore } from '../../../src/utils/proving/provingMachine'; diff --git a/app/tests/web-build-render.test.ts b/app/tests/web-build-render.test.ts index 4ea851027..91fde4a3c 100644 --- a/app/tests/web-build-render.test.ts +++ b/app/tests/web-build-render.test.ts @@ -4,9 +4,10 @@ * @jest-environment node */ -import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'; import { execSync, spawn } from 'child_process'; +import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'; + // Ensure fetch is available (Node.js 18+ has built-in fetch) if (typeof fetch === 'undefined') { throw new Error( diff --git a/app/vite.config.ts b/app/vite.config.ts index a9bf17d54..6fa921686 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -1,13 +1,14 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import { tamaguiPlugin } from '@tamagui/vite-plugin'; -import react from '@vitejs/plugin-react-swc'; import path from 'path'; import { visualizer } from 'rollup-plugin-visualizer'; import { fileURLToPath } from 'url'; import { defineConfig } from 'vite'; import svgr from 'vite-plugin-svgr'; +import { tamaguiPlugin } from '@tamagui/vite-plugin'; +import react from '@vitejs/plugin-react-swc'; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/app/web/main.tsx b/app/web/main.tsx index a9fce385c..dac8632a6 100644 --- a/app/web/main.tsx +++ b/app/web/main.tsx @@ -1,9 +1,5 @@ // SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11 -import 'react-native-get-random-values'; -import './fonts.css'; -import './reset.css'; - import React from 'react'; import { createRoot } from 'react-dom/client'; import { TamaguiProvider, View } from 'tamagui'; @@ -11,6 +7,10 @@ import { TamaguiProvider, View } from 'tamagui'; import App from '../App'; import tamaguiConfig from '../tamagui.config.ts'; +import 'react-native-get-random-values'; +import './fonts.css'; +import './reset.css'; + const Root = () => ( diff --git a/common/.eslintrc.cjs b/common/.eslintrc.cjs new file mode 100644 index 000000000..e77260144 --- /dev/null +++ b/common/.eslintrc.cjs @@ -0,0 +1,116 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2021, + sourceType: 'module', + ecmaFeatures: { jsx: false }, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + ], + plugins: ['simple-import-sort', 'import', 'sort-exports'], + ignorePatterns: [ + 'node_modules/', + 'dist/', + '*.js.map', + '*.d.ts', + 'pubkeys/', + 'sanctionedCountries/', + ], + settings: { + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + project: './tsconfig.json', + }, + }, + }, + rules: { + // Enhanced Import/Export Rules + 'import/order': 'off', + 'no-duplicate-imports': 'off', + + // Import sorting with explicit groups for library structure + 'simple-import-sort/imports': [ + 'error', + { + groups: [ + // Node.js built-ins + ['^node:'], + ['^node:.*/'], + // External packages + ['^[a-zA-Z]'], + // Internal workspace packages + ['^@selfxyz/'], + // Internal relative imports + ['^[./]'], + ], + }, + ], + + // Export sorting - using sort-exports for better type prioritization + 'sort-exports/sort-exports': [ + 'error', + { sortDir: 'asc', ignoreCase: false, sortExportKindFirst: 'type' }, + ], + + // Type import enforcement - critical for tree shaking + '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }], + + // Standard import rules + 'import/first': 'error', + 'import/newline-after-import': 'error', + 'import/no-duplicates': 'error', + + // TypeScript Rules - only essential ones + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + + // General JavaScript Rules - minimal interference + 'no-console': 'off', + 'prefer-const': 'off', + 'no-empty': 'off', + 'no-undef': 'off', + 'no-control-regex': 'off', + 'no-useless-catch': 'off', + 'no-var': 'off', + 'no-multiple-empty-lines': 'off', + + // Custom rule to prevent export * (bad for tree shaking) + // This rule prevents the use of export * which disables tree shaking + // and can significantly increase bundle size. Use selective exports instead. + 'no-restricted-syntax': [ + 'error', + { + selector: 'ExportAllDeclaration', + message: + 'export * is forbidden. Use selective exports for better tree shaking. Example: export { specific1, specific2 } from "./module"', + }, + ], + }, + overrides: [ + { + files: ['*.cjs'], + env: { + node: true, + commonjs: true, + es6: true, + }, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'script', + }, + rules: { + '@typescript-eslint/no-var-requires': 'off', + 'no-undef': 'off', + }, + }, + ], +}; diff --git a/common/index.ts b/common/index.ts index 1499a8d8b..f44d23dc9 100644 --- a/common/index.ts +++ b/common/index.ts @@ -1,92 +1,92 @@ -// Constants exports -export { - TREE_URL, - TREE_URL_STAGING, - API_URL, - API_URL_STAGING, - WS_DB_RELAYER, - WS_DB_RELAYER_STAGING, - PCR0_MANAGER_ADDRESS, - RPC_URL, - PASSPORT_ATTESTATION_ID, - ID_CARD_ATTESTATION_ID, - DEFAULT_MAJORITY, - attributeToPosition, - attributeToPosition_ID, - countryCodes, - commonNames, - countries, - CSCA_TREE_URL, - DSC_TREE_URL, - CSCA_TREE_URL_STAGING, - DSC_TREE_URL_STAGING, - IDENTITY_TREE_URL, - IDENTITY_TREE_URL_STAGING, - CSCA_TREE_URL_ID_CARD, - DSC_TREE_URL_ID_CARD, - CSCA_TREE_URL_STAGING_ID_CARD, - DSC_TREE_URL_STAGING_ID_CARD, - IDENTITY_TREE_URL_ID_CARD, - IDENTITY_TREE_URL_STAGING_ID_CARD, -} from './src/constants/index.js'; - // Type exports from constants +export type { + CertificateData, + DocumentCategory, + IdDocInput, + PassportData, + PassportMetadata, + PublicKeyDetailsECDSA, + PublicKeyDetailsRSA, + SelfApp, + SelfAppDisclosureConfig, + UserIdType, +} from './src/utils/index.js'; + +// Constants exports export type { Country3LetterCode } from './src/constants/index.js'; // Utils exports export { - initPassportDataParsing, - findStartPubKeyIndex, - generateCommitment, - generateNullifier, - genMockIdDoc, - generateMockDSC, - genMockIdDocAndInitDataParsing, - genAndInitMockPassportData, - parseDscCertificateData, + API_URL, + API_URL_STAGING, + CSCA_TREE_URL, + CSCA_TREE_URL_ID_CARD, + CSCA_TREE_URL_STAGING, + CSCA_TREE_URL_STAGING_ID_CARD, + DEFAULT_MAJORITY, + DSC_TREE_URL, + DSC_TREE_URL_ID_CARD, + DSC_TREE_URL_STAGING, + DSC_TREE_URL_STAGING_ID_CARD, + IDENTITY_TREE_URL, + IDENTITY_TREE_URL_ID_CARD, + IDENTITY_TREE_URL_STAGING, + IDENTITY_TREE_URL_STAGING_ID_CARD, + ID_CARD_ATTESTATION_ID, + PASSPORT_ATTESTATION_ID, + PCR0_MANAGER_ADDRESS, + RPC_URL, + TREE_URL, + TREE_URL_STAGING, + WS_DB_RELAYER, + WS_DB_RELAYER_STAGING, + attributeToPosition, + attributeToPosition_ID, + commonNames, + countries, + countryCodes, +} from './src/constants/index.js'; + +// Type exports +export { + EndpointType, + Mode, + SelfAppBuilder, + bigIntToString, brutforceSignatureAlgorithmDsc, - parseCertificateSimple, - initElliptic, - getSKIPEM, - formatMrz, - getCircuitNameFromPassportData, - calculateUserIdentifierHash, - getSolidityPackedUserContextData, - getLeafCscaTree, - getLeafDscTree, buildSMT, + calculateUserIdentifierHash, + findStartPubKeyIndex, + formatEndpoint, + formatMrz, + genAndInitMockPassportData, + genMockIdDoc, + genMockIdDocAndInitDataParsing, generateCircuitInputsDSC, generateCircuitInputsRegister, generateCircuitInputsVCandDisclose, - Mode, - EndpointType, - SelfAppBuilder, + generateCommitment, + generateMockDSC, + generateNullifier, + getCircuitNameFromPassportData, + getLeafCscaTree, + getLeafDscTree, + getSKIPEM, + getSolidityPackedUserContextData, getUniversalLink, - formatEndpoint, hashEndpointWithScope, + initElliptic, + initPassportDataParsing, + parseCertificateSimple, + parseDscCertificateData, stringToBigInt, - bigIntToString, -} from './src/utils/index.js'; - -// Type exports -export type { - IdDocInput, - CertificateData, - PublicKeyDetailsECDSA, - PublicKeyDetailsRSA, - PassportMetadata, - UserIdType, - SelfApp, - SelfAppDisclosureConfig, - PassportData, - DocumentCategory, } from './src/utils/index.js'; // Hash utilities export { - flexiblePoseidon, - hash, - getHashLen, customHasher, + flexiblePoseidon, + getHashLen, + hash, packBytesAndPoseidon, } from './src/utils/hash.js'; diff --git a/common/package.json b/common/package.json index 9b8eacb00..4586dd1d1 100644 --- a/common/package.json +++ b/common/package.json @@ -23,11 +23,6 @@ "import": "./dist/esm/src/constants/index.js", "require": "./dist/cjs/src/constants/index.cjs" }, - "./constants/core": { - "types": "./dist/esm/src/constants/constants.d.ts", - "import": "./dist/esm/src/constants/constants.js", - "require": "./dist/cjs/src/constants/constants.cjs" - }, "./constants/constants": { "types": "./dist/esm/src/constants/constants.d.ts", "import": "./dist/esm/src/constants/constants.js", @@ -38,12 +33,12 @@ "import": "./dist/esm/src/constants/countries.js", "require": "./dist/cjs/src/constants/countries.cjs" }, - "./constants/hashes": { + "./constants/sampleDataHashes": { "types": "./dist/esm/src/constants/sampleDataHashes.d.ts", "import": "./dist/esm/src/constants/sampleDataHashes.js", "require": "./dist/cjs/src/constants/sampleDataHashes.cjs" }, - "./constants/mockCerts": { + "./constants/mockCertificates": { "types": "./dist/esm/src/constants/mockCertificates.d.ts", "import": "./dist/esm/src/constants/mockCertificates.js", "require": "./dist/cjs/src/constants/mockCertificates.cjs" @@ -111,31 +106,11 @@ "import": "./dist/esm/src/utils/bytes.js", "require": "./dist/cjs/src/utils/bytes.cjs" }, - "./utils/certificates": { + "./utils/certificate_parsing": { "types": "./dist/esm/src/utils/certificate_parsing/index.d.ts", "import": "./dist/esm/src/utils/certificate_parsing/index.js", "require": "./dist/cjs/src/utils/certificate_parsing/index.cjs" }, - "./utils/certificates/certUtils": { - "types": "./dist/esm/src/utils/certificate_parsing/certUtils.d.ts", - "import": "./dist/esm/src/utils/certificate_parsing/certUtils.js", - "require": "./dist/cjs/src/utils/certificate_parsing/certUtils.cjs" - }, - "./utils/certificates/curveUtils": { - "types": "./dist/esm/src/utils/certificate_parsing/curveUtils.d.ts", - "import": "./dist/esm/src/utils/certificate_parsing/curveUtils.js", - "require": "./dist/cjs/src/utils/certificate_parsing/curveUtils.cjs" - }, - "./utils/certificates/ellipticInit": { - "types": "./dist/esm/src/utils/certificate_parsing/ellipticInit.d.ts", - "import": "./dist/esm/src/utils/certificate_parsing/ellipticInit.js", - "require": "./dist/cjs/src/utils/certificate_parsing/ellipticInit.cjs" - }, - "./utils/certificates/oidUtils": { - "types": "./dist/esm/src/utils/certificate_parsing/oidUtils.d.ts", - "import": "./dist/esm/src/utils/certificate_parsing/oidUtils.js", - "require": "./dist/cjs/src/utils/certificate_parsing/oidUtils.cjs" - }, "./utils/certificate_parsing/elliptic": { "types": "./dist/esm/src/utils/certificate_parsing/elliptic.d.ts", "import": "./dist/esm/src/utils/certificate_parsing/elliptic.js", @@ -146,12 +121,12 @@ "import": "./dist/esm/src/utils/certificate_parsing/parseCertificateSimple.js", "require": "./dist/cjs/src/utils/certificate_parsing/parseCertificateSimple.cjs" }, - "./utils/certificates/parseNode": { + "./utils/certificate_parsing/parseNode": { "types": "./dist/esm/src/utils/certificate_parsing/parseNode.d.ts", "import": "./dist/esm/src/utils/certificate_parsing/parseNode.js", "require": "./dist/cjs/src/utils/certificate_parsing/parseNode.cjs" }, - "./utils/certificates/parseSimple": { + "./utils/certificate_parsing/parseSimple": { "types": "./dist/esm/src/utils/certificate_parsing/parseSimple.d.ts", "import": "./dist/esm/src/utils/certificate_parsing/parseSimple.js", "require": "./dist/cjs/src/utils/certificate_parsing/parseSimple.cjs" @@ -161,11 +136,6 @@ "import": "./dist/esm/src/utils/circuits/index.js", "require": "./dist/cjs/src/utils/circuits/index.cjs" }, - "./utils/circuitNames": { - "types": "./dist/esm/src/utils/circuits/circuitsName.d.ts", - "import": "./dist/esm/src/utils/circuits/circuitsName.js", - "require": "./dist/cjs/src/utils/circuits/circuitsName.cjs" - }, "./utils/circuits/circuitsName": { "types": "./dist/esm/src/utils/circuits/circuitsName.d.ts", "import": "./dist/esm/src/utils/circuits/circuitsName.js", @@ -216,26 +186,21 @@ "import": "./dist/esm/src/utils/contracts/index.js", "require": "./dist/cjs/src/utils/contracts/index.cjs" }, + "./utils/contracts/forbiddenCountries": { + "types": "./dist/esm/src/utils/contracts/forbiddenCountries.d.ts", + "import": "./dist/esm/src/utils/contracts/forbiddenCountries.js", + "require": "./dist/cjs/src/utils/contracts/forbiddenCountries.cjs" + }, "./utils/csca": { "types": "./dist/esm/src/utils/csca.d.ts", "import": "./dist/esm/src/utils/csca.js", "require": "./dist/cjs/src/utils/csca.cjs" }, - "./utils/curves": { - "types": "./dist/esm/src/utils/certificate_parsing/curves.d.ts", - "import": "./dist/esm/src/utils/certificate_parsing/curves.js", - "require": "./dist/cjs/src/utils/certificate_parsing/curves.cjs" - }, "./utils/date": { "types": "./dist/esm/src/utils/date.d.ts", "import": "./dist/esm/src/utils/date.js", "require": "./dist/cjs/src/utils/date.cjs" }, - "./utils/elliptic": { - "types": "./dist/esm/src/utils/certificate_parsing/elliptic.d.ts", - "import": "./dist/esm/src/utils/certificate_parsing/elliptic.js", - "require": "./dist/cjs/src/utils/certificate_parsing/elliptic.cjs" - }, "./utils/hash": { "types": "./dist/esm/src/utils/hash.d.ts", "import": "./dist/esm/src/utils/hash.js", @@ -256,26 +221,6 @@ "import": "./dist/esm/src/utils/hash/sha.js", "require": "./dist/cjs/src/utils/hash/sha.cjs" }, - "./utils/oids": { - "types": "./dist/esm/src/utils/certificate_parsing/oids.d.ts", - "import": "./dist/esm/src/utils/certificate_parsing/oids.js", - "require": "./dist/cjs/src/utils/certificate_parsing/oids.cjs" - }, - "./utils/passportDg1": { - "types": "./dist/esm/src/utils/passports/dg1.d.ts", - "import": "./dist/esm/src/utils/passports/dg1.js", - "require": "./dist/cjs/src/utils/passports/dg1.cjs" - }, - "./utils/passportFormat": { - "types": "./dist/esm/src/utils/passports/format.d.ts", - "import": "./dist/esm/src/utils/passports/format.js", - "require": "./dist/cjs/src/utils/passports/format.cjs" - }, - "./utils/passportMock": { - "types": "./dist/esm/src/utils/passports/mock.d.ts", - "import": "./dist/esm/src/utils/passports/mock.js", - "require": "./dist/cjs/src/utils/passports/mock.cjs" - }, "./utils/passports": { "types": "./dist/esm/src/utils/passports/index.d.ts", "import": "./dist/esm/src/utils/passports/index.js", @@ -336,11 +281,6 @@ "import": "./dist/esm/src/utils/passports/passport_parsing/parseDscCertificateData.js", "require": "./dist/cjs/src/utils/passports/passport_parsing/parseDscCertificateData.cjs" }, - "./utils/sanctions": { - "types": "./dist/esm/src/utils/contracts/forbiddenCountries.d.ts", - "import": "./dist/esm/src/utils/contracts/forbiddenCountries.js", - "require": "./dist/cjs/src/utils/contracts/forbiddenCountries.cjs" - }, "./utils/scope": { "types": "./dist/esm/src/utils/scope.d.ts", "import": "./dist/esm/src/utils/scope.js", @@ -350,6 +290,21 @@ "types": "./dist/esm/src/utils/trees.d.ts", "import": "./dist/esm/src/utils/trees.js", "require": "./dist/cjs/src/utils/trees.cjs" + }, + "./utils/certificates/parseSimple": { + "types": "./dist/esm/src/utils/certificate_parsing/parseSimple.d.ts", + "import": "./dist/esm/src/utils/certificate_parsing/parseSimple.js", + "require": "./dist/cjs/src/utils/certificate_parsing/parseSimple.cjs" + }, + "./utils/circuitNames": { + "types": "./dist/esm/src/utils/circuits/circuitsName.d.ts", + "import": "./dist/esm/src/utils/circuits/circuitsName.js", + "require": "./dist/cjs/src/utils/circuits/circuitsName.cjs" + }, + "./utils/passportFormat": { + "types": "./dist/esm/src/utils/passports/format.d.ts", + "import": "./dist/esm/src/utils/passports/format.js", + "require": "./dist/cjs/src/utils/passports/format.cjs" } }, "main": "./dist/cjs/index.cjs", @@ -367,10 +322,14 @@ "build:watch": "tsup --watch", "format": "prettier --write .", "lint": "prettier --check .", + "lint:imports": "eslint . --fix", + "lint:imports:check": "eslint .", + "nice": "yarn format && yarn lint:imports", + "nice:check": "yarn lint && yarn lint:imports:check", "prepublishOnly": "yarn build", "test": "NODE_OPTIONS='--loader ts-node/esm' ts-mocha tests/**/*.test.ts --exit", "test-base": "yarn ts-mocha -n import=tsx --max-old-space-size=8192 --paths -p tsconfig.json", - "test:exports": "node scripts/testExports.js", + "test:exports": "node scripts/validateExports.js && node scripts/testExports.js", "test:scope": "NODE_OPTIONS='--loader ts-node/esm' ts-mocha tests/scope.test.ts --exit", "types": "tsc -p tsconfig.json" }, @@ -405,6 +364,14 @@ "devDependencies": { "@types/js-sha1": "^0.6.3", "@types/node-forge": "^1.3.10", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-sort-exports": "^0.9.1", "mocha": "^10.7.3", "prettier": "^3.3.3", "ts-mocha": "^10.0.0", diff --git a/common/scripts/testExports.js b/common/scripts/testExports.js index ff28ffd19..d8020fb8d 100644 --- a/common/scripts/testExports.js +++ b/common/scripts/testExports.js @@ -4,9 +4,9 @@ * Test Clean Re-Exports - Verify that safe re-exports work correctly */ -import { fileURLToPath } from 'url'; +import { existsSync, readdirSync } from 'fs'; import { dirname, join, resolve } from 'path'; -import { existsSync } from 'fs'; +import { fileURLToPath } from 'url'; // Get the directory of the current script const __filename = fileURLToPath(import.meta.url); @@ -17,6 +17,12 @@ const BUILD_DIR = join(__dirname, '..', 'dist', 'esm'); console.log('🧹 Testing Clean Re-Export Implementation...\n'); +// Performance tracking +const startTime = Date.now(); +let totalTests = 0; +let passedTests = 0; +let failedTests = 0; + // Verify build directory exists before proceeding function verifyBuildDirectory() { if (!existsSync(BUILD_DIR)) { @@ -45,6 +51,86 @@ async function safeImport(modulePath, description) { } } +// Test a specific export and track results +async function testExport(modulePath, exportName, description) { + totalTests++; + try { + const module = await safeImport(modulePath, description); + const exportValue = module[exportName]; + + if (typeof exportValue === 'function' || typeof exportValue === 'object') { + console.log(` - ${exportName}: ${typeof exportValue} āœ…`); + passedTests++; + return true; + } else { + console.log(` - ${exportName}: ${typeof exportValue} āŒ (expected function/object)`); + failedTests++; + return false; + } + } catch (error) { + console.log(` - ${exportName}: āŒ (${error.message})`); + failedTests++; + return false; + } +} + +// Test all named exports from a module +async function testModuleExports(modulePath, description) { + totalTests++; + try { + const module = await safeImport(modulePath, description); + const exportNames = Object.keys(module); + + if (exportNames.length === 0) { + console.log(` - ${description}: No exports found āŒ`); + failedTests++; + return false; + } + + console.log(` - ${description}: ${exportNames.length} exports found āœ…`); + for (const exportName of exportNames) { + const exportValue = module[exportName]; + if (typeof exportValue === 'function' || typeof exportValue === 'object') { + console.log(` • ${exportName}: ${typeof exportValue} āœ…`); + passedTests++; + } else { + console.log(` • ${exportName}: ${typeof exportValue} āŒ`); + failedTests++; + } + } + return true; + } catch (error) { + console.log(` - ${description}: āŒ (${error.message})`); + failedTests++; + return false; + } +} + +// Discover and test exports from a directory +async function testDirectoryExports(dirPath, categoryName) { + const fullDirPath = join(BUILD_DIR, dirPath); + if (!existsSync(fullDirPath)) { + console.log(`āš ļø ${categoryName} directory not found: ${dirPath}`); + return; + } + + console.log(`āœ… Testing ${categoryName}...`); + + try { + const files = readdirSync(fullDirPath, { withFileTypes: true }); + const jsFiles = files + .filter((file) => file.isFile() && file.name.endsWith('.js')) + .map((file) => file.name.replace('.js', '')); + + for (const file of jsFiles) { + const modulePath = `${dirPath}/${file}.js`; + await testModuleExports(modulePath, `${categoryName}/${file}`); + } + } catch (error) { + console.error(`āŒ Error testing ${categoryName}:`, error.message); + } +} + async function testReExports() { try { // Verify build directory exists @@ -52,30 +138,47 @@ async function testReExports() { // Test Hash Re-Exports console.log('āœ… Testing Hash Re-Exports...'); - const { hash } = await safeImport('src/utils/hash/sha.js', 'hash module'); - const { flexiblePoseidon } = await safeImport('src/utils/hash/poseidon.js', 'poseidon module'); - const { customHasher } = await safeImport('src/utils/hash/custom.js', 'custom hasher module'); - console.log(' - hash (from sha):', typeof hash, 'āœ…'); - console.log(' - flexiblePoseidon (from poseidon):', typeof flexiblePoseidon, 'āœ…'); - console.log(' - customHasher (from custom):', typeof customHasher, 'āœ…'); + await testExport('src/utils/hash/sha.js', 'hash', 'hash module'); + await testExport('src/utils/hash/poseidon.js', 'flexiblePoseidon', 'poseidon module'); + await testExport('src/utils/hash/custom.js', 'customHasher', 'custom hasher module'); // Test Certificate Re-Exports console.log('\nāœ… Testing Certificate Re-Exports...'); - const { parseCertificateSimple } = await safeImport( + await testExport( 'src/utils/certificate_parsing/parseSimple.js', + 'parseCertificateSimple', 'parse simple certificate module' ); - const { parseCertificate } = await safeImport( + await testExport( 'src/utils/certificate_parsing/parseNode.js', + 'parseCertificate', 'parse node certificate module' ); - const { initElliptic } = await safeImport( + await testExport( 'src/utils/certificate_parsing/ellipticInit.js', + 'initElliptic', 'elliptic init module' ); - console.log(' - parseCertificateSimple:', typeof parseCertificateSimple, 'āœ…'); - console.log(' - parseCertificate:', typeof parseCertificate, 'āœ…'); - console.log(' - initElliptic:', typeof initElliptic, 'āœ…'); + + // Test Array Utilities + console.log('\nāœ… Testing Array Utilities...'); + await testModuleExports('src/utils/arrays.js', 'arrays module'); + + // Test Bytes Utilities + console.log('\nāœ… Testing Bytes Utilities...'); + await testModuleExports('src/utils/bytes.js', 'bytes module'); + + // Test Date Utilities + console.log('\nāœ… Testing Date Utilities...'); + await testModuleExports('src/utils/date.js', 'date module'); + + // Test Scope Utilities + console.log('\nāœ… Testing Scope Utilities...'); + await testModuleExports('src/utils/scope.js', 'scope module'); + + // Test Contract Utilities + console.log('\nāœ… Testing Contract Utilities...'); + await testDirectoryExports('src/utils/contracts', 'Contract'); // Note: Circuit and Passport tests skipped due to JSON import issues in Node.js ESM console.log( @@ -85,25 +188,41 @@ async function testReExports() { console.log(' - The issue is specific to Node.js ESM JSON imports'); console.log(' - All exports are properly configured and tested in the build process'); - console.log('\nšŸŽ‰ SUCCESS! Clean Re-Exports Working Perfectly!'); - console.log('\nšŸ“Š Benefits of Clean Re-Export Approach:'); - console.log(' āœ… No risk of regressions (uses existing, tested code)'); - console.log(' āœ… Same tree-shaking benefits (via package.json exports)'); - console.log(' āœ… Maximum granularity (individual function imports)'); - console.log(' āœ… Simple, maintainable code'); + // Performance metrics + const endTime = Date.now(); + const duration = endTime - startTime; - console.log('\nšŸ”§ Ready-to-Use Level 3 Imports:'); - console.log(' import { hash } from "@selfxyz/common/utils/hash/sha";'); - console.log(' import { flexiblePoseidon } from "@selfxyz/common/utils/hash/poseidon";'); - console.log( - ' import { parseCertificateSimple } from "@selfxyz/common/utils/certificates/parseSimple";' - ); - console.log( - ' import { generateCircuitInputsDSC } from "@selfxyz/common/utils/circuits/dscInputs";' - ); - console.log( - ' import { generateCommitment } from "@selfxyz/common/utils/passports/commitment";' - ); + console.log('\nšŸ“Š Test Results:'); + console.log(` Total Tests: ${totalTests}`); + console.log(` Passed: ${passedTests} āœ…`); + console.log(` Failed: ${failedTests} āŒ`); + console.log(` Success Rate: ${((passedTests / totalTests) * 100).toFixed(1)}%`); + console.log(` Duration: ${duration}ms`); + + if (failedTests === 0) { + console.log('\nšŸŽ‰ SUCCESS! Clean Re-Exports Working Perfectly!'); + console.log('\nšŸ“Š Benefits of Clean Re-Export Approach:'); + console.log(' āœ… No risk of regressions (uses existing, tested code)'); + console.log(' āœ… Same tree-shaking benefits (via package.json exports)'); + console.log(' āœ… Maximum granularity (individual function imports)'); + console.log(' āœ… Simple, maintainable code'); + + console.log('\nšŸ”§ Ready-to-Use Level 3 Imports:'); + console.log(' import { hash } from "@selfxyz/common/utils/hash/sha";'); + console.log(' import { flexiblePoseidon } from "@selfxyz/common/utils/hash/poseidon";'); + console.log( + ' import { parseCertificateSimple } from "@selfxyz/common/utils/certificates/parseSimple";' + ); + console.log( + ' import { generateCircuitInputsDSC } from "@selfxyz/common/utils/circuits/dscInputs";' + ); + console.log( + ' import { generateCommitment } from "@selfxyz/common/utils/passports/commitment";' + ); + } else { + console.log(`\nāš ļø ${failedTests} test(s) failed. Please check the exports configuration.`); + process.exit(1); + } } catch (error) { console.error('āŒ Error testing clean re-exports:', error.message); process.exit(1); diff --git a/common/scripts/validateExports.js b/common/scripts/validateExports.js new file mode 100644 index 000000000..7729c8c37 --- /dev/null +++ b/common/scripts/validateExports.js @@ -0,0 +1,199 @@ +#!/usr/bin/env node + +/** + * Comprehensive Export Validation - Check for missing exports and validate package.json configuration + */ + +import { existsSync, readdirSync, readFileSync } from 'fs'; +import { dirname, join, resolve } from 'path'; +import { fileURLToPath } from 'url'; + +// Get the directory of the current script +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Define paths +const BUILD_DIR = join(__dirname, '..', 'dist', 'esm'); +const PACKAGE_JSON_PATH = join(__dirname, '..', 'package.json'); + +console.log('šŸ” Comprehensive Export Validation...\n'); + +// Performance tracking +const startTime = Date.now(); +let totalExports = 0; +let validExports = 0; +let missingExports = 0; +let invalidExports = 0; + +// Load package.json exports configuration +function loadPackageExports() { + try { + const packageJson = JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf8')); + return packageJson.exports || {}; + } catch (error) { + console.error('āŒ Failed to load package.json:', error.message); + return {}; + } +} + +// Get all available modules from build directory +function getAvailableModules() { + const modules = new Set(); + + function scanDirectory(dirPath, prefix = '') { + const fullPath = join(BUILD_DIR, dirPath); + if (!existsSync(fullPath)) return; + + const items = readdirSync(fullPath, { withFileTypes: true }); + + for (const item of items) { + const itemPath = prefix ? `${prefix}/${item.name}` : item.name; + + if (item.isDirectory()) { + scanDirectory(`${dirPath}/${item.name}`, itemPath); + } else if (item.name.endsWith('.js')) { + const modulePath = itemPath.replace('.js', ''); + modules.add(modulePath); + } + } + } + + scanDirectory('src'); + return Array.from(modules); +} + +// Validate package.json exports against available modules +function validatePackageExports() { + console.log('āœ… Validating Package.json Exports Configuration...'); + + const packageExports = loadPackageExports(); + const availableModules = getAvailableModules(); + + const exportPaths = Object.keys(packageExports).filter( + (key) => + key.startsWith('./utils/') || key.startsWith('./constants/') || key.startsWith('./types/') + ); + + console.log(` Found ${exportPaths.length} configured exports`); + console.log(` Found ${availableModules.length} available modules`); + + for (const exportPath of exportPaths) { + totalExports++; + const cleanPath = exportPath.replace('./', '').replace('/index', ''); + + // Check if the module exists + const moduleExists = availableModules.some( + (module) => module.includes(cleanPath) || cleanPath.includes(module) + ); + + if (moduleExists) { + console.log(` āœ… ${exportPath}`); + validExports++; + } else { + console.log(` āŒ ${exportPath} (module not found)`); + missingExports++; + } + } +} + +// Check for modules that should be exported but aren't +function findMissingExports() { + console.log('\nāœ… Checking for Missing Exports...'); + + const packageExports = loadPackageExports(); + const availableModules = getAvailableModules(); + + const exportPaths = Object.keys(packageExports).filter( + (key) => + key.startsWith('./utils/') || key.startsWith('./constants/') || key.startsWith('./types/') + ); + + const configuredModules = new Set( + exportPaths.map((path) => path.replace('./', '').replace('/index', '')) + ); + + const missingModules = availableModules.filter((module) => { + // Skip internal modules that shouldn't be exported + if (module.includes('internal') || module.includes('private')) { + return false; + } + + // Check if this module should be exported + const shouldBeExported = + module.startsWith('src/utils/') || + module.startsWith('src/constants/') || + module.startsWith('src/types/'); + + if (!shouldBeExported) return false; + + // Check if it's already configured + const cleanModule = module.replace('src/', ''); + return !configuredModules.has(cleanModule); + }); + + if (missingModules.length > 0) { + console.log(` Found ${missingModules.length} modules that could be exported:`); + missingModules.forEach((module) => { + console.log(` šŸ“ ${module.replace('src/', './')}`); + }); + } else { + console.log(' āœ… All relevant modules are properly exported'); + } +} + +// Generate export suggestions +function generateExportSuggestions() { + console.log('\nšŸ’” Export Configuration Suggestions:'); + console.log(' • Consider adding granular exports for frequently used utilities'); + console.log(' • Ensure all public APIs are properly exported'); + console.log(' • Use consistent naming patterns for export paths'); + console.log(' • Consider adding JSDoc comments for better documentation'); +} + +async function runValidation() { + try { + // Verify build directory exists + if (!existsSync(BUILD_DIR)) { + console.error(`āŒ Build directory not found: ${BUILD_DIR}`); + console.error( + ' Please run the build process first (e.g., "npm run build" or "yarn build")' + ); + process.exit(1); + } + + console.log(`āœ… Build directory verified: ${BUILD_DIR}\n`); + + // Run validations + validatePackageExports(); + findMissingExports(); + generateExportSuggestions(); + + // Performance metrics + const endTime = Date.now(); + const duration = endTime - startTime; + + console.log('\nšŸ“Š Validation Results:'); + console.log(` Total Exports Checked: ${totalExports}`); + console.log(` Valid Exports: ${validExports} āœ…`); + console.log(` Missing Exports: ${missingExports} āŒ`); + console.log(` Invalid Exports: ${invalidExports} āš ļø`); + console.log( + ` Success Rate: ${totalExports > 0 ? ((validExports / totalExports) * 100).toFixed(1) : 0}%` + ); + console.log(` Duration: ${duration}ms`); + + if (missingExports === 0 && invalidExports === 0) { + console.log('\nšŸŽ‰ SUCCESS! All exports are properly configured!'); + } else { + console.log( + `\nāš ļø Found ${missingExports + invalidExports} issue(s) with exports configuration.` + ); + process.exit(1); + } + } catch (error) { + console.error('āŒ Error during validation:', error.message); + process.exit(1); + } +} + +runValidation(); diff --git a/common/src/constants/constants.ts b/common/src/constants/constants.ts index 82838068a..f1bdfdd35 100644 --- a/common/src/constants/constants.ts +++ b/common/src/constants/constants.ts @@ -1,46 +1,113 @@ -export const TREE_TRACKER_URL = 'https://tree.self.xyz'; -export const CSCA_TREE_DEPTH = 12; -export const DSC_TREE_DEPTH = 21; -export const COMMITMENT_TREE_DEPTH = 33; -export const DEFAULT_USER_ID_TYPE = 'uuid'; - -export const REDIRECT_URL = 'https://redirect.self.xyz'; -export const WS_RPC_URL_VC_AND_DISCLOSE = 'ws://disclose.proving.self.xyz:8888/'; -export const WS_DB_RELAYER = 'wss://websocket.self.xyz'; -export const WS_DB_RELAYER_STAGING = 'wss://websocket.staging.self.xyz'; +export type Country3LetterCode = keyof typeof countryCodes; +export type document_type = 'passport' | 'id_card'; +export type hashAlgosTypes = 'sha512' | 'sha384' | 'sha256' | 'sha224' | 'sha1'; export const API_URL = 'https://api.self.xyz'; -export const TREE_URL = 'https://tree.self.xyz'; -export const TREE_URL_STAGING = 'https://tree.staging.self.xyz'; export const API_URL_STAGING = 'https://api.staging.self.xyz'; + +export const CHAIN_NAME = 'celo'; + +// possible values because of sha1 constaints: 192,320,384, 448, 576, 640 +export const CIRCUIT_CONSTANTS = { + REGISTER_NULLIFIER_INDEX: 0, + REGISTER_COMMITMENT_INDEX: 1, + REGISTER_MERKLE_ROOT_INDEX: 2, + + DSC_TREE_LEAF_INDEX: 0, + DSC_CSCA_ROOT_INDEX: 1, + + VC_AND_DISCLOSE_REVEALED_DATA_PACKED_INDEX: 0, + VC_AND_DISCLOSE_FORBIDDEN_COUNTRIES_LIST_PACKED_INDEX: 3, + VC_AND_DISCLOSE_NULLIFIER_INDEX: 7, + VC_AND_DISCLOSE_ATTESTATION_ID_INDEX: 8, + VC_AND_DISCLOSE_MERKLE_ROOT_INDEX: 9, + VC_AND_DISCLOSE_CURRENT_DATE_INDEX: 10, + VC_AND_DISCLOSE_PASSPORT_NO_SMT_ROOT_INDEX: 16, + VC_AND_DISCLOSE_NAME_DOB_SMT_ROOT_INDEX: 17, + VC_AND_DISCLOSE_NAME_YOB_SMT_ROOT_INDEX: 18, + VC_AND_DISCLOSE_SCOPE_INDEX: 19, + VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX: 20, +}; + +export const CIRCUIT_TYPES = ['dsc', 'register', 'vc_and_disclose']; + +export const COMMITMENT_TREE_DEPTH = 33; + +export const CSCA_TREE_DEPTH = 12; + export const CSCA_TREE_URL = 'https://tree.self.xyz/csca'; -export const DSC_TREE_URL = 'https://tree.self.xyz/dsc'; -export const CSCA_TREE_URL_STAGING = 'https://tree.staging.self.xyz/csca'; -export const DSC_TREE_URL_STAGING = 'https://tree.staging.self.xyz/dsc'; -export const IDENTITY_TREE_URL = 'https://tree.self.xyz/identity'; -export const IDENTITY_TREE_URL_STAGING = 'https://tree.staging.self.xyz/identity'; export const CSCA_TREE_URL_ID_CARD = 'https://tree.self.xyz/csca-id'; -export const DSC_TREE_URL_ID_CARD = 'https://tree.self.xyz/dsc-id'; + +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 DSC_TREE_URL_STAGING_ID_CARD = 'https://tree.staging.self.xyz/dsc-id'; -export const IDENTITY_TREE_URL_ID_CARD = 'https://tree.self.xyz/identity-id'; -export const IDENTITY_TREE_URL_STAGING_ID_CARD = 'https://tree.staging.self.xyz/identity-id'; -export const PASSPORT_ATTESTATION_ID = '1'; //"8518753152044246090169372947057357973469996808638122125210848696986717482788" -export const ID_CARD_ATTESTATION_ID = '2'; -export const CHAIN_NAME = 'celo'; -export const RPC_URL = 'https://forno.celo.org'; -export const PCR0_MANAGER_ADDRESS = '0xE36d4EE5Fd3916e703A46C21Bb3837dB7680C8B8'; - -// we make it global here because passing it to generateCircuitInputsRegister caused trouble -export const DEVELOPMENT_MODE = true; export const DEFAULT_MAJORITY = '18'; -export const hashAlgos = ['sha512', 'sha384', 'sha256', 'sha224', 'sha1']; -export type hashAlgosTypes = 'sha512' | 'sha384' | 'sha256' | 'sha224' | 'sha1'; -export const saltLengths = [64, 48, 32]; +export const DEFAULT_RPC_URL = 'https://mainnet.optimism.io'; -export type document_type = 'passport' | 'id_card'; +export const DEFAULT_USER_ID_TYPE = 'uuid'; + +export const DEVELOPMENT_MODE = true; + +export const DSC_TREE_DEPTH = 21; + +export const DSC_TREE_URL = 'https://tree.self.xyz/dsc'; + +export const DSC_TREE_URL_ID_CARD = 'https://tree.self.xyz/dsc-id'; + +export const DSC_TREE_URL_STAGING = 'https://tree.staging.self.xyz/dsc'; + +export const DSC_TREE_URL_STAGING_ID_CARD = 'https://tree.staging.self.xyz/dsc-id'; + +export enum DscVerifierId { + dsc_sha1_ecdsa_brainpoolP256r1 = 0, + dsc_sha1_rsa_65537_4096 = 1, + dsc_sha256_ecdsa_brainpoolP256r1 = 2, + dsc_sha256_ecdsa_brainpoolP384r1 = 3, + dsc_sha256_ecdsa_secp256r1 = 4, + dsc_sha256_ecdsa_secp384r1 = 5, + dsc_sha256_ecdsa_secp521r1 = 6, + dsc_sha256_rsa_65537_4096 = 7, + dsc_sha256_rsapss_3_32_3072 = 8, + dsc_sha256_rsapss_65537_32_3072 = 9, + dsc_sha256_rsapss_65537_32_4096 = 10, + dsc_sha384_ecdsa_brainpoolP384r1 = 11, + dsc_sha384_ecdsa_brainpoolP512r1 = 12, + dsc_sha384_ecdsa_secp384r1 = 13, + dsc_sha512_ecdsa_brainpoolP512r1 = 14, + dsc_sha512_ecdsa_secp521r1 = 15, + dsc_sha512_rsa_65537_4096 = 16, + dsc_sha512_rsapss_65537_64_4096 = 17, + dsc_sha256_rsapss_3_32_4096 = 18, + dsc_sha1_ecdsa_secp256r1 = 19, +} + +export const ECDSA_K_LENGTH_FACTOR = 2; + +export const IDENTITY_TREE_URL = 'https://tree.self.xyz/identity'; + +//"8518753152044246090169372947057357973469996808638122125210848696986717482788" +export const IDENTITY_TREE_URL_ID_CARD = 'https://tree.self.xyz/identity-id'; + +export const IDENTITY_TREE_URL_STAGING = 'https://tree.staging.self.xyz/identity'; + +export const IDENTITY_TREE_URL_STAGING_ID_CARD = 'https://tree.staging.self.xyz/identity-id'; + +export const ID_CARD_ATTESTATION_ID = '2'; + +export const MAX_BYTES_IN_FIELD = 31; + +export const MAX_CERT_BYTES: Partial> = { + rsa_sha256_65537_4096: 512, + rsa_sha1_65537_4096: 640, + rsapss_sha256_65537_2048: 640, + rsapss_sha256_65537_3072: 640, + rsapss_sha256_65537_4096: 768, + rsapss_sha256_3_3072: 768, + rsapss_sha256_3_4096: 768, + rsapss_sha384_65537_3072: 768, +}; /** * Maximum number of countries in the forbidden countries list. @@ -48,13 +115,10 @@ export type document_type = 'passport' | 'id_card'; * IMPORTANT: This value must match in both backend and frontend SDK. * Any mismatch will result in an INVALID_FORBIDDEN_COUNTRIES error. */ +export const MAX_DATAHASHES_LEN = 320; + export const MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH = 40; -// Note: Circuit lists are now managed through RegisterVerifierId and DscVerifierId enums below -// instead of separate arrays for better type safety and maintainability - -export const OFAC_TREE_LEVELS = 64; - export const MAX_PADDED_ECONTENT_LEN: Partial> = { sha1: 384, sha224: 512, @@ -71,29 +135,22 @@ export const MAX_PADDED_SIGNED_ATTR_LEN: Record<(typeof hashAlgos)[number], numb sha512: 256, }; -export const MAX_CERT_BYTES: Partial> = { - rsa_sha256_65537_4096: 512, - rsa_sha1_65537_4096: 640, - rsapss_sha256_65537_2048: 640, - rsapss_sha256_65537_3072: 640, - rsapss_sha256_65537_4096: 768, - rsapss_sha256_3_3072: 768, - rsapss_sha256_3_4096: 768, - rsapss_sha384_65537_3072: 768, -}; +// Note: Circuit lists are now managed through RegisterVerifierId and DscVerifierId enums below +// instead of separate arrays for better type safety and maintainability +export const MAX_PUBKEY_DSC_BYTES = 525; -export const ECDSA_K_LENGTH_FACTOR = 2; -// possible values because of sha1 constaints: 192,320,384, 448, 576, 640 +export const OFAC_TREE_LEVELS = 64; -export const CIRCUIT_TYPES = ['dsc', 'register', 'vc_and_disclose']; -export const circuitNameFromMode = { - prove: 'prove', - prove_onchain: 'prove', - prove_offchain: 'prove', - register: 'prove', - vc_and_disclose: 'vc_and_disclose', - dsc: 'dsc', -}; +// we make it global here because passing it to generateCircuitInputsRegister caused trouble +export const PASSPORT_ATTESTATION_ID = '1'; + +export const PCR0_MANAGER_ADDRESS = '0xE36d4EE5Fd3916e703A46C21Bb3837dB7680C8B8'; + +export const REDIRECT_URL = 'https://redirect.self.xyz'; + +export const REGISTER_CONTRACT_ADDRESS = '0x3F346FFdC5d583e4126AF01A02Ac5b9CdB3f1909'; + +export const RPC_URL = 'https://forno.celo.org'; export enum RegisterVerifierId { register_sha256_sha256_sha256_rsa_65537_4096 = 0, @@ -150,28 +207,7 @@ export enum RegisterVerifierId { register_id_sha512_sha512_sha512_rsapss_65537_64_2048 = 51, } -export enum DscVerifierId { - dsc_sha1_ecdsa_brainpoolP256r1 = 0, - dsc_sha1_rsa_65537_4096 = 1, - dsc_sha256_ecdsa_brainpoolP256r1 = 2, - dsc_sha256_ecdsa_brainpoolP384r1 = 3, - dsc_sha256_ecdsa_secp256r1 = 4, - dsc_sha256_ecdsa_secp384r1 = 5, - dsc_sha256_ecdsa_secp521r1 = 6, - dsc_sha256_rsa_65537_4096 = 7, - dsc_sha256_rsapss_3_32_3072 = 8, - dsc_sha256_rsapss_65537_32_3072 = 9, - dsc_sha256_rsapss_65537_32_4096 = 10, - dsc_sha384_ecdsa_brainpoolP384r1 = 11, - dsc_sha384_ecdsa_brainpoolP512r1 = 12, - dsc_sha384_ecdsa_secp384r1 = 13, - dsc_sha512_ecdsa_brainpoolP512r1 = 14, - dsc_sha512_ecdsa_secp521r1 = 15, - dsc_sha512_rsa_65537_4096 = 16, - dsc_sha512_rsapss_65537_64_4096 = 17, - dsc_sha256_rsapss_3_32_4096 = 18, - dsc_sha1_ecdsa_secp256r1 = 19, -} +export const SBT_CONTRACT_ADDRESS = '0x601Fd54FD11C5E77DE84d877e55B829aff20f0A6'; export enum SignatureAlgorithmIndex { rsa_sha256_65537_2048 = 1, @@ -208,6 +244,17 @@ export enum SignatureAlgorithmIndex { ecdsa_sha512_secp521r1_521 = 41, } +export const TREE_TRACKER_URL = 'https://tree.self.xyz'; + +export const TREE_URL = 'https://tree.self.xyz'; +export const TREE_URL_STAGING = 'https://tree.staging.self.xyz'; + +export const WS_DB_RELAYER = 'wss://websocket.self.xyz'; + +export const WS_DB_RELAYER_STAGING = 'wss://websocket.staging.self.xyz'; + +export const WS_RPC_URL_VC_AND_DISCLOSE = 'ws://disclose.proving.self.xyz:8888/'; + export const attributeToPosition = { issuing_state: [2, 4], name: [5, 43], @@ -231,64 +278,35 @@ export const attributeToPosition_ID = { ofac: [92, 92], }; +export const circuitNameFromMode = { + prove: 'prove', + prove_onchain: 'prove', + prove_offchain: 'prove', + register: 'prove', + vc_and_disclose: 'vc_and_disclose', + dsc: 'dsc', +}; export const circuitToSelectorMode = { register: [0, 0], prove_onchain: [1, 0], prove_offchain: [1, 1], }; -export const revealedDataTypes = { - issuing_state: 0, - name: 1, - passport_number: 2, - nationality: 3, - date_of_birth: 4, - gender: 5, - expiry_date: 6, - older_than: 7, - passport_no_ofac: 8, - name_and_dob_ofac: 9, - name_and_yob_ofac: 10, -}; - -export const CIRCUIT_CONSTANTS = { - REGISTER_NULLIFIER_INDEX: 0, - REGISTER_COMMITMENT_INDEX: 1, - REGISTER_MERKLE_ROOT_INDEX: 2, - - DSC_TREE_LEAF_INDEX: 0, - DSC_CSCA_ROOT_INDEX: 1, - - VC_AND_DISCLOSE_REVEALED_DATA_PACKED_INDEX: 0, - VC_AND_DISCLOSE_FORBIDDEN_COUNTRIES_LIST_PACKED_INDEX: 3, - VC_AND_DISCLOSE_NULLIFIER_INDEX: 7, - VC_AND_DISCLOSE_ATTESTATION_ID_INDEX: 8, - VC_AND_DISCLOSE_MERKLE_ROOT_INDEX: 9, - VC_AND_DISCLOSE_CURRENT_DATE_INDEX: 10, - VC_AND_DISCLOSE_PASSPORT_NO_SMT_ROOT_INDEX: 16, - VC_AND_DISCLOSE_NAME_DOB_SMT_ROOT_INDEX: 17, - VC_AND_DISCLOSE_NAME_YOB_SMT_ROOT_INDEX: 18, - VC_AND_DISCLOSE_SCOPE_INDEX: 19, - VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX: 20, -}; - -export const MAX_BYTES_IN_FIELD = 31; -export const MAX_PUBKEY_DSC_BYTES = 525; - -export const MAX_DATAHASHES_LEN = 320; // max formatted and concatenated datagroup hashes length in bytes -export const n_dsc = 120; -export const n_dsc_3072 = 120; -export const n_dsc_4096 = 120; -export const k_dsc = 35; -export const k_dsc_3072 = 35; //48; -export const k_dsc_4096 = 35; -export const n_csca = 120; -export const k_csca = 35; -export const n_dsc_ecdsa = 64; -export const k_dsc_ecdsa = 4; -export const max_dsc_bytes = 1792; -export const max_csca_bytes = 1792; +export const contribute_publicKey = `-----BEGIN RSA PUBLIC KEY----- +MIICCgKCAgEAv/hm7FZZ2KBmaeDHmLoRwuWmCcNKT561RqbsW8ZuYSyPWJUldE9U +Cf0lW3K1H5lsSDkl0Cq84cooL9f6X59Mffb/N24ZKTdL0xdcPwjk4LbcrVm8qubL +0a/4uCNoZZ1my4nxbpLxYtbr8CNmUGvBOVKf8IcjsY6VghIZrO63G6BN/G44su1Z +WcHpboGt9SDQK4enCyKxnCD+PbDYlewSA0n3GRajFfZex1bj1EvrS2hTLv8oNH5e +9H+3TUke0uO6Ttl0bZepoMmPlpAXhJByISqC6SLth4WFIH+G1I/xt9AEM7hOfLMl +KQv/3wlLEgEueRryKAHB2tqkaDKVJyw+tOyWj2iWA+nVgQKAxO4hOw01ljyVbcx6 +KboXwnamlZPFIx4tjEaZ+ClXCFqvXhE9LDFK11QsYzJZl0aRVfTNqcurhEt7SK0f +qzOBhID0Nxk4k9sW1uT6ocW1xp1SB2WotORssOKIAOLJM8IbPl6n/DkYNcfvyXI7 +4BlUrf6M2DgZMYATabIy94AvopHJOyiRfh4NpQPDntWnShiI1em2MmtXiWFCdVFV +6/QfJTKVixJpVfDh386ALXc97EPWDMWIalUwYoV/eRSMnuV8nZ0+Ctp3Qrtk/JYd ++FWhKbtlPeRjmGVr6mVlvDJ7KqtY5/RqqwfWeXhXezGhQqQ/OoQQCRkCAwEAAQ== +-----END RSA PUBLIC KEY-----`; +// not using a library for this as the entry countries use can be differnt than the ISO 3166-1 alpha-3 standard export const countryCodes = { AFG: 'Afghanistan', ALA: 'Aland Islands', @@ -541,8 +559,6 @@ export const countryCodes = { ZMB: 'Zambia', ZWE: 'Zimbabwe', }; -// not using a library for this as the entry countries use can be differnt than the ISO 3166-1 alpha-3 standard -export type Country3LetterCode = keyof typeof countryCodes; export function getCountryCode(countryName: string): string | string { const entries = Object.entries(countryCodes); @@ -550,20 +566,46 @@ export function getCountryCode(countryName: string): string | string { return found ? found[0] : 'undefined'; } -export const contribute_publicKey = `-----BEGIN RSA PUBLIC KEY----- -MIICCgKCAgEAv/hm7FZZ2KBmaeDHmLoRwuWmCcNKT561RqbsW8ZuYSyPWJUldE9U -Cf0lW3K1H5lsSDkl0Cq84cooL9f6X59Mffb/N24ZKTdL0xdcPwjk4LbcrVm8qubL -0a/4uCNoZZ1my4nxbpLxYtbr8CNmUGvBOVKf8IcjsY6VghIZrO63G6BN/G44su1Z -WcHpboGt9SDQK4enCyKxnCD+PbDYlewSA0n3GRajFfZex1bj1EvrS2hTLv8oNH5e -9H+3TUke0uO6Ttl0bZepoMmPlpAXhJByISqC6SLth4WFIH+G1I/xt9AEM7hOfLMl -KQv/3wlLEgEueRryKAHB2tqkaDKVJyw+tOyWj2iWA+nVgQKAxO4hOw01ljyVbcx6 -KboXwnamlZPFIx4tjEaZ+ClXCFqvXhE9LDFK11QsYzJZl0aRVfTNqcurhEt7SK0f -qzOBhID0Nxk4k9sW1uT6ocW1xp1SB2WotORssOKIAOLJM8IbPl6n/DkYNcfvyXI7 -4BlUrf6M2DgZMYATabIy94AvopHJOyiRfh4NpQPDntWnShiI1em2MmtXiWFCdVFV -6/QfJTKVixJpVfDh386ALXc97EPWDMWIalUwYoV/eRSMnuV8nZ0+Ctp3Qrtk/JYd -+FWhKbtlPeRjmGVr6mVlvDJ7KqtY5/RqqwfWeXhXezGhQqQ/OoQQCRkCAwEAAQ== ------END RSA PUBLIC KEY-----`; +export const hashAlgos = ['sha512', 'sha384', 'sha256', 'sha224', 'sha1']; -export const DEFAULT_RPC_URL = 'https://mainnet.optimism.io'; -export const REGISTER_CONTRACT_ADDRESS = '0x3F346FFdC5d583e4126AF01A02Ac5b9CdB3f1909'; -export const SBT_CONTRACT_ADDRESS = '0x601Fd54FD11C5E77DE84d877e55B829aff20f0A6'; +export const k_csca = 35; + +export const k_dsc = 35; + +//48; +export const k_dsc_3072 = 35; + +export const k_dsc_4096 = 35; + +export const k_dsc_ecdsa = 4; + +export const max_csca_bytes = 1792; + +export const max_dsc_bytes = 1792; + +export const n_csca = 120; + +export const n_dsc = 120; + +export const n_dsc_3072 = 120; + +export const n_dsc_4096 = 120; + +export const n_dsc_ecdsa = 64; + +// max formatted and concatenated datagroup hashes length in bytes +export const revealedDataTypes = { + issuing_state: 0, + name: 1, + passport_number: 2, + nationality: 3, + date_of_birth: 4, + gender: 5, + expiry_date: 6, + older_than: 7, + passport_no_ofac: 8, + name_and_dob_ofac: 9, + name_and_yob_ofac: 10, +}; + +export const saltLengths = [64, 48, 32]; diff --git a/common/src/constants/countries.ts b/common/src/constants/countries.ts index 4fb1af424..ef085ccb3 100644 --- a/common/src/constants/countries.ts +++ b/common/src/constants/countries.ts @@ -1,3 +1,5 @@ +export type Country3LetterCode = (typeof countries)[keyof typeof countries]; + export const commonNames = { AFG: 'Afghanistan', ALA: 'Aland Islands', @@ -512,5 +514,3 @@ export const countries = { INTERPOL: 'XPO', SMOM: 'XOM', } as const; - -export type Country3LetterCode = (typeof countries)[keyof typeof countries]; diff --git a/common/src/constants/index.ts b/common/src/constants/index.ts index 02479e83a..f6015259d 100644 --- a/common/src/constants/index.ts +++ b/common/src/constants/index.ts @@ -1,34 +1,38 @@ +// Re-export commonly used constants from constants.ts for optimal tree shaking +export type { Country3LetterCode } from './constants.js'; export { - TREE_URL, - TREE_URL_STAGING, API_URL, API_URL_STAGING, - WS_DB_RELAYER, - WS_DB_RELAYER_STAGING, + CIRCUIT_CONSTANTS, + CSCA_TREE_URL, + CSCA_TREE_URL_ID_CARD, + CSCA_TREE_URL_STAGING, + CSCA_TREE_URL_STAGING_ID_CARD, + DEFAULT_MAJORITY, + DSC_TREE_URL, + DSC_TREE_URL_ID_CARD, + DSC_TREE_URL_STAGING, + DSC_TREE_URL_STAGING_ID_CARD, + DscVerifierId, + IDENTITY_TREE_URL, + IDENTITY_TREE_URL_ID_CARD, + IDENTITY_TREE_URL_STAGING, + IDENTITY_TREE_URL_STAGING_ID_CARD, + ID_CARD_ATTESTATION_ID, + PASSPORT_ATTESTATION_ID, PCR0_MANAGER_ADDRESS, RPC_URL, - PASSPORT_ATTESTATION_ID, - ID_CARD_ATTESTATION_ID, - DEFAULT_MAJORITY, + REDIRECT_URL, + RegisterVerifierId, + SignatureAlgorithmIndex, + TREE_URL, + TREE_URL_STAGING, + WS_DB_RELAYER, + WS_DB_RELAYER_STAGING, attributeToPosition, attributeToPosition_ID, countryCodes, } from './constants.js'; +// Re-export from other constant files export { commonNames, countries } from './countries.js'; -export type { Country3LetterCode } from './constants.js'; - -export { - CSCA_TREE_URL, - DSC_TREE_URL, - CSCA_TREE_URL_STAGING, - DSC_TREE_URL_STAGING, - IDENTITY_TREE_URL, - IDENTITY_TREE_URL_STAGING, - CSCA_TREE_URL_ID_CARD, - DSC_TREE_URL_ID_CARD, - CSCA_TREE_URL_STAGING_ID_CARD, - DSC_TREE_URL_STAGING_ID_CARD, - IDENTITY_TREE_URL_ID_CARD, - IDENTITY_TREE_URL_STAGING_ID_CARD, -} from './constants.js'; diff --git a/common/src/constants/sampleDataHashes.ts b/common/src/constants/sampleDataHashes.ts index 8b9afcccb..cd015c837 100644 --- a/common/src/constants/sampleDataHashes.ts +++ b/common/src/constants/sampleDataHashes.ts @@ -1,18 +1,3 @@ -export const sampleDataHashes_small = [ - [ - 2, - [ - -66, 82, -76, -21, -34, 33, 79, 50, -104, -120, -114, 35, 116, -32, 6, -14, -100, -115, -128, - -8, - ], - ], - [3, [0, -62, 104, 108, -19, -10, 97, -26, 116, -58, 69, 110, 26, 87, 17, 89, 110, -57, 108, -6]], - [ - 14, - [76, 123, -40, 13, 51, -29, 72, -11, 59, -63, -18, -90, 103, 49, 23, -92, -85, -68, -62, -59], - ], -] as [number, number[]][]; - export const sampleDataHashes_large = [ [ 2, @@ -57,3 +42,18 @@ export const sampleDataHashes_large = [ ], ], ] as [number, number[]][]; + +export const sampleDataHashes_small = [ + [ + 2, + [ + -66, 82, -76, -21, -34, 33, 79, 50, -104, -120, -114, 35, 116, -32, 6, -14, -100, -115, -128, + -8, + ], + ], + [3, [0, -62, 104, 108, -19, -10, 97, -26, 116, -58, 69, 110, 26, 87, 17, 89, 110, -57, 108, -6]], + [ + 14, + [76, 123, -40, 13, 51, -29, 72, -11, 59, -63, -18, -90, 103, 49, 23, -92, -85, -68, -62, -59], + ], +] as [number, number[]][]; diff --git a/common/src/constants/vkey.ts b/common/src/constants/vkey.ts index 3c8005de2..66307fe2f 100644 --- a/common/src/constants/vkey.ts +++ b/common/src/constants/vkey.ts @@ -1,7 +1,7 @@ -export const vkey_vc_and_disclose = { +export const vkey_dsc_rsa_65537_sha1 = { protocol: 'groth16', curve: 'bn128', - nPublic: 20, + nPublic: 2, vk_alpha_1: [ '20491192805390485299153009773594534940189261866228447918068658471970481763042', '9383485363053290200918347156157836566562967994039712273449902621266178545958', @@ -31,12 +31,12 @@ export const vkey_vc_and_disclose = { ], vk_delta_2: [ [ - '2285641925224838978222516003838691301522837942576149813249618262683607431978', - '8933979452959246793652418502124635637608136544198967823461038471987929468463', + '13609834548161300582477853991526320293433640330539801500903753457627888495381', + '8441806186745188372537460321981116999801031201583499966958826604947980830000', ], [ - '16099512190238976912962671487125443206965718241797607052101309249042700281031', - '13866973425260784693055729377971041037525083882024304009793262309055835677530', + '8448384202753302962223109595229113261664582566011624155734783894938928271855', + '14114983069796580083449425790812236094683577678022815810458824633453412210627', ], ['1', '0'], ], @@ -72,117 +72,27 @@ export const vkey_vc_and_disclose = { ], IC: [ [ - '17675992131793847472607582103290528032110944356332065253938771650575512637150', - '3334529637547487349647542974294469269353530698716058653313633946852810587219', + '8080423910694661461576427977746141048179131345871564682235127365538915251175', + '15077260046277123685110202133518064301144478802752095822556731494534324607918', '1', ], [ - '4073040853156698088579125842860781690905988024237850240402200045640356184109', - '4679018198189152660097843382950080652306538412548068412033586660232563193013', + '16011015387972163546219334947336021918538394305813640145299036166236111586389', + '19422172264639146231714658451804174650252945098828793769061895536286758622279', '1', ], [ - '15776533017793451196514777082124337882856102873453705308829629571923075424417', - '14232490066350175683786572931273695352468015232518411187366417165893912115230', - '1', - ], - [ - '1180945442359952286721463819274447148908421750703875389048309699338667338411', - '6539452064449720183898998074274190026127754682197287334130838477279315254881', - '1', - ], - [ - '13806944210187274185057734594353372528156342424366258241133564778474054935868', - '7800429229653987231645474546639647711164156063512193001130226490464812892063', - '1', - ], - [ - '5522822939351592924817275047393709421174176326835572758552827440001401610167', - '11245123080087310106980015850199801755152167910673823675982721730905258212233', - '1', - ], - [ - '5627481952482064829757269526369949776646066892934683965609154755505296555965', - '4545484539969611530913200236731153733781933150464540998302850238315475023119', - '1', - ], - [ - '20423400711755442910136460433293470952040919967735945317336472158180565467951', - '6378416884992747212393622109218662385874688399353342355554228954581250524736', - '1', - ], - [ - '4373305143599295486105733121446353133863889257489542032576171376300323541304', - '4026684213449689055353486192784741961832172326825055022163307876785200324637', - '1', - ], - [ - '5068833407180337147676969453261920994101725947652623057924700035380226397907', - '11087944526730366835444251823404529324958715143332979454630299083515861157072', - '1', - ], - [ - '21831082589187376499865612459731026493616836999924912561891098220429445525521', - '10842131628581855605843884617846867779319917434679079988533026500378178602950', - '1', - ], - [ - '14267596758527912595273989225764996478640991994242422111486793250630191081468', - '18694764448853871920117677375229746676709722363749986739355723996303104296068', - '1', - ], - [ - '10612001726856359227588948695393307655456532716872467807420271676133645773510', - '1540081878138518609133189078681275557428892906987071405333678709982239141734', - '1', - ], - [ - '7645334938708591281146159611078021141015093576924464549682793344782173540643', - '5041018151127409344023520612676035165867301524846720998477901879854547090576', - '1', - ], - [ - '8883975772061177470672187114470132395286660826025700056750575280427708604951', - '8320685858262483078856712401649683261512346806365950058502541940795537677566', - '1', - ], - [ - '19770351503660034123641636433521671083703252249281883512457304565751018243912', - '1942705439280745753504371271759370270294305194237531295574972095513458387941', - '1', - ], - [ - '15226311176844690062341353072910557091317547496535044902570312644662561307949', - '19296708345054645579869153897992480989971014206920627404347102350029660305578', - '1', - ], - [ - '5436668919010108586842015267897845254611520180011711994231132539697114737868', - '21034924479027562029885591632326051974733967422882023235118894616793689864649', - '1', - ], - [ - '17617503314542022213516716240700336131305032342072938364934850750624139958532', - '7535025988359437238238236154472398858799932717035093350843709570356278900481', - '1', - ], - [ - '15079990665367846194463669410480448234210628628217480894032215566550223721508', - '14422402677444824910876930195543885009950058186617851677855759748496927334854', - '1', - ], - [ - '15949464882252583714786988301769970813994683305898379568444041314948594948891', - '10982667165993413891579207656117800635412428118849500079522502265949839258411', + '2543445962105990625291559091474595879940066015891597850114251320085420659626', + '1917249161113849432608012443620967377861258824599427135619631874160142518773', '1', ], ], }; -export const vkey_prove_rsa_65537_sha256 = { +export const vkey_dsc_rsa_65537_sha256 = { protocol: 'groth16', curve: 'bn128', - nPublic: 51, + nPublic: 2, vk_alpha_1: [ '20491192805390485299153009773594534940189261866228447918068658471970481763042', '9383485363053290200918347156157836566562967994039712273449902621266178545958', @@ -212,12 +122,12 @@ export const vkey_prove_rsa_65537_sha256 = { ], vk_delta_2: [ [ - '14621056654033030159111384006506361438733969388519830162058188044378001616013', - '13712669930807027066432455963920004753804575153306478154082225172540894791697', + '8403974525672515951605465754909425916978281298593354504437469807907113049853', + '12233134836151850512596961158180983853133742935319340320561432564845137384819', ], [ - '21664748003830939002021923557411259243652793372223608877730406967860908232579', - '8648865297679023017392575432725553399779724355967143745086353612497178809510', + '3209155548902127778431906050698597513646227271655778722256683596743569531044', + '16830777068052670490128170305087202969267881418665601837992321846223880096264', ], ['1', '0'], ], @@ -253,263 +163,109 @@ export const vkey_prove_rsa_65537_sha256 = { ], IC: [ [ - '18837847330460101767470685001162782791541723452908436811770248703422385774364', - '13924593305026026107933010319328520261235500792223955967488219194416191858218', + '16231288969314859968324689058413762096922845561563844884666690734569347763082', + '5910239835236935696830364945179549816839705839711095023727334010208312533092', '1', ], [ - '18499035744135408535006498619187878301564976457052297587006457806425510673636', - '7226548414236873699673490761763172101403491901987747605307552092067988817369', + '6260576084748320398294355533743833076081106851106584793995576172400792840042', + '21319331746978162427565186170902229291674424766799571029877219503610111212711', '1', ], [ - '15076998223900447147424484817648709178376766854878255045911605115135846913714', - '1588555885165810180157418868205800830691130731692806042865483039743264904188', + '16479555690731841331208639285970255252465816317199429107438320860232249030762', + '3783712303962417058251692820237130203111118349896777989119917429903130718416', + '1', + ], + ], +}; + +export const vkey_dsc_rsapss_65537_sha256 = { + protocol: 'groth16', + curve: 'bn128', + nPublic: 2, + vk_alpha_1: [ + '20491192805390485299153009773594534940189261866228447918068658471970481763042', + '9383485363053290200918347156157836566562967994039712273449902621266178545958', + '1', + ], + vk_beta_2: [ + [ + '6375614351688725206403948262868962793625744043794305715222011528459656738731', + '4252822878758300859123897981450591353533073413197771768651442665752259397132', + ], + [ + '10505242626370262277552901082094356697409835680220590971873171140371331206856', + '21847035105528745403288232691147584728191162732299865338377159692350059136679', + ], + ['1', '0'], + ], + vk_gamma_2: [ + [ + '10857046999023057135944570762232829481370756359578518086990519993285655852781', + '11559732032986387107991004021392285783925812861821192530917403151452391805634', + ], + [ + '8495653923123431417604973247489272438418190587263600148770280649306958101930', + '4082367875863433681332203403145435568316851327593401208105741076214120093531', + ], + ['1', '0'], + ], + vk_delta_2: [ + [ + '6017862818859039949402494358517294962820240623943684783826304039772640083873', + '11178871189289476718066796914086694937261254534191355355208715473729459679073', + ], + [ + '18289536515878615632378439677874060078266723798016506833131127246101859022607', + '8005285279181761514246985809032780535330313241768111893953981130952718939039', + ], + ['1', '0'], + ], + vk_alphabeta_12: [ + [ + [ + '2029413683389138792403550203267699914886160938906632433982220835551125967885', + '21072700047562757817161031222997517981543347628379360635925549008442030252106', + ], + [ + '5940354580057074848093997050200682056184807770593307860589430076672439820312', + '12156638873931618554171829126792193045421052652279363021382169897324752428276', + ], + [ + '7898200236362823042373859371574133993780991612861777490112507062703164551277', + '7074218545237549455313236346927434013100842096812539264420499035217050630853', + ], + ], + [ + [ + '7077479683546002997211712695946002074877511277312570035766170199895071832130', + '10093483419865920389913245021038182291233451549023025229112148274109565435465', + ], + [ + '4595479056700221319381530156280926371456704509942304414423590385166031118820', + '19831328484489333784475432780421641293929726139240675179672856274388269393268', + ], + [ + '11934129596455521040620786944827826205713621633706285934057045369193958244500', + '8037395052364110730298837004334506829870972346962140206007064471173334027475', + ], + ], + ], + IC: [ + [ + '8938477176893632284539660223582989287068454472173218831171935317066824606917', + '9409256007408490215082527289125535503645664704563806461206465772769775389099', '1', ], [ - '9963925548680369694281529428493633860798787720411551209962187028714238599244', - '15230355743339633214614822894639457206342798359948816221317666466364056693778', + '9406714425071299321475031445293798140218018985951799864352153321364734136304', + '16190677786472274760082919772279780726948507745938757138812410377304364738549', '1', ], [ - '6950503983449781600992674217118280101447858351713896494323644937055764792179', - '7808371895612695836144162291280415010167996463807346279391316375701199123656', - '1', - ], - [ - '13087819526106045804491969868083814032877953990371363285347311427240882433405', - '11966315196652888498918569284209954760513051208513943007355755386005245500621', - '1', - ], - [ - '12282094920874016072527195101275592151196471143933884133850581178083772252468', - '15235775181834492665531005890809584611442500045185485789836774473981675243021', - '1', - ], - [ - '21812893128244174490845186114318384111309631750779790216970477092597858785848', - '2589668621973063987240457489694488373626065998506803531328282066733461219789', - '1', - ], - [ - '19736662517096924810330699257646097752878927062458868611606640349386574406597', - '4060072089820676848549092831416954645358719303909012118727092426282594652097', - '1', - ], - [ - '11719045032278320337718855450887707488077032991245401830404333912363712928329', - '20429459895397450814127488292309624504089765803886468608012330383192001870669', - '1', - ], - [ - '19004300167179661071877369317533970354840401420580304563087979100558811364752', - '14022355176806855344226798992865107732568349801838395169345899640004492990780', - '1', - ], - [ - '13025111431804865430474042749794279094175996106474252922476866200732597336046', - '19194822113343343818445001979787110041399069657752328387546318167620461948345', - '1', - ], - [ - '9281420432070758003455252378726458803842106759942105235771363724299995612746', - '10868451960138543140016636651288606302763103558353989956931009739891356663931', - '1', - ], - [ - '6881336186311591694695019764740377864808644241540598555149034884273494350187', - '2786626651294071079928778385820392810695323510322088767264869337774915531398', - '1', - ], - [ - '3868887205879058647589024065476442552380444440663584200982400886666148790886', - '11551675778288798821641950282239822965295574518488993089870363480882633402459', - '1', - ], - [ - '15813415033010213936605839865891774355917576449019813469202169386353113595525', - '1870609980383477505308305636362868336100621908783112905201075702227453404515', - '1', - ], - [ - '21157749399797355849483685908557759044725963489573087916410489368331599307794', - '14854788610114730057746604308209140307245160608186995032258954670144804516951', - '1', - ], - [ - '16385822015903726360112736921395358523788300915122301306552678753856007139950', - '21852835770022094372574175833966771172218278214837381092449524015831270082443', - '1', - ], - [ - '19248229849556541284232787022946438125257193537403487445681116918406935705772', - '6793532544675704467666654331031442502924492451846685587791069517168976618998', - '1', - ], - [ - '18836493774186273716542303051216191129399492669285916094819482474147823428307', - '6509216046298943932406178827402599665125001577776608255883440730727474111293', - '1', - ], - [ - '2809621426118306485690309749022510198095154893010698079349725235989206182716', - '4497399751266171421893644701436353917277499549766569195676686979408432567040', - '1', - ], - [ - '6170933041071274746144995882643288027398824461699676189537821814335178617455', - '11939422270083845120808849126065865852360008142577598821317493776193010862067', - '1', - ], - [ - '13907558045406644600787852336772673795175470348585502650967694884336926510609', - '10402183670236567810177022075839926456374913570620093187656925228836601635112', - '1', - ], - [ - '9838642093868354522737246454481472310758813632867424849018704154654047098002', - '14170682187871718359141978372219783598112313240689881926254383115412693527146', - '1', - ], - [ - '19268808621727088609886330192714416713650881167710481846410131194840574819112', - '4548738826497792767156172897040645223030132393843932100535275236775868885881', - '1', - ], - [ - '1232597044718469636783882051858339480293220683836150648093038756719345792733', - '4164564286518446421261975868208035039046547841927122039276997213757603100039', - '1', - ], - [ - '5294620535222419533372104895954254795204154607883725935765987312986783187633', - '1510029808556715440734681023178490974005286595460173200541383072987550292273', - '1', - ], - [ - '7913521827196573900757966679883707039843053961079282485559952654357012865178', - '12822272309007319462325152966219603030360768804371467102819817670179571110561', - '1', - ], - [ - '11741856835780496736124514582563930032103983891200913422564159143798432491425', - '1186685996713398101844576210414267184996946163374847451849537513476110807467', - '1', - ], - [ - '3848448520214659071169420881995284083054226208336229906265856219238707518565', - '12381374183899166684143029657071579470205894330864920551795150885160592446021', - '1', - ], - [ - '2729354333883753415251820919113586347364808857675492186344760048485902311739', - '15156786867835927048585286604426887268249585171019125857459307501585887100974', - '1', - ], - [ - '16327260115537188860691343668951285278159950664733445048928882023437126306794', - '15469859044222521064134805435371078520376095849473017636140479838581816450418', - '1', - ], - [ - '9716588579885449992683804112491878761164620027004437304769643243506657789021', - '4292706684411927078028017507044791383954395251541518728702327216870791282903', - '1', - ], - [ - '3831081473705850109604085868551425990958661808755750553828925508405362814908', - '7692088199943626776992448758764793963791676479101773985328943563508103917662', - '1', - ], - [ - '14553842600721830593567536989022992714300892536915530699049444789457197924996', - '1034992513758432733967666097255620384731263408100321609845288946444012846161', - '1', - ], - [ - '8298955061619332575841977271384783772861434116506072222428896327020549590778', - '3417308972480405311337590188121737420812511616327499449644814830511455229125', - '1', - ], - [ - '17492972350290531881303108441024194391834883043603735002548578305198527539792', - '20178093813925833001632123131014833838779278549203596933431582561790322377177', - '1', - ], - [ - '20266961211116442799505089820504252966764415195756292869126528880324253997340', - '12721789314780990071009310805499247522750756905074142078629315686295071200692', - '1', - ], - [ - '2848503208668242872636925431606615634594079979671099350197778718883635724263', - '4654302785281536218111627060020949573498492991361181450337516475942903887229', - '1', - ], - [ - '8117348408366486765069198284524473502421701109551785034877096925063881017611', - '9240711853483292534294774801520720851385345818400239892620763606707852365913', - '1', - ], - [ - '14726850444695139754420062135396416349332736703641747713568870234740172750594', - '19695982391909446471699547638880558431250407146813365085967041143148712990281', - '1', - ], - [ - '10713887585841823429966077588975547340632721253524339816841192298502141129142', - '15875414001171419054582031537391697841529253632260086276963072228015908768572', - '1', - ], - [ - '8726527790501042190955587753490550968193378562596306669177536293797796528295', - '2567561473507954539369683410861518721775690943462480886913419763755359623122', - '1', - ], - [ - '20228003583559059699534488013186856436814319213991227118734829746090088629768', - '3062194835746234285630044060950549287869018179041416439415198981293605738893', - '1', - ], - [ - '15326430247878492538731905607224792134839791639951162876881030896580260626530', - '3286497031007325457398280838270081812804433848003710004921976210893659583713', - '1', - ], - [ - '21172455070651382026237320306432651725116860744924112007014279936313214413699', - '3124713746257151433204645213247841462846602236377570194562035843093051589347', - '1', - ], - [ - '586329559940161576696101534294134289580198770923725856103144741895937907876', - '8549358505942194684807970498040082081114943321990836277323952699459596852004', - '1', - ], - [ - '10801780763283804426115126946120589447640285117455543080483767206343304180554', - '10884989352940730579839064046335509450313089154054617399581142310521031798261', - '1', - ], - [ - '7642110729948206734175115603784267089466490166359943206653870027902551255488', - '11475699997476077360759438942765927072551540977873657634945729769964213394775', - '1', - ], - [ - '6477632809823450072672399006410105825115578703752819060774243488278595863752', - '5842794419924960246061189125669290725098229148434953995106895999043038757426', - '1', - ], - [ - '9851792234521580273104967216407455160006004510538086041246868797144654998160', - '11682999507634805671497034306879162257155282925402112526209517483082417200425', - '1', - ], - [ - '7849825230838163134393354944881831816101066439604289649483128481654599764130', - '19425402357687722197365422640263552055101566124507367803085779467136114966243', + '17636847760071529621476637340355013609204110513013412005293780563117471695582', + '16269304104382842638426610869142550901209834485529174763447294867648099217552', '1', ], ], @@ -851,6 +607,342 @@ export const vkey_prove_rsa_65537_sha1 = { ], }; +export const vkey_prove_rsa_65537_sha256 = { + protocol: 'groth16', + curve: 'bn128', + nPublic: 51, + vk_alpha_1: [ + '20491192805390485299153009773594534940189261866228447918068658471970481763042', + '9383485363053290200918347156157836566562967994039712273449902621266178545958', + '1', + ], + vk_beta_2: [ + [ + '6375614351688725206403948262868962793625744043794305715222011528459656738731', + '4252822878758300859123897981450591353533073413197771768651442665752259397132', + ], + [ + '10505242626370262277552901082094356697409835680220590971873171140371331206856', + '21847035105528745403288232691147584728191162732299865338377159692350059136679', + ], + ['1', '0'], + ], + vk_gamma_2: [ + [ + '10857046999023057135944570762232829481370756359578518086990519993285655852781', + '11559732032986387107991004021392285783925812861821192530917403151452391805634', + ], + [ + '8495653923123431417604973247489272438418190587263600148770280649306958101930', + '4082367875863433681332203403145435568316851327593401208105741076214120093531', + ], + ['1', '0'], + ], + vk_delta_2: [ + [ + '14621056654033030159111384006506361438733969388519830162058188044378001616013', + '13712669930807027066432455963920004753804575153306478154082225172540894791697', + ], + [ + '21664748003830939002021923557411259243652793372223608877730406967860908232579', + '8648865297679023017392575432725553399779724355967143745086353612497178809510', + ], + ['1', '0'], + ], + vk_alphabeta_12: [ + [ + [ + '2029413683389138792403550203267699914886160938906632433982220835551125967885', + '21072700047562757817161031222997517981543347628379360635925549008442030252106', + ], + [ + '5940354580057074848093997050200682056184807770593307860589430076672439820312', + '12156638873931618554171829126792193045421052652279363021382169897324752428276', + ], + [ + '7898200236362823042373859371574133993780991612861777490112507062703164551277', + '7074218545237549455313236346927434013100842096812539264420499035217050630853', + ], + ], + [ + [ + '7077479683546002997211712695946002074877511277312570035766170199895071832130', + '10093483419865920389913245021038182291233451549023025229112148274109565435465', + ], + [ + '4595479056700221319381530156280926371456704509942304414423590385166031118820', + '19831328484489333784475432780421641293929726139240675179672856274388269393268', + ], + [ + '11934129596455521040620786944827826205713621633706285934057045369193958244500', + '8037395052364110730298837004334506829870972346962140206007064471173334027475', + ], + ], + ], + IC: [ + [ + '18837847330460101767470685001162782791541723452908436811770248703422385774364', + '13924593305026026107933010319328520261235500792223955967488219194416191858218', + '1', + ], + [ + '18499035744135408535006498619187878301564976457052297587006457806425510673636', + '7226548414236873699673490761763172101403491901987747605307552092067988817369', + '1', + ], + [ + '15076998223900447147424484817648709178376766854878255045911605115135846913714', + '1588555885165810180157418868205800830691130731692806042865483039743264904188', + '1', + ], + [ + '9963925548680369694281529428493633860798787720411551209962187028714238599244', + '15230355743339633214614822894639457206342798359948816221317666466364056693778', + '1', + ], + [ + '6950503983449781600992674217118280101447858351713896494323644937055764792179', + '7808371895612695836144162291280415010167996463807346279391316375701199123656', + '1', + ], + [ + '13087819526106045804491969868083814032877953990371363285347311427240882433405', + '11966315196652888498918569284209954760513051208513943007355755386005245500621', + '1', + ], + [ + '12282094920874016072527195101275592151196471143933884133850581178083772252468', + '15235775181834492665531005890809584611442500045185485789836774473981675243021', + '1', + ], + [ + '21812893128244174490845186114318384111309631750779790216970477092597858785848', + '2589668621973063987240457489694488373626065998506803531328282066733461219789', + '1', + ], + [ + '19736662517096924810330699257646097752878927062458868611606640349386574406597', + '4060072089820676848549092831416954645358719303909012118727092426282594652097', + '1', + ], + [ + '11719045032278320337718855450887707488077032991245401830404333912363712928329', + '20429459895397450814127488292309624504089765803886468608012330383192001870669', + '1', + ], + [ + '19004300167179661071877369317533970354840401420580304563087979100558811364752', + '14022355176806855344226798992865107732568349801838395169345899640004492990780', + '1', + ], + [ + '13025111431804865430474042749794279094175996106474252922476866200732597336046', + '19194822113343343818445001979787110041399069657752328387546318167620461948345', + '1', + ], + [ + '9281420432070758003455252378726458803842106759942105235771363724299995612746', + '10868451960138543140016636651288606302763103558353989956931009739891356663931', + '1', + ], + [ + '6881336186311591694695019764740377864808644241540598555149034884273494350187', + '2786626651294071079928778385820392810695323510322088767264869337774915531398', + '1', + ], + [ + '3868887205879058647589024065476442552380444440663584200982400886666148790886', + '11551675778288798821641950282239822965295574518488993089870363480882633402459', + '1', + ], + [ + '15813415033010213936605839865891774355917576449019813469202169386353113595525', + '1870609980383477505308305636362868336100621908783112905201075702227453404515', + '1', + ], + [ + '21157749399797355849483685908557759044725963489573087916410489368331599307794', + '14854788610114730057746604308209140307245160608186995032258954670144804516951', + '1', + ], + [ + '16385822015903726360112736921395358523788300915122301306552678753856007139950', + '21852835770022094372574175833966771172218278214837381092449524015831270082443', + '1', + ], + [ + '19248229849556541284232787022946438125257193537403487445681116918406935705772', + '6793532544675704467666654331031442502924492451846685587791069517168976618998', + '1', + ], + [ + '18836493774186273716542303051216191129399492669285916094819482474147823428307', + '6509216046298943932406178827402599665125001577776608255883440730727474111293', + '1', + ], + [ + '2809621426118306485690309749022510198095154893010698079349725235989206182716', + '4497399751266171421893644701436353917277499549766569195676686979408432567040', + '1', + ], + [ + '6170933041071274746144995882643288027398824461699676189537821814335178617455', + '11939422270083845120808849126065865852360008142577598821317493776193010862067', + '1', + ], + [ + '13907558045406644600787852336772673795175470348585502650967694884336926510609', + '10402183670236567810177022075839926456374913570620093187656925228836601635112', + '1', + ], + [ + '9838642093868354522737246454481472310758813632867424849018704154654047098002', + '14170682187871718359141978372219783598112313240689881926254383115412693527146', + '1', + ], + [ + '19268808621727088609886330192714416713650881167710481846410131194840574819112', + '4548738826497792767156172897040645223030132393843932100535275236775868885881', + '1', + ], + [ + '1232597044718469636783882051858339480293220683836150648093038756719345792733', + '4164564286518446421261975868208035039046547841927122039276997213757603100039', + '1', + ], + [ + '5294620535222419533372104895954254795204154607883725935765987312986783187633', + '1510029808556715440734681023178490974005286595460173200541383072987550292273', + '1', + ], + [ + '7913521827196573900757966679883707039843053961079282485559952654357012865178', + '12822272309007319462325152966219603030360768804371467102819817670179571110561', + '1', + ], + [ + '11741856835780496736124514582563930032103983891200913422564159143798432491425', + '1186685996713398101844576210414267184996946163374847451849537513476110807467', + '1', + ], + [ + '3848448520214659071169420881995284083054226208336229906265856219238707518565', + '12381374183899166684143029657071579470205894330864920551795150885160592446021', + '1', + ], + [ + '2729354333883753415251820919113586347364808857675492186344760048485902311739', + '15156786867835927048585286604426887268249585171019125857459307501585887100974', + '1', + ], + [ + '16327260115537188860691343668951285278159950664733445048928882023437126306794', + '15469859044222521064134805435371078520376095849473017636140479838581816450418', + '1', + ], + [ + '9716588579885449992683804112491878761164620027004437304769643243506657789021', + '4292706684411927078028017507044791383954395251541518728702327216870791282903', + '1', + ], + [ + '3831081473705850109604085868551425990958661808755750553828925508405362814908', + '7692088199943626776992448758764793963791676479101773985328943563508103917662', + '1', + ], + [ + '14553842600721830593567536989022992714300892536915530699049444789457197924996', + '1034992513758432733967666097255620384731263408100321609845288946444012846161', + '1', + ], + [ + '8298955061619332575841977271384783772861434116506072222428896327020549590778', + '3417308972480405311337590188121737420812511616327499449644814830511455229125', + '1', + ], + [ + '17492972350290531881303108441024194391834883043603735002548578305198527539792', + '20178093813925833001632123131014833838779278549203596933431582561790322377177', + '1', + ], + [ + '20266961211116442799505089820504252966764415195756292869126528880324253997340', + '12721789314780990071009310805499247522750756905074142078629315686295071200692', + '1', + ], + [ + '2848503208668242872636925431606615634594079979671099350197778718883635724263', + '4654302785281536218111627060020949573498492991361181450337516475942903887229', + '1', + ], + [ + '8117348408366486765069198284524473502421701109551785034877096925063881017611', + '9240711853483292534294774801520720851385345818400239892620763606707852365913', + '1', + ], + [ + '14726850444695139754420062135396416349332736703641747713568870234740172750594', + '19695982391909446471699547638880558431250407146813365085967041143148712990281', + '1', + ], + [ + '10713887585841823429966077588975547340632721253524339816841192298502141129142', + '15875414001171419054582031537391697841529253632260086276963072228015908768572', + '1', + ], + [ + '8726527790501042190955587753490550968193378562596306669177536293797796528295', + '2567561473507954539369683410861518721775690943462480886913419763755359623122', + '1', + ], + [ + '20228003583559059699534488013186856436814319213991227118734829746090088629768', + '3062194835746234285630044060950549287869018179041416439415198981293605738893', + '1', + ], + [ + '15326430247878492538731905607224792134839791639951162876881030896580260626530', + '3286497031007325457398280838270081812804433848003710004921976210893659583713', + '1', + ], + [ + '21172455070651382026237320306432651725116860744924112007014279936313214413699', + '3124713746257151433204645213247841462846602236377570194562035843093051589347', + '1', + ], + [ + '586329559940161576696101534294134289580198770923725856103144741895937907876', + '8549358505942194684807970498040082081114943321990836277323952699459596852004', + '1', + ], + [ + '10801780763283804426115126946120589447640285117455543080483767206343304180554', + '10884989352940730579839064046335509450313089154054617399581142310521031798261', + '1', + ], + [ + '7642110729948206734175115603784267089466490166359943206653870027902551255488', + '11475699997476077360759438942765927072551540977873657634945729769964213394775', + '1', + ], + [ + '6477632809823450072672399006410105825115578703752819060774243488278595863752', + '5842794419924960246061189125669290725098229148434953995106895999043038757426', + '1', + ], + [ + '9851792234521580273104967216407455160006004510538086041246868797144654998160', + '11682999507634805671497034306879162257155282925402112526209517483082417200425', + '1', + ], + [ + '7849825230838163134393354944881831816101066439604289649483128481654599764130', + '19425402357687722197365422640263552055101566124507367803085779467136114966243', + '1', + ], + ], +}; + export const vkey_prove_rsapss_65537_sha256 = { protocol: 'groth16', curve: 'bn128', @@ -1187,10 +1279,10 @@ export const vkey_prove_rsapss_65537_sha256 = { ], }; -export const vkey_dsc_rsa_65537_sha1 = { +export const vkey_vc_and_disclose = { protocol: 'groth16', curve: 'bn128', - nPublic: 2, + nPublic: 20, vk_alpha_1: [ '20491192805390485299153009773594534940189261866228447918068658471970481763042', '9383485363053290200918347156157836566562967994039712273449902621266178545958', @@ -1220,12 +1312,12 @@ export const vkey_dsc_rsa_65537_sha1 = { ], vk_delta_2: [ [ - '13609834548161300582477853991526320293433640330539801500903753457627888495381', - '8441806186745188372537460321981116999801031201583499966958826604947980830000', + '2285641925224838978222516003838691301522837942576149813249618262683607431978', + '8933979452959246793652418502124635637608136544198967823461038471987929468463', ], [ - '8448384202753302962223109595229113261664582566011624155734783894938928271855', - '14114983069796580083449425790812236094683577678022815810458824633453412210627', + '16099512190238976912962671487125443206965718241797607052101309249042700281031', + '13866973425260784693055729377971041037525083882024304009793262309055835677530', ], ['1', '0'], ], @@ -1261,200 +1353,108 @@ export const vkey_dsc_rsa_65537_sha1 = { ], IC: [ [ - '8080423910694661461576427977746141048179131345871564682235127365538915251175', - '15077260046277123685110202133518064301144478802752095822556731494534324607918', + '17675992131793847472607582103290528032110944356332065253938771650575512637150', + '3334529637547487349647542974294469269353530698716058653313633946852810587219', '1', ], [ - '16011015387972163546219334947336021918538394305813640145299036166236111586389', - '19422172264639146231714658451804174650252945098828793769061895536286758622279', + '4073040853156698088579125842860781690905988024237850240402200045640356184109', + '4679018198189152660097843382950080652306538412548068412033586660232563193013', '1', ], [ - '2543445962105990625291559091474595879940066015891597850114251320085420659626', - '1917249161113849432608012443620967377861258824599427135619631874160142518773', - '1', - ], - ], -}; - -export const vkey_dsc_rsa_65537_sha256 = { - protocol: 'groth16', - curve: 'bn128', - nPublic: 2, - vk_alpha_1: [ - '20491192805390485299153009773594534940189261866228447918068658471970481763042', - '9383485363053290200918347156157836566562967994039712273449902621266178545958', - '1', - ], - vk_beta_2: [ - [ - '6375614351688725206403948262868962793625744043794305715222011528459656738731', - '4252822878758300859123897981450591353533073413197771768651442665752259397132', - ], - [ - '10505242626370262277552901082094356697409835680220590971873171140371331206856', - '21847035105528745403288232691147584728191162732299865338377159692350059136679', - ], - ['1', '0'], - ], - vk_gamma_2: [ - [ - '10857046999023057135944570762232829481370756359578518086990519993285655852781', - '11559732032986387107991004021392285783925812861821192530917403151452391805634', - ], - [ - '8495653923123431417604973247489272438418190587263600148770280649306958101930', - '4082367875863433681332203403145435568316851327593401208105741076214120093531', - ], - ['1', '0'], - ], - vk_delta_2: [ - [ - '8403974525672515951605465754909425916978281298593354504437469807907113049853', - '12233134836151850512596961158180983853133742935319340320561432564845137384819', - ], - [ - '3209155548902127778431906050698597513646227271655778722256683596743569531044', - '16830777068052670490128170305087202969267881418665601837992321846223880096264', - ], - ['1', '0'], - ], - vk_alphabeta_12: [ - [ - [ - '2029413683389138792403550203267699914886160938906632433982220835551125967885', - '21072700047562757817161031222997517981543347628379360635925549008442030252106', - ], - [ - '5940354580057074848093997050200682056184807770593307860589430076672439820312', - '12156638873931618554171829126792193045421052652279363021382169897324752428276', - ], - [ - '7898200236362823042373859371574133993780991612861777490112507062703164551277', - '7074218545237549455313236346927434013100842096812539264420499035217050630853', - ], - ], - [ - [ - '7077479683546002997211712695946002074877511277312570035766170199895071832130', - '10093483419865920389913245021038182291233451549023025229112148274109565435465', - ], - [ - '4595479056700221319381530156280926371456704509942304414423590385166031118820', - '19831328484489333784475432780421641293929726139240675179672856274388269393268', - ], - [ - '11934129596455521040620786944827826205713621633706285934057045369193958244500', - '8037395052364110730298837004334506829870972346962140206007064471173334027475', - ], - ], - ], - IC: [ - [ - '16231288969314859968324689058413762096922845561563844884666690734569347763082', - '5910239835236935696830364945179549816839705839711095023727334010208312533092', - '1', - ], - [ - '6260576084748320398294355533743833076081106851106584793995576172400792840042', - '21319331746978162427565186170902229291674424766799571029877219503610111212711', - '1', - ], - [ - '16479555690731841331208639285970255252465816317199429107438320860232249030762', - '3783712303962417058251692820237130203111118349896777989119917429903130718416', - '1', - ], - ], -}; - -export const vkey_dsc_rsapss_65537_sha256 = { - protocol: 'groth16', - curve: 'bn128', - nPublic: 2, - vk_alpha_1: [ - '20491192805390485299153009773594534940189261866228447918068658471970481763042', - '9383485363053290200918347156157836566562967994039712273449902621266178545958', - '1', - ], - vk_beta_2: [ - [ - '6375614351688725206403948262868962793625744043794305715222011528459656738731', - '4252822878758300859123897981450591353533073413197771768651442665752259397132', - ], - [ - '10505242626370262277552901082094356697409835680220590971873171140371331206856', - '21847035105528745403288232691147584728191162732299865338377159692350059136679', - ], - ['1', '0'], - ], - vk_gamma_2: [ - [ - '10857046999023057135944570762232829481370756359578518086990519993285655852781', - '11559732032986387107991004021392285783925812861821192530917403151452391805634', - ], - [ - '8495653923123431417604973247489272438418190587263600148770280649306958101930', - '4082367875863433681332203403145435568316851327593401208105741076214120093531', - ], - ['1', '0'], - ], - vk_delta_2: [ - [ - '6017862818859039949402494358517294962820240623943684783826304039772640083873', - '11178871189289476718066796914086694937261254534191355355208715473729459679073', - ], - [ - '18289536515878615632378439677874060078266723798016506833131127246101859022607', - '8005285279181761514246985809032780535330313241768111893953981130952718939039', - ], - ['1', '0'], - ], - vk_alphabeta_12: [ - [ - [ - '2029413683389138792403550203267699914886160938906632433982220835551125967885', - '21072700047562757817161031222997517981543347628379360635925549008442030252106', - ], - [ - '5940354580057074848093997050200682056184807770593307860589430076672439820312', - '12156638873931618554171829126792193045421052652279363021382169897324752428276', - ], - [ - '7898200236362823042373859371574133993780991612861777490112507062703164551277', - '7074218545237549455313236346927434013100842096812539264420499035217050630853', - ], - ], - [ - [ - '7077479683546002997211712695946002074877511277312570035766170199895071832130', - '10093483419865920389913245021038182291233451549023025229112148274109565435465', - ], - [ - '4595479056700221319381530156280926371456704509942304414423590385166031118820', - '19831328484489333784475432780421641293929726139240675179672856274388269393268', - ], - [ - '11934129596455521040620786944827826205713621633706285934057045369193958244500', - '8037395052364110730298837004334506829870972346962140206007064471173334027475', - ], - ], - ], - IC: [ - [ - '8938477176893632284539660223582989287068454472173218831171935317066824606917', - '9409256007408490215082527289125535503645664704563806461206465772769775389099', - '1', - ], - [ - '9406714425071299321475031445293798140218018985951799864352153321364734136304', - '16190677786472274760082919772279780726948507745938757138812410377304364738549', - '1', - ], - [ - '17636847760071529621476637340355013609204110513013412005293780563117471695582', - '16269304104382842638426610869142550901209834485529174763447294867648099217552', + '15776533017793451196514777082124337882856102873453705308829629571923075424417', + '14232490066350175683786572931273695352468015232518411187366417165893912115230', + '1', + ], + [ + '1180945442359952286721463819274447148908421750703875389048309699338667338411', + '6539452064449720183898998074274190026127754682197287334130838477279315254881', + '1', + ], + [ + '13806944210187274185057734594353372528156342424366258241133564778474054935868', + '7800429229653987231645474546639647711164156063512193001130226490464812892063', + '1', + ], + [ + '5522822939351592924817275047393709421174176326835572758552827440001401610167', + '11245123080087310106980015850199801755152167910673823675982721730905258212233', + '1', + ], + [ + '5627481952482064829757269526369949776646066892934683965609154755505296555965', + '4545484539969611530913200236731153733781933150464540998302850238315475023119', + '1', + ], + [ + '20423400711755442910136460433293470952040919967735945317336472158180565467951', + '6378416884992747212393622109218662385874688399353342355554228954581250524736', + '1', + ], + [ + '4373305143599295486105733121446353133863889257489542032576171376300323541304', + '4026684213449689055353486192784741961832172326825055022163307876785200324637', + '1', + ], + [ + '5068833407180337147676969453261920994101725947652623057924700035380226397907', + '11087944526730366835444251823404529324958715143332979454630299083515861157072', + '1', + ], + [ + '21831082589187376499865612459731026493616836999924912561891098220429445525521', + '10842131628581855605843884617846867779319917434679079988533026500378178602950', + '1', + ], + [ + '14267596758527912595273989225764996478640991994242422111486793250630191081468', + '18694764448853871920117677375229746676709722363749986739355723996303104296068', + '1', + ], + [ + '10612001726856359227588948695393307655456532716872467807420271676133645773510', + '1540081878138518609133189078681275557428892906987071405333678709982239141734', + '1', + ], + [ + '7645334938708591281146159611078021141015093576924464549682793344782173540643', + '5041018151127409344023520612676035165867301524846720998477901879854547090576', + '1', + ], + [ + '8883975772061177470672187114470132395286660826025700056750575280427708604951', + '8320685858262483078856712401649683261512346806365950058502541940795537677566', + '1', + ], + [ + '19770351503660034123641636433521671083703252249281883512457304565751018243912', + '1942705439280745753504371271759370270294305194237531295574972095513458387941', + '1', + ], + [ + '15226311176844690062341353072910557091317547496535044902570312644662561307949', + '19296708345054645579869153897992480989971014206920627404347102350029660305578', + '1', + ], + [ + '5436668919010108586842015267897845254611520180011711994231132539697114737868', + '21034924479027562029885591632326051974733967422882023235118894616793689864649', + '1', + ], + [ + '17617503314542022213516716240700336131305032342072938364934850750624139958532', + '7535025988359437238238236154472398858799932717035093350843709570356278900481', + '1', + ], + [ + '15079990665367846194463669410480448234210628628217480894032215566550223721508', + '14422402677444824910876930195543885009950058186617851677855759748496927334854', + '1', + ], + [ + '15949464882252583714786988301769970813994683305898379568444041314948594948891', + '10982667165993413891579207656117800635412428118849500079522502265949839258411', '1', ], ], diff --git a/common/src/scripts/generateCountryOptions.ts b/common/src/scripts/generateCountryOptions.ts index a1c5d56b2..7d8b99445 100644 --- a/common/src/scripts/generateCountryOptions.ts +++ b/common/src/scripts/generateCountryOptions.ts @@ -1,9 +1,10 @@ -import { countryCodes } from '../constants/constants.js'; -import getCountryISO2 from 'country-iso-3-to-2'; import { flag } from 'country-emoji'; +import getCountryISO2 from 'country-iso-3-to-2'; import fs from 'fs'; import path from 'path'; +import { countryCodes } from '../constants/constants.js'; + try { console.log('Generating country options...'); diff --git a/common/src/types/app.ts b/common/src/types/app.ts index ce1351eea..96dd7f7b3 100644 --- a/common/src/types/app.ts +++ b/common/src/types/app.ts @@ -1 +1 @@ -export type { SelfApp, SelfAppDisclosureConfig, EndpointType } from '../utils/appType.js'; +export type { EndpointType, SelfApp, SelfAppDisclosureConfig } from '../utils/appType.js'; diff --git a/common/src/types/index.ts b/common/src/types/index.ts index cf81789c6..84f11c052 100644 --- a/common/src/types/index.ts +++ b/common/src/types/index.ts @@ -1,3 +1,3 @@ -export type { PassportData, DocumentType, DocumentCategory } from '../utils/types.js'; +export type { DocumentCategory, DocumentType, PassportData } from '../utils/types.js'; export type { PassportMetadata } from '../utils/passports/passport_parsing/parsePassportData.js'; export type { UserIdType } from '../utils/circuits/uuid.js'; diff --git a/common/src/types/passport.ts b/common/src/types/passport.ts index ee9a85499..b985989c5 100644 --- a/common/src/types/passport.ts +++ b/common/src/types/passport.ts @@ -1,3 +1,2 @@ -export type { PassportData, DocumentType, DocumentCategory } from '../utils/types.js'; - +export type { DocumentCategory, DocumentType, PassportData } from '../utils/types.js'; export type { PassportMetadata } from '../utils/passports/passport_parsing/parsePassportData.js'; diff --git a/common/src/utils/appType.ts b/common/src/utils/appType.ts index 8d8adda73..4e9cef840 100644 --- a/common/src/utils/appType.ts +++ b/common/src/utils/appType.ts @@ -1,13 +1,14 @@ -import { UserIdType, validateUserId } from './circuits/uuid.js'; - -export type Mode = 'register' | 'dsc' | 'vc_and_disclose'; -export type EndpointType = 'https' | 'celo' | 'staging_celo' | 'staging_https'; - import { v4 } from 'uuid'; + import { REDIRECT_URL } from '../constants/constants.js'; -import { Country3LetterCode } from '../constants/countries.js'; +import type { Country3LetterCode } from '../constants/countries.js'; +import type { UserIdType } from './circuits/uuid.js'; +import { validateUserId } from './circuits/uuid.js'; import { formatEndpoint } from './scope.js'; +export type EndpointType = 'https' | 'celo' | 'staging_celo' | 'staging_https'; +export type Mode = 'register' | 'dsc' | 'vc_and_disclose'; + export interface SelfApp { appName: string; logoBase64: string; diff --git a/common/src/utils/bytes.ts b/common/src/utils/bytes.ts index 866ced1d7..aa9456447 100644 --- a/common/src/utils/bytes.ts +++ b/common/src/utils/bytes.ts @@ -1,8 +1,99 @@ import { MAX_BYTES_IN_FIELD } from '../constants/constants.js'; +export function bigIntToChunkedBytes( + num: BigInt | bigint, + bytesPerChunk: number, + numChunks: number +) { + const res: string[] = []; + const bigintNum: bigint = typeof num == 'bigint' ? num : num.valueOf(); + const msk = (1n << BigInt(bytesPerChunk)) - 1n; + for (let i = 0; i < numChunks; ++i) { + res.push(((bigintNum >> BigInt(i * bytesPerChunk)) & msk).toString()); + } + return res; +} +export function bytesToBigDecimal(arr: number[]): string { + let result = BigInt(0); + for (let i = 0; i < arr.length; i++) { + result = result * BigInt(256) + BigInt(arr[i] & 0xff); + } + return result.toString(); +} + +export function computeIntChunkLength(byteLength: number) { + const packSize = MAX_BYTES_IN_FIELD; + const remain = byteLength % packSize; + let numChunks = (byteLength - remain) / packSize; + if (remain > 0) { + numChunks += 1; + } + return numChunks; +} + +export function derToBytes(derValue: string) { + const bytes = []; + for (let i = 0; i < derValue.length; i++) { + bytes.push(derValue.charCodeAt(i)); + } + return bytes; +} + +export function hexStringToSignedIntArray(hexString: string) { + const result = []; + for (let i = 0; i < hexString.length; i += 2) { + const byte = parseInt(hexString.substr(i, 2), 16); + result.push(byte > 127 ? byte - 256 : byte); + } + return result; +} + +export function hexToBin(n: string): string { + let bin = Number(`0x${n[0]}`).toString(2); + for (let i = 1; i < n.length; i += 1) { + bin += Number(`0x${n[i]}`).toString(2).padStart(4, '0'); + } + return bin; +} + +export function hexToDecimal(hex: string): string { + return BigInt(`0x${hex}`).toString(); +} + +export function hexToSignedBytes(hexString: string): number[] { + const bytes = []; + for (let i = 0; i < hexString.length - 1; i += 2) { + const byte = parseInt(hexString.substr(i, 2), 16); + bytes.push(byte >= 128 ? byte - 256 : byte); + } + return bytes; +} + +export function num2Bits(n: number, inValue: bigint): bigint[] { + const out: bigint[] = new Array(n).fill(BigInt(0)); + let lc1: bigint = BigInt(0); + let e2: bigint = BigInt(1); + + for (let i = 0; i < n; i++) { + out[i] = (inValue >> BigInt(i)) & BigInt(1); + + if (out[i] !== BigInt(0) && out[i] !== BigInt(1)) { + throw new Error('Bit value is not binary.'); + } + + lc1 += out[i] * e2; + e2 = e2 << BigInt(1); + } + + if (lc1 !== inValue) { + throw new Error('Reconstructed value does not match the input.'); + } + return out; +} + export function packBytes(unpacked) { const bytesCount = [31, 31, 31]; - let packed = [0n, 0n, 0n]; + const packed = [0n, 0n, 0n]; let byteIndex = 0; for (let i = 0; i < bytesCount.length; i++) { @@ -15,15 +106,6 @@ export function packBytes(unpacked) { } return packed; } -export function computeIntChunkLength(byteLength: number) { - const packSize = MAX_BYTES_IN_FIELD; - const remain = byteLength % packSize; - let numChunks = (byteLength - remain) / packSize; - if (remain > 0) { - numChunks += 1; - } - return numChunks; -} export function packBytesArray(unpacked: number[]) { const packSize = MAX_BYTES_IN_FIELD; @@ -55,19 +137,6 @@ export function packBytesArray(unpacked: number[]) { return out; } -export function toUnsigned(byte: number) { - return byte & 0xff; -} - -export function toSigned(byte: number) { - return byte > 127 ? byte - 256 : byte; -} - -export const toBinaryString = (byte: any) => { - const binary = (parseInt(byte, 10) & 0xff).toString(2).padStart(8, '0'); - return binary; -}; - export function splitToWords(number: bigint, wordsize: number, numberElement: number) { let t = number; const words: string[] = []; @@ -83,86 +152,17 @@ export function splitToWords(number: bigint, wordsize: number, numberElement: nu return words; } -export function bytesToBigDecimal(arr: number[]): string { - let result = BigInt(0); - for (let i = 0; i < arr.length; i++) { - result = result * BigInt(256) + BigInt(arr[i] & 0xff); - } - return result.toString(); -} +export const toBinaryString = (byte: any) => { + const binary = (parseInt(byte, 10) & 0xff).toString(2).padStart(8, '0'); + return binary; +}; -export function hexToDecimal(hex: string): string { - return BigInt(`0x${hex}`).toString(); +export function toSigned(byte: number) { + return byte > 127 ? byte - 256 : byte; } - -export function hexToSignedBytes(hexString: string): number[] { - let bytes = []; - for (let i = 0; i < hexString.length - 1; i += 2) { - let byte = parseInt(hexString.substr(i, 2), 16); - bytes.push(byte >= 128 ? byte - 256 : byte); - } - return bytes; +export function toUnsigned(byte: number) { + return byte & 0xff; } - export function toUnsignedByte(signedByte: number) { return signedByte < 0 ? signedByte + 256 : signedByte; } - -export function bigIntToChunkedBytes( - num: BigInt | bigint, - bytesPerChunk: number, - numChunks: number -) { - const res: string[] = []; - const bigintNum: bigint = typeof num == 'bigint' ? num : num.valueOf(); - const msk = (1n << BigInt(bytesPerChunk)) - 1n; - for (let i = 0; i < numChunks; ++i) { - res.push(((bigintNum >> BigInt(i * bytesPerChunk)) & msk).toString()); - } - return res; -} - -export function hexStringToSignedIntArray(hexString: string) { - let result = []; - for (let i = 0; i < hexString.length; i += 2) { - let byte = parseInt(hexString.substr(i, 2), 16); - result.push(byte > 127 ? byte - 256 : byte); - } - return result; -} - -export function hexToBin(n: string): string { - let bin = Number(`0x${n[0]}`).toString(2); - for (let i = 1; i < n.length; i += 1) { - bin += Number(`0x${n[i]}`).toString(2).padStart(4, '0'); - } - return bin; -} -export function num2Bits(n: number, inValue: bigint): bigint[] { - const out: bigint[] = new Array(n).fill(BigInt(0)); - let lc1: bigint = BigInt(0); - let e2: bigint = BigInt(1); - - for (let i = 0; i < n; i++) { - out[i] = (inValue >> BigInt(i)) & BigInt(1); - - if (out[i] !== BigInt(0) && out[i] !== BigInt(1)) { - throw new Error('Bit value is not binary.'); - } - - lc1 += out[i] * e2; - e2 = e2 << BigInt(1); - } - - if (lc1 !== inValue) { - throw new Error('Reconstructed value does not match the input.'); - } - return out; -} -export function derToBytes(derValue: string) { - const bytes = []; - for (let i = 0; i < derValue.length; i++) { - bytes.push(derValue.charCodeAt(i)); - } - return bytes; -} diff --git a/common/src/utils/certificate_parsing/certUtils.ts b/common/src/utils/certificate_parsing/certUtils.ts index 167d53fd6..f17fae53e 100644 --- a/common/src/utils/certificate_parsing/certUtils.ts +++ b/common/src/utils/certificate_parsing/certUtils.ts @@ -1,11 +1,10 @@ -export { - getSubjectKeyIdentifier, - getAuthorityKeyIdentifier, - getIssuerCountryCode, -} from './utils.js'; - export type { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, } from './dataStructure.js'; +export { + getAuthorityKeyIdentifier, + getIssuerCountryCode, + getSubjectKeyIdentifier, +} from './utils.js'; diff --git a/common/src/utils/certificate_parsing/curveUtils.ts b/common/src/utils/certificate_parsing/curveUtils.ts index 3724bfc8a..d1993def3 100644 --- a/common/src/utils/certificate_parsing/curveUtils.ts +++ b/common/src/utils/certificate_parsing/curveUtils.ts @@ -1,7 +1,7 @@ export { - normalizeHex, - identifyCurve, - getECDSACurveBits, getCurveForElliptic, + getECDSACurveBits, + identifyCurve, + normalizeHex, standardCurves, } from './curves.js'; diff --git a/common/src/utils/certificate_parsing/curves.ts b/common/src/utils/certificate_parsing/curves.ts index 3a00c5cde..283ae9fb0 100644 --- a/common/src/utils/certificate_parsing/curves.ts +++ b/common/src/utils/certificate_parsing/curves.ts @@ -8,6 +8,72 @@ export interface StandardCurve { h: string; } +export function getCurveForElliptic(curveName: string): string { + const curves = { + secp224r1: 'p224', + secp256r1: 'p256', + secp384r1: 'p384', + secp521r1: 'p521', + brainpoolP224r1: 'brainpoolP224r1', + brainpoolP256r1: 'brainpoolP256r1', + brainpoolP384r1: 'brainpoolP384r1', + brainpoolP512r1: 'brainpoolP512r1', + }; + + if (!curves[curveName]) { + throw new Error('Invalid curve: ' + curveName); + } + + return curves[curveName]; +} + +export function getECDSACurveBits(curveName: string): string { + const curveBits: { [key: string]: number } = { + secp224r1: 224, + secp256r1: 256, + secp384r1: 384, + secp521r1: 521, + brainpoolP224r1: 224, + brainpoolP256r1: 256, + brainpoolP384r1: 384, + brainpoolP512r1: 512, + }; + if (curveName in curveBits) { + return curveBits[curveName].toString(); + } + console.log('\x1b[31m%s\x1b[0m', `curve name ${curveName} not found in curveBits`); + return 'unknown'; +} + +export function identifyCurve(params: any): string { + const normalizedParams = { + p: normalizeHex(params.p), + a: normalizeHex(params.a), + b: normalizeHex(params.b), + G: normalizeHex(params.G), + n: normalizeHex(params.n), + h: normalizeHex(params.h), + }; + + for (const curve of standardCurves) { + if ( + normalizedParams.p === normalizeHex(curve.p) && + normalizedParams.a === normalizeHex(curve.a) && + normalizedParams.b === normalizeHex(curve.b) && + normalizedParams.G === normalizeHex(curve.G) && + normalizedParams.n === normalizeHex(curve.n) && + normalizedParams.h === normalizeHex(curve.h) + ) { + return curve.name; + } + } + console.log('Unknown curve:', normalizedParams); + return 'Unknown curve'; +} + +export function normalizeHex(hex: string): string { + return hex.toLowerCase().replace(/^0x/, '').replace(/^00/, ''); +} export const standardCurves: StandardCurve[] = [ { name: 'secp192r1', @@ -100,69 +166,3 @@ export const standardCurves: StandardCurve[] = [ h: '01', }, ]; - -export function normalizeHex(hex: string): string { - return hex.toLowerCase().replace(/^0x/, '').replace(/^00/, ''); -} - -export function identifyCurve(params: any): string { - const normalizedParams = { - p: normalizeHex(params.p), - a: normalizeHex(params.a), - b: normalizeHex(params.b), - G: normalizeHex(params.G), - n: normalizeHex(params.n), - h: normalizeHex(params.h), - }; - - for (const curve of standardCurves) { - if ( - normalizedParams.p === normalizeHex(curve.p) && - normalizedParams.a === normalizeHex(curve.a) && - normalizedParams.b === normalizeHex(curve.b) && - normalizedParams.G === normalizeHex(curve.G) && - normalizedParams.n === normalizeHex(curve.n) && - normalizedParams.h === normalizeHex(curve.h) - ) { - return curve.name; - } - } - console.log('Unknown curve:', normalizedParams); - return 'Unknown curve'; -} - -export function getECDSACurveBits(curveName: string): string { - const curveBits: { [key: string]: number } = { - secp224r1: 224, - secp256r1: 256, - secp384r1: 384, - secp521r1: 521, - brainpoolP224r1: 224, - brainpoolP256r1: 256, - brainpoolP384r1: 384, - brainpoolP512r1: 512, - }; - if (curveName in curveBits) { - return curveBits[curveName].toString(); - } - console.log('\x1b[31m%s\x1b[0m', `curve name ${curveName} not found in curveBits`); - return 'unknown'; -} -export function getCurveForElliptic(curveName: string): string { - const curves = { - secp224r1: 'p224', - secp256r1: 'p256', - secp384r1: 'p384', - secp521r1: 'p521', - brainpoolP224r1: 'brainpoolP224r1', - brainpoolP256r1: 'brainpoolP256r1', - brainpoolP384r1: 'brainpoolP384r1', - brainpoolP512r1: 'brainpoolP512r1', - }; - - if (!curves[curveName]) { - throw new Error('Invalid curve: ' + curveName); - } - - return curves[curveName]; -} diff --git a/common/src/utils/certificate_parsing/dataStructure.ts b/common/src/utils/certificate_parsing/dataStructure.ts index 084e106e4..ab1b22a47 100644 --- a/common/src/utils/certificate_parsing/dataStructure.ts +++ b/common/src/utils/certificate_parsing/dataStructure.ts @@ -1,4 +1,4 @@ -import { StandardCurve } from './curves.js'; +import type { StandardCurve } from './curves.js'; export interface CertificateData { id: string; @@ -23,6 +23,14 @@ export interface CertificateData { publicKeyAlgoOID?: string; } +export interface PublicKeyDetailsECDSA { + x: string; + y: string; + curve: string; + params: StandardCurve; + bits: string; +} + export interface PublicKeyDetailsRSA { modulus: string; exponent: string; @@ -34,11 +42,3 @@ export interface PublicKeyDetailsRSAPSS extends PublicKeyDetailsRSA { mgf: string; saltLength: string; } - -export interface PublicKeyDetailsECDSA { - x: string; - y: string; - curve: string; - params: StandardCurve; - bits: string; -} diff --git a/common/src/utils/certificate_parsing/elliptic.ts b/common/src/utils/certificate_parsing/elliptic.ts index 2d4980f8e..5b1bc207d 100644 --- a/common/src/utils/certificate_parsing/elliptic.ts +++ b/common/src/utils/certificate_parsing/elliptic.ts @@ -10,7 +10,7 @@ export function initElliptic(): typeof elliptic { configurable: true, enumerable: true, get: function () { - var curve = new PresetCurve(options); + const curve = new PresetCurve(options); Object.defineProperty(curves, name, { configurable: true, enumerable: true, diff --git a/common/src/utils/certificate_parsing/index.ts b/common/src/utils/certificate_parsing/index.ts index 97d2c62d6..538fa407e 100644 --- a/common/src/utils/certificate_parsing/index.ts +++ b/common/src/utils/certificate_parsing/index.ts @@ -1,33 +1,27 @@ -export { parseCertificateSimple } from './parseCertificateSimple.js'; - -export { parseCertificate } from './parseCertificate.js'; - -export { initElliptic } from './elliptic.js'; - -export { - normalizeHex, - identifyCurve, - getECDSACurveBits, - getCurveForElliptic, - standardCurves, -} from './curves.js'; - -export { - oidMap, - mapSecpCurves, - getSecpFromNist, - getFriendlyName, - extractHashFunction, -} from './oids.js'; - -export { - getSubjectKeyIdentifier, - getAuthorityKeyIdentifier, - getIssuerCountryCode, -} from './utils.js'; - export type { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, } from './dataStructure.js'; +export { + extractHashFunction, + getFriendlyName, + getSecpFromNist, + mapSecpCurves, + oidMap, +} from './oids.js'; +export { + getAuthorityKeyIdentifier, + getIssuerCountryCode, + getSubjectKeyIdentifier, +} from './utils.js'; +export { + getCurveForElliptic, + getECDSACurveBits, + identifyCurve, + normalizeHex, + standardCurves, +} from './curves.js'; +export { initElliptic } from './elliptic.js'; +export { parseCertificate } from './parseCertificate.js'; +export { parseCertificateSimple } from './parseCertificateSimple.js'; diff --git a/common/src/utils/certificate_parsing/oidUtils.ts b/common/src/utils/certificate_parsing/oidUtils.ts index dbd661daa..a2e434531 100644 --- a/common/src/utils/certificate_parsing/oidUtils.ts +++ b/common/src/utils/certificate_parsing/oidUtils.ts @@ -1,7 +1,7 @@ export { - oidMap, - mapSecpCurves, - getSecpFromNist, - getFriendlyName, extractHashFunction, + getFriendlyName, + getSecpFromNist, + mapSecpCurves, + oidMap, } from './oids.js'; diff --git a/common/src/utils/certificate_parsing/oids.ts b/common/src/utils/certificate_parsing/oids.ts index 663e1bc33..768c1ddde 100644 --- a/common/src/utils/certificate_parsing/oids.ts +++ b/common/src/utils/certificate_parsing/oids.ts @@ -1,3 +1,50 @@ +export function extractHashFunction(friendlyName: string): string { + if (friendlyName.toLowerCase().includes('sha1')) { + return 'sha1'; + } + if (friendlyName.toLowerCase().includes('sha256')) { + return 'sha256'; + } + if (friendlyName.toLowerCase().includes('sha384')) { + return 'sha384'; + } + if (friendlyName.toLowerCase().includes('sha512')) { + return 'sha512'; + } + throw new Error('hash function not found in: ' + friendlyName); + + return 'unknown'; +} + +export function getFriendlyName(oid: string): string { + return getFriendlyNameSecpCurves(oidMap[oid]) || 'Unknown Algorithm'; +} + +export function getSecpFromNist(nist: string): string { + switch (nist) { + case 'nistP224': + return 'secp224r1'; + case 'nistP256': + return 'secp256r1'; + case 'nistP384': + return 'secp384r1'; + case 'nistP521': + return 'secp521r1'; + } + return nist; +} + +function getFriendlyNameSecpCurves(friendlyName: string): string { + return mapSecpCurves[friendlyName] || friendlyName; +} + +export const mapSecpCurves: { [key: string]: string } = { + ECDSA_224: 'secp224r1', + ECDSA_P256: 'secp256r1', + ECDSA_P384: 'secp384r1', + ECDSA_P521: 'secp521r1', +}; + export const oidMap: { [key: string]: string } = { '1.2.840.113549.3.7': '3des', '2.16.840.1.101.3.4.1.2': 'aes128', @@ -105,50 +152,3 @@ export const oidMap: { [key: string]: string } = { '1.2.840.10045.3.1.5': 'x962P239v2', '1.2.840.10045.3.1.6': 'x962P239v3', }; - -export const mapSecpCurves: { [key: string]: string } = { - ECDSA_224: 'secp224r1', - ECDSA_P256: 'secp256r1', - ECDSA_P384: 'secp384r1', - ECDSA_P521: 'secp521r1', -}; - -export function getSecpFromNist(nist: string): string { - switch (nist) { - case 'nistP224': - return 'secp224r1'; - case 'nistP256': - return 'secp256r1'; - case 'nistP384': - return 'secp384r1'; - case 'nistP521': - return 'secp521r1'; - } - return nist; -} - -function getFriendlyNameSecpCurves(friendlyName: string): string { - return mapSecpCurves[friendlyName] || friendlyName; -} - -export function getFriendlyName(oid: string): string { - return getFriendlyNameSecpCurves(oidMap[oid]) || 'Unknown Algorithm'; -} - -export function extractHashFunction(friendlyName: string): string { - if (friendlyName.toLowerCase().includes('sha1')) { - return 'sha1'; - } - if (friendlyName.toLowerCase().includes('sha256')) { - return 'sha256'; - } - if (friendlyName.toLowerCase().includes('sha384')) { - return 'sha384'; - } - if (friendlyName.toLowerCase().includes('sha512')) { - return 'sha512'; - } - throw new Error('hash function not found in: ' + friendlyName); - - return 'unknown'; -} diff --git a/common/src/utils/certificate_parsing/parseCertificate.ts b/common/src/utils/certificate_parsing/parseCertificate.ts index c1f7a4627..f1965eaf3 100644 --- a/common/src/utils/certificate_parsing/parseCertificate.ts +++ b/common/src/utils/certificate_parsing/parseCertificate.ts @@ -1,5 +1,5 @@ +import type { CertificateData } from './dataStructure.js'; import { parseCertificateSimple } from './parseCertificateSimple.js'; -import { CertificateData } from './dataStructure.js'; export async function parseCertificate(pem: string, fileName: string): Promise { // Check if we're in a Node.js environment diff --git a/common/src/utils/certificate_parsing/parseCertificateNode.ts b/common/src/utils/certificate_parsing/parseCertificateNode.ts index 01d4bfceb..606eda830 100644 --- a/common/src/utils/certificate_parsing/parseCertificateNode.ts +++ b/common/src/utils/certificate_parsing/parseCertificateNode.ts @@ -1,6 +1,7 @@ -import { writeFileSync, unlinkSync } from 'fs'; import { execSync } from 'child_process'; -import { CertificateData } from './dataStructure.js'; +import { unlinkSync, writeFileSync } from 'fs'; + +import type { CertificateData } from './dataStructure.js'; export function addOpenSslInfo( certificateData: CertificateData, diff --git a/common/src/utils/certificate_parsing/parseCertificateSimple.ts b/common/src/utils/certificate_parsing/parseCertificateSimple.ts index 8d3940891..baf68669f 100644 --- a/common/src/utils/certificate_parsing/parseCertificateSimple.ts +++ b/common/src/utils/certificate_parsing/parseCertificateSimple.ts @@ -1,91 +1,45 @@ import * as asn1js from 'asn1js'; import { Certificate, RSAPublicKey, RSASSAPSSParams } from 'pkijs'; -import { getFriendlyName, getSecpFromNist } from './oids.js'; -import { + +import { circuitNameFromMode } from '../../constants/constants.js'; +import type { Mode } from '../appType.js'; +import type { StandardCurve } from './curves.js'; +import { getCurveForElliptic, getECDSACurveBits, identifyCurve } from './curves.js'; +import type { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, PublicKeyDetailsRSAPSS, } from './dataStructure.js'; -import { getCurveForElliptic, getECDSACurveBits, identifyCurve, StandardCurve } from './curves.js'; -import { getIssuerCountryCode, getSubjectKeyIdentifier } from './utils.js'; -import { circuitNameFromMode } from '../../constants/constants.js'; -import { Mode } from '../appType.js'; import { initElliptic } from './elliptic.js'; +import { getFriendlyName, getSecpFromNist } from './oids.js'; +import { getIssuerCountryCode, getSubjectKeyIdentifier } from './utils.js'; -export function parseCertificateSimple(pem: string): CertificateData { - let certificateData: CertificateData = { - id: '', - issuer: '', - validity: { - notBefore: '', - notAfter: '', - }, - subjectKeyIdentifier: '', - authorityKeyIdentifier: '', - signatureAlgorithm: '', - hashAlgorithm: '', - publicKeyDetails: undefined, - tbsBytes: undefined, - tbsBytesLength: '', - rawPem: '', - rawTxt: '', - publicKeyAlgoOID: '', - }; - try { - const cert = getCertificateFromPem(pem); - certificateData.tbsBytes = getTBSBytesForge(cert); - certificateData.tbsBytesLength = certificateData.tbsBytes.length.toString(); +export const getAuthorityKeyIdentifier = (cert: Certificate): string => { + const authorityKeyIdentifier = cert.extensions.find((ext) => ext.extnID === '2.5.29.35'); + if (authorityKeyIdentifier) { + let akiValue = Buffer.from(authorityKeyIdentifier.extnValue.valueBlock.valueHexView).toString( + 'hex' + ); - const publicKeyAlgoOID = cert.subjectPublicKeyInfo.algorithm.algorithmId; - const publicKeyAlgoFN = getFriendlyName(publicKeyAlgoOID); - const signatureAlgoOID = cert.signatureAlgorithm.algorithmId; - const signatureAlgoFN = getFriendlyName(signatureAlgoOID); - certificateData.hashAlgorithm = getHashAlgorithm(signatureAlgoFN); - certificateData.publicKeyAlgoOID = publicKeyAlgoOID; - let params; - if (publicKeyAlgoFN === 'RSA' && signatureAlgoFN != 'RSASSA_PSS') { - certificateData.signatureAlgorithm = 'rsa'; - params = getParamsRSA(cert); - } else if (publicKeyAlgoFN === 'ECC') { - certificateData.signatureAlgorithm = 'ecdsa'; - params = getParamsECDSA(cert); - } else if (publicKeyAlgoFN === 'RSASSA_PSS' || signatureAlgoFN === 'RSASSA_PSS') { - certificateData.signatureAlgorithm = 'rsapss'; - params = getParamsRSAPSS(cert); - } else { - console.log(publicKeyAlgoFN); - } - certificateData.publicKeyDetails = params; - certificateData.issuer = getIssuerCountryCode(cert); - certificateData.validity = { - notBefore: cert.notBefore.value.toString(), - notAfter: cert.notAfter.value.toString(), - }; - const ski = getSubjectKeyIdentifier(cert); - certificateData.id = ski.slice(0, 12); - certificateData.subjectKeyIdentifier = ski; - certificateData.rawPem = pem; - - const authorityKeyIdentifier = getAuthorityKeyIdentifier(cert); - certificateData.authorityKeyIdentifier = authorityKeyIdentifier; - - // corner case for rsapss - if ( - certificateData.signatureAlgorithm === 'rsapss' && - (!certificateData.hashAlgorithm || certificateData.hashAlgorithm === 'unknown') - ) { - certificateData.hashAlgorithm = ( - certificateData.publicKeyDetails as PublicKeyDetailsRSAPSS - ).hashAlgorithm; + // Match the ASN.1 sequence header pattern: 30 followed by length + const sequenceMatch = akiValue.match(/^30([0-9a-f]{2}|8[0-9a-f][0-9a-f])/i); + if (sequenceMatch) { + // console.log('Sequence length indicator:', sequenceMatch[1]); } - return certificateData; - } catch (error) { - console.error(`Error processing certificate`, error); - throw error; + // Match the keyIdentifier pattern: 80 followed by length (usually 14) + const keyIdMatch = akiValue.match(/80([0-9a-f]{2})/i); + if (keyIdMatch) { + const keyIdLength = parseInt(keyIdMatch[1], 16); + // Extract the actual key ID (length * 2 because hex) + const startIndex = akiValue.indexOf(keyIdMatch[0]) + 4; + akiValue = akiValue.slice(startIndex, startIndex + keyIdLength * 2); + return akiValue.toUpperCase(); + } } -} + return null; +}; function getParamsRSA(cert: Certificate): PublicKeyDetailsRSA { const publicKeyValue = cert.subjectPublicKeyInfo.parsedKey as RSAPublicKey; @@ -136,6 +90,89 @@ function getParamsRSAPSS(cert: Certificate): PublicKeyDetailsRSAPSS { }; } +export function getCertificateFromPem(pemContent: string): Certificate { + const pemFormatted = pemContent.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n|\r)/g, ''); + const binary = Buffer.from(pemFormatted, 'base64'); + const arrayBuffer = new ArrayBuffer(binary.length); + const view = new Uint8Array(arrayBuffer); + for (let i = 0; i < binary.length; i++) { + view[i] = binary[i]; + } + + const asn1 = asn1js.fromBER(arrayBuffer); + if (asn1.offset === -1) { + throw new Error(`ASN.1 parsing error: ${asn1.result.error}`); + } + + return new Certificate({ schema: asn1.result }); +} + +export const getCircuitName = ( + circuitMode: 'prove' | 'dsc' | 'vc_and_disclose', + signatureAlgorithm: string, + hashFunction: string, + domainParameter: string, + keyLength: string +) => { + const circuit = circuitNameFromMode[circuitMode]; + if (circuit == 'vc_and_disclose') { + return 'vc_and_disclose'; + } + if (circuit == 'dsc') { + return ( + circuit + + '_' + + signatureAlgorithm + + '_' + + hashFunction + + '_' + + domainParameter + + '_' + + keyLength + ); + } + return ( + circuit + + '_' + + signatureAlgorithm + + '_' + + hashFunction + + '_' + + domainParameter + + '_' + + keyLength + ); +}; + +export const getCircuitNameOld = ( + circuitMode: Mode, + signatureAlgorithm: string, + hashFunction: string +) => { + const circuit = circuitNameFromMode[circuitMode]; + if (circuit == 'vc_and_disclose') { + return 'vc_and_disclose'; + } else if (signatureAlgorithm === 'ecdsa') { + return circuit + '_' + signatureAlgorithm + '_secp256r1_' + hashFunction; + } else { + return circuit + '_' + signatureAlgorithm + '_65537_' + hashFunction; + } +}; +export function getHashAlgorithm(rawSignatureAlgorithm: string) { + const input = rawSignatureAlgorithm.toLowerCase(); + const patterns = [/sha-?1/i, /sha-?224/i, /sha-?256/i, /sha-?384/i, /sha-?512/i]; + + for (const pattern of patterns) { + const match = input.match(pattern); + if (match) { + // Remove any hyphens and return standardized format + return match[0].replace('-', ''); + } + } + + return 'unknown'; +} + export function getParamsECDSA(cert: Certificate): PublicKeyDetailsECDSA { try { const algorithmParams = cert.subjectPublicKeyInfo.algorithm.algorithmParams; @@ -155,7 +192,7 @@ export function getParamsECDSA(cert: Certificate): PublicKeyDetailsECDSA { bits, x, y = 'Unknown'; - let curveParams: StandardCurve = {} as StandardCurve; + const curveParams: StandardCurve = {} as StandardCurve; // Try to get the curve name from the OID if (algorithmParams instanceof asn1js.ObjectIdentifier) { @@ -257,115 +294,80 @@ export function getParamsECDSA(cert: Certificate): PublicKeyDetailsECDSA { } } -export const getAuthorityKeyIdentifier = (cert: Certificate): string => { - const authorityKeyIdentifier = cert.extensions.find((ext) => ext.extnID === '2.5.29.35'); - if (authorityKeyIdentifier) { - let akiValue = Buffer.from(authorityKeyIdentifier.extnValue.valueBlock.valueHexView).toString( - 'hex' - ); - - // Match the ASN.1 sequence header pattern: 30 followed by length - const sequenceMatch = akiValue.match(/^30([0-9a-f]{2}|8[0-9a-f][0-9a-f])/i); - if (sequenceMatch) { - // console.log('Sequence length indicator:', sequenceMatch[1]); - } - - // Match the keyIdentifier pattern: 80 followed by length (usually 14) - const keyIdMatch = akiValue.match(/80([0-9a-f]{2})/i); - if (keyIdMatch) { - const keyIdLength = parseInt(keyIdMatch[1], 16); - // Extract the actual key ID (length * 2 because hex) - const startIndex = akiValue.indexOf(keyIdMatch[0]) + 4; - akiValue = akiValue.slice(startIndex, startIndex + keyIdLength * 2); - return akiValue.toUpperCase(); - } - } - return null; -}; - -export const getCircuitName = ( - circuitMode: 'prove' | 'dsc' | 'vc_and_disclose', - signatureAlgorithm: string, - hashFunction: string, - domainParameter: string, - keyLength: string -) => { - const circuit = circuitNameFromMode[circuitMode]; - if (circuit == 'vc_and_disclose') { - return 'vc_and_disclose'; - } - if (circuit == 'dsc') { - return ( - circuit + - '_' + - signatureAlgorithm + - '_' + - hashFunction + - '_' + - domainParameter + - '_' + - keyLength - ); - } - return ( - circuit + - '_' + - signatureAlgorithm + - '_' + - hashFunction + - '_' + - domainParameter + - '_' + - keyLength - ); -}; -export const getCircuitNameOld = ( - circuitMode: Mode, - signatureAlgorithm: string, - hashFunction: string -) => { - const circuit = circuitNameFromMode[circuitMode]; - if (circuit == 'vc_and_disclose') { - return 'vc_and_disclose'; - } else if (signatureAlgorithm === 'ecdsa') { - return circuit + '_' + signatureAlgorithm + '_secp256r1_' + hashFunction; - } else { - return circuit + '_' + signatureAlgorithm + '_65537_' + hashFunction; - } -}; - -export function getHashAlgorithm(rawSignatureAlgorithm: string) { - const input = rawSignatureAlgorithm.toLowerCase(); - const patterns = [/sha-?1/i, /sha-?224/i, /sha-?256/i, /sha-?384/i, /sha-?512/i]; - - for (const pattern of patterns) { - const match = input.match(pattern); - if (match) { - // Remove any hyphens and return standardized format - return match[0].replace('-', ''); - } - } - - return 'unknown'; -} - -export function getCertificateFromPem(pemContent: string): Certificate { - const pemFormatted = pemContent.replace(/(-----(BEGIN|END) CERTIFICATE-----|\n|\r)/g, ''); - const binary = Buffer.from(pemFormatted, 'base64'); - const arrayBuffer = new ArrayBuffer(binary.length); - const view = new Uint8Array(arrayBuffer); - for (let i = 0; i < binary.length; i++) { - view[i] = binary[i]; - } - - const asn1 = asn1js.fromBER(arrayBuffer); - if (asn1.offset === -1) { - throw new Error(`ASN.1 parsing error: ${asn1.result.error}`); - } - - return new Certificate({ schema: asn1.result }); -} - export function getTBSBytesForge(certificate: Certificate): number[] { return Array.from(certificate.tbsView.map((byte) => parseInt(byte.toString(16), 16))); } + +export function parseCertificateSimple(pem: string): CertificateData { + const certificateData: CertificateData = { + id: '', + issuer: '', + validity: { + notBefore: '', + notAfter: '', + }, + subjectKeyIdentifier: '', + authorityKeyIdentifier: '', + signatureAlgorithm: '', + hashAlgorithm: '', + publicKeyDetails: undefined, + tbsBytes: undefined, + tbsBytesLength: '', + rawPem: '', + rawTxt: '', + publicKeyAlgoOID: '', + }; + try { + const cert = getCertificateFromPem(pem); + certificateData.tbsBytes = getTBSBytesForge(cert); + certificateData.tbsBytesLength = certificateData.tbsBytes.length.toString(); + + const publicKeyAlgoOID = cert.subjectPublicKeyInfo.algorithm.algorithmId; + const publicKeyAlgoFN = getFriendlyName(publicKeyAlgoOID); + const signatureAlgoOID = cert.signatureAlgorithm.algorithmId; + const signatureAlgoFN = getFriendlyName(signatureAlgoOID); + certificateData.hashAlgorithm = getHashAlgorithm(signatureAlgoFN); + certificateData.publicKeyAlgoOID = publicKeyAlgoOID; + let params; + if (publicKeyAlgoFN === 'RSA' && signatureAlgoFN != 'RSASSA_PSS') { + certificateData.signatureAlgorithm = 'rsa'; + params = getParamsRSA(cert); + } else if (publicKeyAlgoFN === 'ECC') { + certificateData.signatureAlgorithm = 'ecdsa'; + params = getParamsECDSA(cert); + } else if (publicKeyAlgoFN === 'RSASSA_PSS' || signatureAlgoFN === 'RSASSA_PSS') { + certificateData.signatureAlgorithm = 'rsapss'; + params = getParamsRSAPSS(cert); + } else { + console.log(publicKeyAlgoFN); + } + certificateData.publicKeyDetails = params; + certificateData.issuer = getIssuerCountryCode(cert); + certificateData.validity = { + notBefore: cert.notBefore.value.toString(), + notAfter: cert.notAfter.value.toString(), + }; + const ski = getSubjectKeyIdentifier(cert); + certificateData.id = ski.slice(0, 12); + certificateData.subjectKeyIdentifier = ski; + certificateData.rawPem = pem; + + const authorityKeyIdentifier = getAuthorityKeyIdentifier(cert); + certificateData.authorityKeyIdentifier = authorityKeyIdentifier; + + // corner case for rsapss + if ( + certificateData.signatureAlgorithm === 'rsapss' && + (!certificateData.hashAlgorithm || certificateData.hashAlgorithm === 'unknown') + ) { + certificateData.hashAlgorithm = ( + certificateData.publicKeyDetails as PublicKeyDetailsRSAPSS + ).hashAlgorithm; + } + + return certificateData; + } catch (error) { + console.error(`Error processing certificate`, error); + throw error; + } +} diff --git a/common/src/utils/certificate_parsing/parseNode.ts b/common/src/utils/certificate_parsing/parseNode.ts index d78aa40ba..435bec89f 100644 --- a/common/src/utils/certificate_parsing/parseNode.ts +++ b/common/src/utils/certificate_parsing/parseNode.ts @@ -1,2 +1,2 @@ -export { parseCertificate } from './parseCertificate.js'; export { addOpenSslInfo } from './parseCertificateNode.js'; +export { parseCertificate } from './parseCertificate.js'; diff --git a/common/src/utils/certificate_parsing/utils.ts b/common/src/utils/certificate_parsing/utils.ts index 76df40586..40dc2e166 100644 --- a/common/src/utils/certificate_parsing/utils.ts +++ b/common/src/utils/certificate_parsing/utils.ts @@ -1,24 +1,6 @@ import * as asn1js from 'asn1js'; -import { Certificate } from 'pkijs'; import { sha256 } from 'js-sha256'; - -export const getSubjectKeyIdentifier = (cert: Certificate): string => { - const subjectKeyIdentifier = cert.extensions.find((ext) => ext.extnID === '2.5.29.14'); - if (subjectKeyIdentifier) { - let skiValue = Buffer.from(subjectKeyIdentifier.extnValue.valueBlock.valueHexView).toString( - 'hex' - ); - - skiValue = skiValue.replace(/^(?:30(?:16|1E|22|32|42))?(?:04(?:08|14|1C|20|30|40))?/, ''); - return skiValue; - } else { - // console.log('\x1b[31m%s\x1b[0m', 'no subject key identifier found'); // it's no big deal if this is not found - // do a sha1 of the certificate tbs - const hash = sha256.create(); - hash.update(cert.tbsView); - return hash.hex(); - } -}; +import type { Certificate } from 'pkijs'; export const getAuthorityKeyIdentifier = (cert: Certificate): string => { const authorityKeyIdentifierExt = cert.extensions.find((ext) => ext.extnID === '2.5.29.35'); @@ -54,3 +36,21 @@ export function getIssuerCountryCode(cert: Certificate): string { } return issuerCountryCode.toUpperCase(); } + +export const getSubjectKeyIdentifier = (cert: Certificate): string => { + const subjectKeyIdentifier = cert.extensions.find((ext) => ext.extnID === '2.5.29.14'); + if (subjectKeyIdentifier) { + let skiValue = Buffer.from(subjectKeyIdentifier.extnValue.valueBlock.valueHexView).toString( + 'hex' + ); + + skiValue = skiValue.replace(/^(?:30(?:16|1E|22|32|42))?(?:04(?:08|14|1C|20|30|40))?/, ''); + return skiValue; + } else { + // console.log('\x1b[31m%s\x1b[0m', 'no subject key identifier found'); // it's no big deal if this is not found + // do a sha1 of the certificate tbs + const hash = sha256.create(); + hash.update(cert.tbsView); + return hash.hex(); + } +}; diff --git a/common/src/utils/circuits/circuitsName.ts b/common/src/utils/circuits/circuitsName.ts index 5a8ce6161..0da8cdeb0 100644 --- a/common/src/utils/circuits/circuitsName.ts +++ b/common/src/utils/circuits/circuitsName.ts @@ -1,4 +1,4 @@ -import { PassportData } from '../types.js'; +import type { PassportData } from '../types.js'; export function getCircuitNameFromPassportData( passportData: PassportData, diff --git a/common/src/utils/circuits/formatOutputs.ts b/common/src/utils/circuits/formatOutputs.ts index 2074729b2..278385442 100644 --- a/common/src/utils/circuits/formatOutputs.ts +++ b/common/src/utils/circuits/formatOutputs.ts @@ -1,22 +1,24 @@ import { attributeToPosition, attributeToPosition_ID } from '../../constants/constants.js'; -import { SelfAppDisclosureConfig } from '../appType.js'; +import type { SelfAppDisclosureConfig } from '../appType.js'; -/*** OpenPassport Attestation ***/ -export function formatForbiddenCountriesListFromCircuitOutput( - forbiddenCountriesList: string +export function formatAndUnpackForbiddenCountriesList( + forbiddenCountriesList_packed: string[] ): string[] { - const countryList1 = unpackReveal(forbiddenCountriesList, 'id'); - // dump every '\x00' value from the list - const cleanedCountryList = countryList1.filter((value) => value !== '\x00'); - // Concatenate every 3 elements to form country codes - const formattedCountryList = []; - for (let i = 0; i < cleanedCountryList.length; i += 3) { - const countryCode = cleanedCountryList.slice(i, i + 3).join(''); + const forbiddenCountriesList_packed_formatted = [ + forbiddenCountriesList_packed['forbidden_countries_list_packed[0]'], + forbiddenCountriesList_packed['forbidden_countries_list_packed[1]'], + forbiddenCountriesList_packed['forbidden_countries_list_packed[2]'], + forbiddenCountriesList_packed['forbidden_countries_list_packed[3]'], + ]; + const trimmed = trimu0000(unpackReveal(forbiddenCountriesList_packed_formatted, 'id')); + const countries: string[] = []; + for (let i = 0; i < trimmed.length; i += 3) { + const countryCode = trimmed.slice(i, i + 3).join(''); if (countryCode.length === 3) { - formattedCountryList.push(countryCode); + countries.push(countryCode); } } - return formattedCountryList; + return countries; // Return countries array instead of trimmed } /*** Disclose circuits ***/ @@ -25,51 +27,6 @@ function trimu0000(unpackedReveal: string[]): string[] { return unpackedReveal.filter((value) => value !== '\u0000'); } -export function getAttributeFromUnpackedReveal( - unpackedReveal: string[], - attribute: string, - id_type: 'passport' | 'id' -) { - const position = - id_type === 'passport' ? attributeToPosition[attribute] : attributeToPosition_ID[attribute]; - let attributeValue = ''; - for (let i = position[0]; i <= position[1]; i++) { - if (unpackedReveal[i] !== '\u0000') { - attributeValue += unpackedReveal[i]; - } - } - return attributeValue; -} - -export function unpackReveal( - revealedData_packed: string | string[], - id_type: 'passport' | 'id' -): string[] { - // If revealedData_packed is not an array, convert it to an array - const packedArray = Array.isArray(revealedData_packed) - ? revealedData_packed - : [revealedData_packed]; - - const bytesCount = id_type === 'passport' ? [31, 31, 31] : [31, 31, 31, 31]; // nb of bytes in each of the first three field elements - const bytesArray = packedArray.flatMap((element: string, index: number) => { - const bytes = bytesCount[index] || 31; // Use 31 as default if index is out of range - const elementBigInt = BigInt(element); - const byteMask = BigInt(255); // 0xFF - const bytesOfElement = [...Array(bytes)].map((_, byteIndex) => { - return (elementBigInt >> (BigInt(byteIndex) * BigInt(8))) & byteMask; - }); - return bytesOfElement; - }); - - return bytesArray.map((byte: bigint) => String.fromCharCode(Number(byte))); -} - -export function getOlderThanFromCircuitOutput(olderThan: string[]): number { - const ageString = olderThan.map((code) => String.fromCharCode(parseInt(code))).join(''); - const age = parseInt(ageString, 10); - return isNaN(age) ? 0 : age; -} - export function formatAndUnpackReveal( revealedData_packed: string[], id_type: 'passport' | 'id' @@ -93,38 +50,46 @@ export function formatAndUnpackReveal( ); } -export function formatAndUnpackForbiddenCountriesList( - forbiddenCountriesList_packed: string[] +/*** OpenPassport Attestation ***/ +export function formatForbiddenCountriesListFromCircuitOutput( + forbiddenCountriesList: string ): string[] { - const forbiddenCountriesList_packed_formatted = [ - forbiddenCountriesList_packed['forbidden_countries_list_packed[0]'], - forbiddenCountriesList_packed['forbidden_countries_list_packed[1]'], - forbiddenCountriesList_packed['forbidden_countries_list_packed[2]'], - forbiddenCountriesList_packed['forbidden_countries_list_packed[3]'], - ]; - const trimmed = trimu0000(unpackReveal(forbiddenCountriesList_packed_formatted, 'id')); - const countries: string[] = []; - for (let i = 0; i < trimmed.length; i += 3) { - const countryCode = trimmed.slice(i, i + 3).join(''); + const countryList1 = unpackReveal(forbiddenCountriesList, 'id'); + // dump every '\x00' value from the list + const cleanedCountryList = countryList1.filter((value) => value !== '\x00'); + // Concatenate every 3 elements to form country codes + const formattedCountryList = []; + for (let i = 0; i < cleanedCountryList.length; i += 3) { + const countryCode = cleanedCountryList.slice(i, i + 3).join(''); if (countryCode.length === 3) { - countries.push(countryCode); + formattedCountryList.push(countryCode); } } - return countries; // Return countries array instead of trimmed + return formattedCountryList; } -export function revealBitmapFromMapping(attributeToReveal: { [key: string]: string }): string[] { - const reveal_bitmap = Array(90).fill('0'); - - Object.entries(attributeToReveal).forEach(([attribute, reveal]) => { - if (reveal !== '') { - const [start, end] = attributeToPosition[attribute as keyof typeof attributeToPosition]; - reveal_bitmap.fill('1', start, end + 1); +export function getAttributeFromUnpackedReveal( + unpackedReveal: string[], + attribute: string, + id_type: 'passport' | 'id' +) { + const position = + id_type === 'passport' ? attributeToPosition[attribute] : attributeToPosition_ID[attribute]; + let attributeValue = ''; + for (let i = position[0]; i <= position[1]; i++) { + if (unpackedReveal[i] !== '\u0000') { + attributeValue += unpackedReveal[i]; } - }); - - return reveal_bitmap; + } + return attributeValue; } + +export function getOlderThanFromCircuitOutput(olderThan: string[]): number { + const ageString = olderThan.map((code) => String.fromCharCode(parseInt(code))).join(''); + const age = parseInt(ageString, 10); + return isNaN(age) ? 0 : age; +} + export function revealBitmapFromAttributes( disclosureOptions: SelfAppDisclosureConfig, id_type: 'passport' | 'id' @@ -140,3 +105,38 @@ export function revealBitmapFromAttributes( return reveal_bitmap; } + +export function revealBitmapFromMapping(attributeToReveal: { [key: string]: string }): string[] { + const reveal_bitmap = Array(90).fill('0'); + + Object.entries(attributeToReveal).forEach(([attribute, reveal]) => { + if (reveal !== '') { + const [start, end] = attributeToPosition[attribute as keyof typeof attributeToPosition]; + reveal_bitmap.fill('1', start, end + 1); + } + }); + + return reveal_bitmap; +} +export function unpackReveal( + revealedData_packed: string | string[], + id_type: 'passport' | 'id' +): string[] { + // If revealedData_packed is not an array, convert it to an array + const packedArray = Array.isArray(revealedData_packed) + ? revealedData_packed + : [revealedData_packed]; + + const bytesCount = id_type === 'passport' ? [31, 31, 31] : [31, 31, 31, 31]; // nb of bytes in each of the first three field elements + const bytesArray = packedArray.flatMap((element: string, index: number) => { + const bytes = bytesCount[index] || 31; // Use 31 as default if index is out of range + const elementBigInt = BigInt(element); + const byteMask = BigInt(255); // 0xFF + const bytesOfElement = [...Array(bytes)].map((_, byteIndex) => { + return (elementBigInt >> (BigInt(byteIndex) * BigInt(8))) & byteMask; + }); + return bytesOfElement; + }); + + return bytesArray.map((byte: bigint) => String.fromCharCode(Number(byte))); +} diff --git a/common/src/utils/circuits/generateInputs.ts b/common/src/utils/circuits/generateInputs.ts index 4f862b175..0c863db03 100644 --- a/common/src/utils/circuits/generateInputs.ts +++ b/common/src/utils/circuits/generateInputs.ts @@ -1,13 +1,11 @@ import { + COMMITMENT_TREE_DEPTH, + max_csca_bytes, + max_dsc_bytes, MAX_PADDED_ECONTENT_LEN, MAX_PADDED_SIGNED_ATTR_LEN, - max_csca_bytes, - COMMITMENT_TREE_DEPTH, OFAC_TREE_LEVELS, } from '../../constants/constants.js'; -import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; -import { SMT } from '@openpassport/zk-kit-smt'; -import { max_dsc_bytes } from '../../constants/constants.js'; import { getCurrentDateYYMMDD } from '../date.js'; import { hash, packBytesAndPoseidon } from '../hash.js'; import { formatMrz } from '../passports/format.js'; @@ -33,10 +31,69 @@ import { getNameYobLeaf, getPassportNumberAndNationalityLeaf, } from '../trees.js'; -import { PassportData } from '../types.js'; +import type { PassportData } from '../types.js'; import { formatCountriesList } from './formatInputs.js'; import { stringToAsciiBigIntArray } from './uuid.js'; +import type { LeanIMT } from '@openpassport/zk-kit-lean-imt'; +import type { SMT } from '@openpassport/zk-kit-smt'; + +// this get the commitment index whether it is a string or a bigint +// this is necessary rn because when the tree is send from the server in a serialized form, +// the bigints are converted to strings and I can't figure out how to use tree.import to load bigints there +export function findIndexInTree(tree: LeanIMT, commitment: bigint): number { + let index = tree.indexOf(commitment); + if (index === -1) { + index = tree.indexOf(commitment.toString() as unknown as bigint); + } + if (index === -1) { + throw new Error('This commitment was not found in the tree'); + } else { + // console.log(`Index of commitment in the registry: ${index}`); + } + return index; +} + +export function formatInput(input: any) { + if (Array.isArray(input)) { + return input.map((item) => BigInt(item).toString()); + } else if (input instanceof Uint8Array) { + return Array.from(input).map((num) => BigInt(num).toString()); + } else if (typeof input === 'string' && input.includes(',')) { + const numbers = input + .split(',') + .map((s) => s.trim()) + .filter((s) => s !== '' && !isNaN(Number(s))) + .map(Number); + + try { + return numbers.map((num) => BigInt(num).toString()); + } catch (e) { + throw e; + } + } else { + return [BigInt(input).toString()]; + } +} + +export function generateCircuitInputsCountryVerifier( + passportData: PassportData, + sparsemerkletree: SMT +) { + const mrz_bytes = formatMrz(passportData.mrz); + const usa_ascii = stringToAsciiBigIntArray('USA'); + const country_leaf = getCountryLeaf(usa_ascii, mrz_bytes.slice(7, 10)); + const { root, closestleaf, siblings } = generateSMTProof(sparsemerkletree, country_leaf); + + return { + dg1: formatInput(mrz_bytes), + hostCountry: formatInput(usa_ascii), + smt_leaf_key: formatInput(closestleaf), + smt_root: formatInput(root), + smt_siblings: formatInput(siblings), + }; +} + export function generateCircuitInputsDSC( passportData: PassportData, serializedCscaTree: string[][] @@ -91,6 +148,58 @@ export function generateCircuitInputsDSC( }; } +export function generateCircuitInputsOfac( + passportData: PassportData, + sparsemerkletree: SMT, + proofLevel: number +) { + const { mrz, documentType } = passportData; + const isPassportType = documentType === 'passport' || documentType === 'mock_passport'; + + const mrz_bytes = formatMrz(mrz); // Assume formatMrz handles basic formatting + const nameSlice = isPassportType + ? mrz_bytes.slice(5 + 5, 44 + 5) + : mrz_bytes.slice(60 + 5, 90 + 5); + const dobSlice = isPassportType + ? mrz_bytes.slice(57 + 5, 63 + 5) + : mrz_bytes.slice(30 + 5, 36 + 5); + const yobSlice = isPassportType + ? mrz_bytes.slice(57 + 5, 59 + 5) + : mrz_bytes.slice(30 + 5, 32 + 5); + const nationalitySlice = isPassportType + ? mrz_bytes.slice(54 + 5, 57 + 5) + : mrz_bytes.slice(45 + 5, 48 + 5); + const passNoSlice = isPassportType + ? mrz_bytes.slice(44 + 5, 53 + 5) + : mrz_bytes.slice(5 + 5, 14 + 5); + + let leafToProve: bigint; + + if (proofLevel == 3) { + if (!isPassportType) { + throw new Error( + 'Proof level 3 (Passport Number) is only applicable to passport document types.' + ); + } + leafToProve = getPassportNumberAndNationalityLeaf(passNoSlice, nationalitySlice); + } else if (proofLevel == 2) { + leafToProve = getNameDobLeaf(nameSlice, dobSlice); + } else if (proofLevel == 1) { + leafToProve = getNameYobLeaf(nameSlice, yobSlice); + } else { + throw new Error('Invalid proof level specified for OFAC check.'); + } + + const { root, closestleaf, siblings } = generateSMTProof(sparsemerkletree, leafToProve); + + return { + dg1: formatInput(mrz_bytes), + smt_leaf_key: formatInput(closestleaf), + smt_root: formatInput(root), + smt_siblings: formatInput(siblings), + }; +} + export function generateCircuitInputsRegister( secret: string, passportData: PassportData, @@ -288,111 +397,3 @@ export function generateCircuitInputsVCandDisclose( return finalInputs; } - -export function generateCircuitInputsOfac( - passportData: PassportData, - sparsemerkletree: SMT, - proofLevel: number -) { - const { mrz, documentType } = passportData; - const isPassportType = documentType === 'passport' || documentType === 'mock_passport'; - - const mrz_bytes = formatMrz(mrz); // Assume formatMrz handles basic formatting - const nameSlice = isPassportType - ? mrz_bytes.slice(5 + 5, 44 + 5) - : mrz_bytes.slice(60 + 5, 90 + 5); - const dobSlice = isPassportType - ? mrz_bytes.slice(57 + 5, 63 + 5) - : mrz_bytes.slice(30 + 5, 36 + 5); - const yobSlice = isPassportType - ? mrz_bytes.slice(57 + 5, 59 + 5) - : mrz_bytes.slice(30 + 5, 32 + 5); - const nationalitySlice = isPassportType - ? mrz_bytes.slice(54 + 5, 57 + 5) - : mrz_bytes.slice(45 + 5, 48 + 5); - const passNoSlice = isPassportType - ? mrz_bytes.slice(44 + 5, 53 + 5) - : mrz_bytes.slice(5 + 5, 14 + 5); - - let leafToProve: bigint; - - if (proofLevel == 3) { - if (!isPassportType) { - throw new Error( - 'Proof level 3 (Passport Number) is only applicable to passport document types.' - ); - } - leafToProve = getPassportNumberAndNationalityLeaf(passNoSlice, nationalitySlice); - } else if (proofLevel == 2) { - leafToProve = getNameDobLeaf(nameSlice, dobSlice); - } else if (proofLevel == 1) { - leafToProve = getNameYobLeaf(nameSlice, yobSlice); - } else { - throw new Error('Invalid proof level specified for OFAC check.'); - } - - const { root, closestleaf, siblings } = generateSMTProof(sparsemerkletree, leafToProve); - - return { - dg1: formatInput(mrz_bytes), - smt_leaf_key: formatInput(closestleaf), - smt_root: formatInput(root), - smt_siblings: formatInput(siblings), - }; -} - -export function generateCircuitInputsCountryVerifier( - passportData: PassportData, - sparsemerkletree: SMT -) { - const mrz_bytes = formatMrz(passportData.mrz); - const usa_ascii = stringToAsciiBigIntArray('USA'); - const country_leaf = getCountryLeaf(usa_ascii, mrz_bytes.slice(7, 10)); - const { root, closestleaf, siblings } = generateSMTProof(sparsemerkletree, country_leaf); - - return { - dg1: formatInput(mrz_bytes), - hostCountry: formatInput(usa_ascii), - smt_leaf_key: formatInput(closestleaf), - smt_root: formatInput(root), - smt_siblings: formatInput(siblings), - }; -} - -// this get the commitment index whether it is a string or a bigint -// this is necessary rn because when the tree is send from the server in a serialized form, -// the bigints are converted to strings and I can't figure out how to use tree.import to load bigints there -export function findIndexInTree(tree: LeanIMT, commitment: bigint): number { - let index = tree.indexOf(commitment); - if (index === -1) { - index = tree.indexOf(commitment.toString() as unknown as bigint); - } - if (index === -1) { - throw new Error('This commitment was not found in the tree'); - } else { - // console.log(`Index of commitment in the registry: ${index}`); - } - return index; -} - -export function formatInput(input: any) { - if (Array.isArray(input)) { - return input.map((item) => BigInt(item).toString()); - } else if (input instanceof Uint8Array) { - return Array.from(input).map((num) => BigInt(num).toString()); - } else if (typeof input === 'string' && input.includes(',')) { - const numbers = input - .split(',') - .map((s) => s.trim()) - .filter((s) => s !== '' && !isNaN(Number(s))) - .map(Number); - - try { - return numbers.map((num) => BigInt(num).toString()); - } catch (e) { - throw e; - } - } else { - return [BigInt(input).toString()]; - } -} diff --git a/common/src/utils/circuits/index.ts b/common/src/utils/circuits/index.ts index 7e92081eb..60e39e27f 100644 --- a/common/src/utils/circuits/index.ts +++ b/common/src/utils/circuits/index.ts @@ -1,36 +1,31 @@ +export type { UserIdType } from './uuid.js'; export { - generateCircuitInputsDSC, - generateCircuitInputsRegister, - generateCircuitInputsVCandDisclose, - generateCircuitInputsOfac, -} from './generateInputs.js'; - -export { getCircuitNameFromPassportData } from './circuitsName.js'; - -export { - formatForbiddenCountriesListFromCircuitOutput, - getAttributeFromUnpackedReveal, - unpackReveal, - getOlderThanFromCircuitOutput, - formatAndUnpackReveal, - formatAndUnpackForbiddenCountriesList, - revealBitmapFromMapping, - revealBitmapFromAttributes, -} from './formatOutputs.js'; - -export { formatCountriesList, reverseBytes, reverseCountryBytes } from './formatInputs.js'; - -export { - castFromUUID, bigIntToHex, - hexToUUID, + castFromScope, + castFromUUID, + castToAddress, + castToScope, castToUUID, castToUserIdentifier, - castToAddress, - castFromScope, - castToScope, + hexToUUID, stringToAsciiBigIntArray, validateUserId, } from './uuid.js'; - -export type { UserIdType } from './uuid.js'; +export { + formatAndUnpackForbiddenCountriesList, + formatAndUnpackReveal, + formatForbiddenCountriesListFromCircuitOutput, + getAttributeFromUnpackedReveal, + getOlderThanFromCircuitOutput, + revealBitmapFromAttributes, + revealBitmapFromMapping, + unpackReveal, +} from './formatOutputs.js'; +export { formatCountriesList, reverseBytes, reverseCountryBytes } from './formatInputs.js'; +export { + generateCircuitInputsDSC, + generateCircuitInputsOfac, + generateCircuitInputsRegister, + generateCircuitInputsVCandDisclose, +} from './generateInputs.js'; +export { getCircuitNameFromPassportData } from './circuitsName.js'; diff --git a/common/src/utils/circuits/uuid.ts b/common/src/utils/circuits/uuid.ts index c6c714cab..79b6cff2a 100644 --- a/common/src/utils/circuits/uuid.ts +++ b/common/src/utils/circuits/uuid.ts @@ -17,18 +17,29 @@ function uuidToBigInt(uuid: string): bigint { return bigInt; } +export function bigIntToHex(bigInt: bigint): string { + return bigInt.toString(16).padStart(32, '0'); +} + +export function castFromScope(scope: string): string { + checkStringLength(scope); + return stringToBigInt(scope).toString(); +} + export function castFromUUID(uuid: string): string { const bigInt = uuidToBigInt(uuid); checkBigInt(bigInt); return bigInt.toString(); } -export function bigIntToHex(bigInt: bigint): string { - return bigInt.toString(16).padStart(32, '0'); +export function castToAddress(bigInt: bigint): string { + return `0x${bigInt.toString(16).padStart(40, '0')}`; } -export function hexToUUID(hex: string): string { - return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +export function castToScope(num: bigint): string { + const str = num.toString().slice(1); // Remove leading '1' + const charCodes = str.match(/.{1,3}/g) || []; + return String.fromCharCode(...charCodes.map((code) => parseInt(code, 10))); } export function castToUUID(bigInt: bigint): string { @@ -36,19 +47,6 @@ export function castToUUID(bigInt: bigint): string { return hexToUUID(hex); } -export function castToUserIdentifier(bigInt: bigint, user_identifier_type: UserIdType): string { - switch (user_identifier_type) { - case 'hex': - return castToAddress(bigInt); - case 'uuid': - return castToUUID(bigInt); - } -} - -export function castToAddress(bigInt: bigint): string { - return `0x${bigInt.toString(16).padStart(40, '0')}`; -} - /// scope function checkStringLength(str: string) { if (str.length > 25) { @@ -65,19 +63,21 @@ function stringToBigInt(str: string): bigint { ); } -export function castFromScope(scope: string): string { - checkStringLength(scope); - return stringToBigInt(scope).toString(); +export function castToUserIdentifier(bigInt: bigint, user_identifier_type: UserIdType): string { + switch (user_identifier_type) { + case 'hex': + return castToAddress(bigInt); + case 'uuid': + return castToUUID(bigInt); + } } -export function castToScope(num: bigint): string { - const str = num.toString().slice(1); // Remove leading '1' - const charCodes = str.match(/.{1,3}/g) || []; - return String.fromCharCode(...charCodes.map((code) => parseInt(code, 10))); +export function hexToUUID(hex: string): string { + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; } export function stringToAsciiBigIntArray(str: string): bigint[] { - let asciiBigIntArray = []; + const asciiBigIntArray = []; for (let i = 0; i < str.length; i++) { asciiBigIntArray.push(BigInt(str.charCodeAt(i))); } diff --git a/common/src/utils/contracts/forbiddenCountries.ts b/common/src/utils/contracts/forbiddenCountries.ts index a3683afc1..c6b9f2854 100644 --- a/common/src/utils/contracts/forbiddenCountries.ts +++ b/common/src/utils/contracts/forbiddenCountries.ts @@ -1,5 +1,5 @@ -import { Country3LetterCode } from '../../constants/countries.js'; import { MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH } from '../../constants/constants.js'; +import type { Country3LetterCode } from '../../constants/countries.js'; export function getPackedForbiddenCountries( forbiddenCountriesList: Array diff --git a/common/src/utils/contracts/formatCallData.ts b/common/src/utils/contracts/formatCallData.ts index f8df9b08b..a4a41ee31 100644 --- a/common/src/utils/contracts/formatCallData.ts +++ b/common/src/utils/contracts/formatCallData.ts @@ -1,24 +1,3 @@ -export function formatCallData_register(parsedCallData: any[]) { - return { - blinded_dsc_commitment: parsedCallData[3][0], - nullifier: parsedCallData[3][1], - commitment: parsedCallData[3][2], - attestation_id: parsedCallData[3][3], - a: parsedCallData[0], - b: [parsedCallData[1][0], parsedCallData[1][1]], - c: parsedCallData[2], - }; -} -export function formatCallData_dsc(parsedCallData: any[]) { - return { - blinded_dsc_commitment: parsedCallData[3][0], - merkle_root: parsedCallData[3][1], - a: parsedCallData[0], - b: [parsedCallData[1][0], parsedCallData[1][1]], - c: parsedCallData[2], - }; -} - export function formatCallData_disclose(parsedCallData: any[]) { return { nullifier: parsedCallData[3][0], @@ -40,6 +19,39 @@ export function formatCallData_disclose(parsedCallData: any[]) { c: parsedCallData[2], }; } +export function formatCallData_dsc(parsedCallData: any[]) { + return { + blinded_dsc_commitment: parsedCallData[3][0], + merkle_root: parsedCallData[3][1], + a: parsedCallData[0], + b: [parsedCallData[1][0], parsedCallData[1][1]], + c: parsedCallData[2], + }; +} + +export function formatCallData_register(parsedCallData: any[]) { + return { + blinded_dsc_commitment: parsedCallData[3][0], + nullifier: parsedCallData[3][1], + commitment: parsedCallData[3][2], + attestation_id: parsedCallData[3][3], + a: parsedCallData[0], + b: [parsedCallData[1][0], parsedCallData[1][1]], + c: parsedCallData[2], + }; +} + +export function formatProof(proof: any, publicSignals: any) { + return { + a: proof.a, + b: [ + [proof.b[0][1], proof.b[0][0]], + [proof.b[1][1], proof.b[1][0]], + ], + c: proof.c, + pubSignals: publicSignals, + }; +} export function packForbiddenCountriesList(forbiddenCountries: string[]) { const MAX_BYTES_IN_FIELD = 31; @@ -88,15 +100,3 @@ export function packForbiddenCountriesList(forbiddenCountries: string[]) { return output; } - -export function formatProof(proof: any, publicSignals: any) { - return { - a: proof.a, - b: [ - [proof.b[0][1], proof.b[0][0]], - [proof.b[1][1], proof.b[1][0]], - ], - c: proof.c, - pubSignals: publicSignals, - }; -} diff --git a/common/src/utils/contracts/index.ts b/common/src/utils/contracts/index.ts index 1bc4c638d..fb0787d9e 100644 --- a/common/src/utils/contracts/index.ts +++ b/common/src/utils/contracts/index.ts @@ -1,9 +1,8 @@ export { - formatCallData_register, - formatCallData_dsc, formatCallData_disclose, - packForbiddenCountriesList, + formatCallData_dsc, + formatCallData_register, formatProof, + packForbiddenCountriesList, } from './formatCallData.js'; - export { getPackedForbiddenCountries } from './forbiddenCountries.js'; diff --git a/common/src/utils/csca.ts b/common/src/utils/csca.ts index 06c13bd2d..695017963 100644 --- a/common/src/utils/csca.ts +++ b/common/src/utils/csca.ts @@ -1,58 +1,6 @@ import { API_URL, API_URL_STAGING } from '../constants/constants.js'; import { SKI_PEM, SKI_PEM_DEV } from '../constants/skiPem.js'; -export function findStartIndexEC(point: string, messagePadded: number[]): [number, number] { - const pointNumArray = []; - for (let i = 0; i < point.length; i += 2) { - pointNumArray.push(parseInt(point.slice(i, i + 2), 16)); - } - - let startIndex = -1; - - for (let i = 0; i < messagePadded.length - pointNumArray.length + 1; i++) { - const isMatch = pointNumArray.every((byte, j) => messagePadded[i + j] === byte); - if (isMatch) { - startIndex = i; - break; - } - } - - if (startIndex === -1) { - throw new Error('DSC Pubkey not found in CSCA certificate'); - } - return [startIndex, pointNumArray.length]; -} - -// @returns [startIndex, length] where startIndex is the index of the first byte of the modulus in the message and length is the length of the modulus in bytes -export function findStartIndex(modulus: string, messagePaddedNumber: number[]): [number, number] { - const modulusNumArray = []; - for (let i = 0; i < modulus.length; i += 2) { - const hexPair = modulus.slice(i, i + 2); - const number = parseInt(hexPair, 16); - modulusNumArray.push(number); - } - - // console.log('Modulus length:', modulusNumArray.length); - // console.log('Message length:', messagePaddedNumber.length); - // console.log('Modulus (hex):', modulusNumArray.map(n => n.toString(16).padStart(2, '0')).join('')); - // console.log('Message (hex):', messagePaddedNumber.map(n => n.toString(16).padStart(2, '0')).join('')); - - for (let i = 0; i < messagePaddedNumber.length - modulusNumArray.length + 1; i++) { - let matched = true; - for (let j = 0; j < modulusNumArray.length; j++) { - if (modulusNumArray[j] !== messagePaddedNumber[i + j]) { - matched = false; - break; - } - } - if (matched) { - return [i, modulusNumArray.length]; - } - } - - throw new Error('DSC Pubkey not found in certificate'); -} - export function findOIDPosition( oid: string, message: number[] @@ -66,7 +14,7 @@ export function findOIDPosition( // Convert remaining parts to ASN.1 DER encoding for (let i = 2; i < oidParts.length; i++) { let value = oidParts[i]; - let bytes = []; + const bytes = []; // Handle multi-byte values if (value >= 128) { @@ -121,6 +69,58 @@ export function findOIDPosition( throw new Error('OID not found in message'); } +// @returns [startIndex, length] where startIndex is the index of the first byte of the modulus in the message and length is the length of the modulus in bytes +export function findStartIndex(modulus: string, messagePaddedNumber: number[]): [number, number] { + const modulusNumArray = []; + for (let i = 0; i < modulus.length; i += 2) { + const hexPair = modulus.slice(i, i + 2); + const number = parseInt(hexPair, 16); + modulusNumArray.push(number); + } + + // console.log('Modulus length:', modulusNumArray.length); + // console.log('Message length:', messagePaddedNumber.length); + // console.log('Modulus (hex):', modulusNumArray.map(n => n.toString(16).padStart(2, '0')).join('')); + // console.log('Message (hex):', messagePaddedNumber.map(n => n.toString(16).padStart(2, '0')).join('')); + + for (let i = 0; i < messagePaddedNumber.length - modulusNumArray.length + 1; i++) { + let matched = true; + for (let j = 0; j < modulusNumArray.length; j++) { + if (modulusNumArray[j] !== messagePaddedNumber[i + j]) { + matched = false; + break; + } + } + if (matched) { + return [i, modulusNumArray.length]; + } + } + + throw new Error('DSC Pubkey not found in certificate'); +} + +export function findStartIndexEC(point: string, messagePadded: number[]): [number, number] { + const pointNumArray = []; + for (let i = 0; i < point.length; i += 2) { + pointNumArray.push(parseInt(point.slice(i, i + 2), 16)); + } + + let startIndex = -1; + + for (let i = 0; i < messagePadded.length - pointNumArray.length + 1; i++) { + const isMatch = pointNumArray.every((byte, j) => messagePadded[i + j] === byte); + if (isMatch) { + startIndex = i; + break; + } + } + + if (startIndex === -1) { + throw new Error('DSC Pubkey not found in CSCA certificate'); + } + return [startIndex, pointNumArray.length]; +} + export function getCSCAFromSKI(ski: string, skiPem: any = null): string { const normalizedSki = ski.replace(/\s+/g, '').toLowerCase(); const isSkiProvided = skiPem !== null; diff --git a/common/src/utils/date.ts b/common/src/utils/date.ts index b0ff0ec2f..6cca72a20 100644 --- a/common/src/utils/date.ts +++ b/common/src/utils/date.ts @@ -1,6 +1,6 @@ export function getAdjustedTimestampBytes(y: number = 0, m: number = 0, d: number = 0): number[] { // Get the current date/time - let currentDate: Date = new Date(); + const currentDate: Date = new Date(); // Optionally adjust the date if (y !== 0) currentDate.setFullYear(currentDate.getFullYear() + y); @@ -20,6 +20,20 @@ export function getAdjustedTimestampBytes(y: number = 0, m: number = 0, d: numbe return bytes; } +export function getCurrentDateYYMMDD(dayDiff: number = 0): number[] { + const date = new Date(); + date.setDate(date.getDate() + dayDiff); // Adjust the date by the dayDiff + const year = date.getUTCFullYear(); + const month = date.getUTCMonth() + 1; + const day = date.getUTCDate(); + const YY = `0${year % 100}`.slice(-2); + const MM = `0${month}`.slice(-2); + const DD = `0${day}`.slice(-2); + + const yymmdd = `${YY}${MM}${DD}`; + return Array.from(yymmdd).map((char) => parseInt(char)); +} + export function getTimestampBytesFromYearFraction(yearFraction: number): number[] { // Separate the year and the fractional part const year = Math.floor(yearFraction); @@ -87,17 +101,3 @@ export function yymmddToByteArray(yymmdd: string): number[] { const byteArray = Array.from(yymmdd).map((char) => char.charCodeAt(0)); return byteArray; } - -export function getCurrentDateYYMMDD(dayDiff: number = 0): number[] { - const date = new Date(); - date.setDate(date.getDate() + dayDiff); // Adjust the date by the dayDiff - const year = date.getUTCFullYear(); - const month = date.getUTCMonth() + 1; - const day = date.getUTCDate(); - const YY = `0${year % 100}`.slice(-2); - const MM = `0${month}`.slice(-2); - const DD = `0${day}`.slice(-2); - - const yymmdd = `${YY}${MM}${DD}`; - return Array.from(yymmdd).map((char) => parseInt(char)); -} diff --git a/common/src/utils/hash.ts b/common/src/utils/hash.ts index 362c66d21..07f2cd8e1 100644 --- a/common/src/utils/hash.ts +++ b/common/src/utils/hash.ts @@ -1,3 +1,9 @@ +import { ethers } from 'ethers'; +// @ts-ignore - ESLint incorrectly flags this as needing default import, but TypeScript definitions use named export +import { sha1 } from 'js-sha1'; +import { sha224, sha256 } from 'js-sha256'; +import { sha384, sha512 } from 'js-sha512'; +import * as forge from 'node-forge'; import { poseidon1, poseidon2, @@ -16,13 +22,49 @@ import { poseidon15, poseidon16, } from 'poseidon-lite'; -import { sha224, sha256 } from 'js-sha256'; -// @ts-ignore - ESLint incorrectly flags this as needing default import, but TypeScript definitions use named export -import { sha1 } from 'js-sha1'; -import { sha384, sha512 } from 'js-sha512'; + import { hexToSignedBytes, packBytesArray } from './bytes.js'; -import * as forge from 'node-forge'; -import { ethers } from 'ethers'; + +export function calculateUserIdentifierHash( + destChainID: number, + userID: string, + userDefinedData: string +): BigInt { + const solidityPackedUserContextData = getSolidityPackedUserContextData( + destChainID, + userID, + userDefinedData + ); + const inputBytes = Buffer.from(solidityPackedUserContextData.slice(2), 'hex'); + const sha256Hash = ethers.sha256(inputBytes); + const ripemdHash = ethers.ripemd160(sha256Hash); + return BigInt(ripemdHash); +} + +export function customHasher(pubKeyFormatted: string[]) { + if (pubKeyFormatted.length < 16) { + // if k is less than 16, we can use a single poseidon hash + return flexiblePoseidon(pubKeyFormatted.map(BigInt)).toString(); + } else { + const rounds = Math.ceil(pubKeyFormatted.length / 16); // do up to 16 rounds of poseidon + if (rounds > 16) { + throw new Error('Number of rounds is greater than 16'); + } + const hash = new Array(rounds); + for (let i = 0; i < rounds; i++) { + hash[i] = { inputs: new Array(16).fill(BigInt(0)) }; + } + for (let i = 0; i < rounds; i++) { + for (let j = 0; j < 16; j++) { + if (i * 16 + j < pubKeyFormatted.length) { + hash[i].inputs[j] = BigInt(pubKeyFormatted[i * 16 + j]); + } + } + } + const finalHash = flexiblePoseidon(hash.map((h) => poseidon16(h.inputs))); + return finalHash.toString(); + } +} export function flexiblePoseidon(inputs: bigint[]): bigint { switch (inputs.length) { @@ -63,6 +105,40 @@ export function flexiblePoseidon(inputs: bigint[]): bigint { } } +export function getHashLen(hashFunction: string) { + switch (hashFunction) { + case 'sha1': + return 20; + case 'sha224': + return 28; + case 'sha256': + return 32; + case 'sha384': + return 48; + case 'sha512': + return 64; + default: + console.log(`${hashFunction} not found in getHashLen`); + return 32; + } +} + +export function getSolidityPackedUserContextData( + destChainID: number, + userID: string, + userDefinedData: string +): string { + const userIdHex = userID.replace(/-/g, ''); + return ethers.solidityPacked( + ['bytes32', 'bytes32', 'bytes'], + [ + ethers.zeroPadValue(ethers.toBeHex(destChainID), 32), + ethers.zeroPadValue('0x' + userIdHex, 32), + ethers.toUtf8Bytes(userDefinedData), + ] + ); +} + // hash function - crypto is not supported in react native export function hash( hashFunction: string, @@ -105,82 +181,7 @@ export function hash( throw new Error(`Invalid format: ${format}`); } -export function getHashLen(hashFunction: string) { - switch (hashFunction) { - case 'sha1': - return 20; - case 'sha224': - return 28; - case 'sha256': - return 32; - case 'sha384': - return 48; - case 'sha512': - return 64; - default: - console.log(`${hashFunction} not found in getHashLen`); - return 32; - } -} - -export function customHasher(pubKeyFormatted: string[]) { - if (pubKeyFormatted.length < 16) { - // if k is less than 16, we can use a single poseidon hash - return flexiblePoseidon(pubKeyFormatted.map(BigInt)).toString(); - } else { - const rounds = Math.ceil(pubKeyFormatted.length / 16); // do up to 16 rounds of poseidon - if (rounds > 16) { - throw new Error('Number of rounds is greater than 16'); - } - const hash = new Array(rounds); - for (let i = 0; i < rounds; i++) { - hash[i] = { inputs: new Array(16).fill(BigInt(0)) }; - } - for (let i = 0; i < rounds; i++) { - for (let j = 0; j < 16; j++) { - if (i * 16 + j < pubKeyFormatted.length) { - hash[i].inputs[j] = BigInt(pubKeyFormatted[i * 16 + j]); - } - } - } - const finalHash = flexiblePoseidon(hash.map((h) => poseidon16(h.inputs))); - return finalHash.toString(); - } -} - export function packBytesAndPoseidon(unpacked: number[]) { const packed = packBytesArray(unpacked); return customHasher(packed.map(String)).toString(); } - -export function calculateUserIdentifierHash( - destChainID: number, - userID: string, - userDefinedData: string -): BigInt { - const solidityPackedUserContextData = getSolidityPackedUserContextData( - destChainID, - userID, - userDefinedData - ); - const inputBytes = Buffer.from(solidityPackedUserContextData.slice(2), 'hex'); - const sha256Hash = ethers.sha256(inputBytes); - const ripemdHash = ethers.ripemd160(sha256Hash); - return BigInt(ripemdHash); -} - -export function getSolidityPackedUserContextData( - destChainID: number, - userID: string, - userDefinedData: string -): string { - const userIdHex = userID.replace(/-/g, ''); - return ethers.solidityPacked( - ['bytes32', 'bytes32', 'bytes'], - [ - ethers.zeroPadValue(ethers.toBeHex(destChainID), 32), - ethers.zeroPadValue('0x' + userIdHex, 32), - ethers.toUtf8Bytes(userDefinedData), - ] - ); -} diff --git a/common/src/utils/hash/custom.ts b/common/src/utils/hash/custom.ts index 6108131a1..57a23403e 100644 --- a/common/src/utils/hash/custom.ts +++ b/common/src/utils/hash/custom.ts @@ -1,5 +1,5 @@ export { - customHasher, calculateUserIdentifierHash, + customHasher, getSolidityPackedUserContextData, } from '../hash.js'; diff --git a/common/src/utils/hash/sha.ts b/common/src/utils/hash/sha.ts index 251cbbadd..d2e3cf868 100644 --- a/common/src/utils/hash/sha.ts +++ b/common/src/utils/hash/sha.ts @@ -1 +1 @@ -export { hash, getHashLen } from '../hash.js'; +export { getHashLen, hash } from '../hash.js'; diff --git a/common/src/utils/index.ts b/common/src/utils/index.ts index 413c56323..e114ecfe3 100644 --- a/common/src/utils/index.ts +++ b/common/src/utils/index.ts @@ -1,52 +1,52 @@ -export { - initPassportDataParsing, - findStartPubKeyIndex, - generateCommitment, - generateNullifier, -} from './passports/passport.js'; -export { - genMockIdDoc, - generateMockDSC, - genMockIdDocAndInitDataParsing, -} from './passports/genMockIdDoc.js'; -export type { IdDocInput } from './passports/genMockIdDoc.js'; -export { genAndInitMockPassportData } from './passports/genMockPassportData.js'; -export { parseDscCertificateData } from './passports/passport_parsing/parseDscCertificateData.js'; -export { brutforceSignatureAlgorithmDsc } from './passports/passport_parsing/brutForceDscSignature.js'; -export { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple.js'; -export { initElliptic } from './certificate_parsing/elliptic.js'; export type { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, } from './certificate_parsing/dataStructure.js'; -export { getSKIPEM } from './csca.js'; -export { formatMrz } from './passports/format.js'; -export { getCircuitNameFromPassportData } from './circuits/circuitsName.js'; +export type { DocumentCategory, PassportData } from './types.js'; +export type { IdDocInput } from './passports/genMockIdDoc.js'; +export type { PassportMetadata } from './passports/passport_parsing/parsePassportData.js'; +export type { UserIdType } from './circuits/uuid.js'; +export { + EndpointType, + Mode, + SelfApp, + SelfAppBuilder, + SelfAppDisclosureConfig, + getUniversalLink, +} from './appType.js'; +export { bigIntToString, formatEndpoint, hashEndpointWithScope, stringToBigInt } from './scope.js'; +export { brutforceSignatureAlgorithmDsc } from './passports/passport_parsing/brutForceDscSignature.js'; +export { buildSMT, getLeafCscaTree, getLeafDscTree } from './trees.js'; export { - flexiblePoseidon, - hash, - getHashLen, - customHasher, - packBytesAndPoseidon, calculateUserIdentifierHash, + customHasher, + flexiblePoseidon, + getHashLen, getSolidityPackedUserContextData, + hash, + packBytesAndPoseidon, } from './hash.js'; -export { getLeafCscaTree, getLeafDscTree, buildSMT } from './trees.js'; +export { + findStartPubKeyIndex, + generateCommitment, + generateNullifier, + initPassportDataParsing, +} from './passports/passport.js'; +export { formatMrz } from './passports/format.js'; +export { genAndInitMockPassportData } from './passports/genMockPassportData.js'; +export { + genMockIdDoc, + genMockIdDocAndInitDataParsing, + generateMockDSC, +} from './passports/genMockIdDoc.js'; export { generateCircuitInputsDSC, generateCircuitInputsRegister, generateCircuitInputsVCandDisclose, } from './circuits/generateInputs.js'; -export type { PassportMetadata } from './passports/passport_parsing/parsePassportData.js'; -export type { UserIdType } from './circuits/uuid.js'; -export type { PassportData, DocumentCategory } from './types.js'; -export { - Mode, - EndpointType, - SelfApp, - SelfAppDisclosureConfig, - SelfAppBuilder, - getUniversalLink, -} from './appType.js'; -export { formatEndpoint, hashEndpointWithScope, stringToBigInt, bigIntToString } from './scope.js'; +export { getCircuitNameFromPassportData } from './circuits/circuitsName.js'; +export { getSKIPEM } from './csca.js'; +export { initElliptic } from './certificate_parsing/elliptic.js'; +export { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple.js'; +export { parseDscCertificateData } from './passports/passport_parsing/parseDscCertificateData.js'; diff --git a/common/src/utils/passportData.ts b/common/src/utils/passportData.ts index 3901af467..16cc98377 100644 --- a/common/src/utils/passportData.ts +++ b/common/src/utils/passportData.ts @@ -1,4 +1,5 @@ -import { PassportData } from './types.js'; +import type { PassportData } from './types.js'; + const fs = require('fs'); const path = require('path'); diff --git a/common/src/utils/passports/dg1.ts b/common/src/utils/passports/dg1.ts index 63f2ea942..5c2d07e87 100644 --- a/common/src/utils/passports/dg1.ts +++ b/common/src/utils/passports/dg1.ts @@ -1,6 +1,5 @@ -import { formatName } from './format.js'; -import { formatDG1Attribute } from './format.js'; -import { IdDocInput } from './genMockIdDoc.js'; +import { formatDG1Attribute, formatName } from './format.js'; +import type { IdDocInput } from './genMockIdDoc.js'; export function genDG1(idDocInput: IdDocInput) { switch (idDocInput.idType) { diff --git a/common/src/utils/passports/format.ts b/common/src/utils/passports/format.ts index b235c1faa..1645f361d 100644 --- a/common/src/utils/passports/format.ts +++ b/common/src/utils/passports/format.ts @@ -5,7 +5,7 @@ export function formatAndConcatenateDataHashes( dg1HashOffset: number ) { // concatenating dataHashes : - let concat: number[] = []; + const concat: number[] = []; const startingSequence = Array.from( { length: dg1HashOffset }, @@ -83,6 +83,63 @@ export function formatAndConcatenateDataHashes( return concat; } +export function formatDG1Attribute(index: number[], value: string) { + const max_length = index[1] - index[0] + 1; + if (value.length > max_length) { + throw new Error( + `Value is too long for index ${index[0]}-${index[1]} value: ${value} value.length: ${value.length} maxLength: ${max_length}` + ); + } + return value.padEnd(max_length, '<'); +} + +export function formatDg2Hash(dg2Hash: number[]) { + const unsignedBytesDg2Hash = dg2Hash.map((x) => toUnsignedByte(x)); + while (unsignedBytesDg2Hash.length < 64) { + // pad it to 64 bytes to correspond to the hash length of sha512 and avoid multiplying circuits + unsignedBytesDg2Hash.push(0); + } + return unsignedBytesDg2Hash; +} + +export function formatMrz(mrz: string) { + const mrzCharcodes = [...mrz].map((char) => char.charCodeAt(0)); + + if (mrz.length === 88) { + mrzCharcodes.unshift(88); // the length of the mrz data + mrzCharcodes.unshift(95, 31); // the MRZ_INFO_TAG + mrzCharcodes.unshift(91); // the new length of the whole array + mrzCharcodes.unshift(97); // the tag for DG1 + } else if (mrz.length === 90) { + mrzCharcodes.unshift(90); // the length of the mrz data + mrzCharcodes.unshift(95, 31); // the MRZ_INFO_TAG + mrzCharcodes.unshift(93); // the new length of the whole array + mrzCharcodes.unshift(97); // the tag for DG1 + } else { + throw new Error(`Unsupported MRZ length: ${mrz.length}. Expected 88 or 90 characters.`); + } + + return mrzCharcodes; +} + +export function formatName(firstName: string, lastName: string, targetLength: number) { + // Split names by spaces and join parts with '<' + const formattedLastName = lastName.toUpperCase().split(' ').join('<'); + const formattedFirstName = firstName.toUpperCase().split(' ').join('<'); + + // Combine with '<<' separator + let result = `${formattedLastName}<<${formattedFirstName}`; + + // Pad with '<' or truncate to target length + if (result.length < targetLength) { + result = result.padEnd(targetLength, '<'); + } else if (result.length > targetLength) { + result = result.substring(0, targetLength); + } + + return result; +} + export function generateSignedAttr(messageDigest: number[]) { const constructedEContent = []; @@ -107,60 +164,3 @@ export function generateSignedAttr(messageDigest: number[]) { constructedEContent.push(...messageDigest); return constructedEContent; } - -export function formatMrz(mrz: string) { - const mrzCharcodes = [...mrz].map((char) => char.charCodeAt(0)); - - if (mrz.length === 88) { - mrzCharcodes.unshift(88); // the length of the mrz data - mrzCharcodes.unshift(95, 31); // the MRZ_INFO_TAG - mrzCharcodes.unshift(91); // the new length of the whole array - mrzCharcodes.unshift(97); // the tag for DG1 - } else if (mrz.length === 90) { - mrzCharcodes.unshift(90); // the length of the mrz data - mrzCharcodes.unshift(95, 31); // the MRZ_INFO_TAG - mrzCharcodes.unshift(93); // the new length of the whole array - mrzCharcodes.unshift(97); // the tag for DG1 - } else { - throw new Error(`Unsupported MRZ length: ${mrz.length}. Expected 88 or 90 characters.`); - } - - return mrzCharcodes; -} - -export function formatDg2Hash(dg2Hash: number[]) { - const unsignedBytesDg2Hash = dg2Hash.map((x) => toUnsignedByte(x)); - while (unsignedBytesDg2Hash.length < 64) { - // pad it to 64 bytes to correspond to the hash length of sha512 and avoid multiplying circuits - unsignedBytesDg2Hash.push(0); - } - return unsignedBytesDg2Hash; -} - -export function formatDG1Attribute(index: number[], value: string) { - const max_length = index[1] - index[0] + 1; - if (value.length > max_length) { - throw new Error( - `Value is too long for index ${index[0]}-${index[1]} value: ${value} value.length: ${value.length} maxLength: ${max_length}` - ); - } - return value.padEnd(max_length, '<'); -} - -export function formatName(firstName: string, lastName: string, targetLength: number) { - // Split names by spaces and join parts with '<' - const formattedLastName = lastName.toUpperCase().split(' ').join('<'); - const formattedFirstName = firstName.toUpperCase().split(' ').join('<'); - - // Combine with '<<' separator - let result = `${formattedLastName}<<${formattedFirstName}`; - - // Pad with '<' or truncate to target length - if (result.length < targetLength) { - result = result.padEnd(targetLength, '<'); - } else if (result.length > targetLength) { - result = result.substring(0, targetLength); - } - - return result; -} diff --git a/common/src/utils/passports/genMockIdDoc.ts b/common/src/utils/passports/genMockIdDoc.ts index 3e6e4175a..0e528933d 100644 --- a/common/src/utils/passports/genMockIdDoc.ts +++ b/common/src/utils/passports/genMockIdDoc.ts @@ -1,19 +1,23 @@ // generate a mock id document -import { DocumentType, PassportData, SignatureAlgorithm } from '../types.js'; -import { API_URL_STAGING, hashAlgosTypes } from '../../constants/constants.js'; -import { countries } from '../../constants/countries.js'; -import { genDG1 } from './dg1.js'; -import { getHashLen, hash } from '../hash.js'; -import { formatAndConcatenateDataHashes, formatMrz, generateSignedAttr } from './format.js'; -import forge from 'node-forge'; -import elliptic from 'elliptic'; -import { getMockDSC } from './getMockDSC.js'; -import { PublicKeyDetailsRSAPSS } from '../certificate_parsing/dataStructure.js'; -import { PublicKeyDetailsECDSA } from '../certificate_parsing/dataStructure.js'; -import { parseCertificateSimple } from '../certificate_parsing/parseCertificateSimple.js'; -import { getCurveForElliptic } from '../certificate_parsing/curves.js'; import * as asn1 from 'asn1js'; +import elliptic from 'elliptic'; +import forge from 'node-forge'; + +import type { hashAlgosTypes } from '../../constants/constants.js'; +import { API_URL_STAGING } from '../../constants/constants.js'; +import { countries } from '../../constants/countries.js'; +import { getCurveForElliptic } from '../certificate_parsing/curves.js'; +import type { + PublicKeyDetailsECDSA, + PublicKeyDetailsRSAPSS, +} from '../certificate_parsing/dataStructure.js'; +import { parseCertificateSimple } from '../certificate_parsing/parseCertificateSimple.js'; +import { getHashLen, hash } from '../hash.js'; +import type { DocumentType, PassportData, SignatureAlgorithm } from '../types.js'; +import { genDG1 } from './dg1.js'; +import { formatAndConcatenateDataHashes, formatMrz, generateSignedAttr } from './format.js'; +import { getMockDSC } from './getMockDSC.js'; import { initPassportDataParsing } from './passport.js'; export interface IdDocInput { @@ -44,27 +48,6 @@ const defaultIdDocInput: IdDocInput = { sex: 'M', }; -export async function generateMockDSC( - signatureType: string -): Promise<{ privateKeyPem: string; dsc: string }> { - const response = await fetch(`${API_URL_STAGING}/api/v2/generate-dsc`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ signatureType }), - }); - if (!response.ok) { - throw new Error(`Failed to generate DSC: ${response.status} ${response.statusText}`); - } - const data = await response.json(); - if (!data || !data.data) { - throw new Error('Missing data in server response'); - } - if (typeof data.data.privateKeyPem !== 'string' || typeof data.data.dsc !== 'string') { - throw new Error('Invalid DSC response format from server'); - } - return { privateKeyPem: data.data.privateKeyPem, dsc: data.data.dsc }; -} - export function genMockIdDoc( userInput: Partial = {}, mockDSC?: { dsc: string; privateKeyPem: string } @@ -112,6 +95,27 @@ export function genMockIdDocAndInitDataParsing(userInput: Partial = }); } +export async function generateMockDSC( + signatureType: string +): Promise<{ privateKeyPem: string; dsc: string }> { + const response = await fetch(`${API_URL_STAGING}/api/v2/generate-dsc`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ signatureType }), + }); + if (!response.ok) { + throw new Error(`Failed to generate DSC: ${response.status} ${response.statusText}`); + } + const data = await response.json(); + if (!data || !data.data) { + throw new Error('Missing data in server response'); + } + if (typeof data.data.privateKeyPem !== 'string' || typeof data.data.dsc !== 'string') { + throw new Error('Invalid DSC response format from server'); + } + return { privateKeyPem: data.data.privateKeyPem, dsc: data.data.dsc }; +} + function generateRandomBytes(length: number): number[] { // Generate numbers between -128 and 127 to match the existing signed byte format return Array.from({ length }, () => Math.floor(Math.random() * 256) - 128); @@ -156,7 +160,7 @@ function sign( return Array.from(signatureBytes, (c: string) => c.charCodeAt(0)); } else if (signatureAlgorithm === 'ecdsa') { const curve = (publicKeyDetails as PublicKeyDetailsECDSA).curve; - let curveForElliptic = getCurveForElliptic(curve); + const curveForElliptic = getCurveForElliptic(curve); const ec = new elliptic.ec(curveForElliptic); const privateKeyDer = Buffer.from( diff --git a/common/src/utils/passports/genMockPassportData.ts b/common/src/utils/passports/genMockPassportData.ts index f07d91d5e..b4b0f3ce7 100644 --- a/common/src/utils/passports/genMockPassportData.ts +++ b/common/src/utils/passports/genMockPassportData.ts @@ -1,18 +1,19 @@ import * as asn1 from 'asn1js'; import elliptic from 'elliptic'; import * as forge from 'node-forge'; -import { countryCodes } from '../../constants/constants.js'; + +import type { countryCodes } from '../../constants/constants.js'; import { getCurveForElliptic } from '../certificate_parsing/curves.js'; -import { +import type { PublicKeyDetailsECDSA, PublicKeyDetailsRSAPSS, } from '../certificate_parsing/dataStructure.js'; import { parseCertificateSimple } from '../certificate_parsing/parseCertificateSimple.js'; import { getHashLen, hash } from '../hash.js'; -import { PassportData, SignatureAlgorithm } from '../types.js'; +import type { PassportData, SignatureAlgorithm } from '../types.js'; import { formatAndConcatenateDataHashes, formatMrz, generateSignedAttr } from './format.js'; -import { initPassportDataParsing } from './passport.js'; import { getMockDSC } from './getMockDSC.js'; +import { initPassportDataParsing } from './passport.js'; function generateRandomBytes(length: number): number[] { // Generate numbers between -128 and 127 to match the existing signed byte format @@ -38,6 +39,32 @@ function generateDataGroupHashes(mrzHash: number[], hashLen: number): [number, n return dataGroups; } +export function genAndInitMockPassportData( + dgHashAlgo: string, + eContentHashAlgo: string, + signatureType: SignatureAlgorithm, + nationality: keyof typeof countryCodes, + birthDate: string, + expiryDate: string, + passportNumber: string = '15AA81234', + lastName: string = 'DUPONT', + firstName: string = 'ALPHONSE HUGHUES ALBERT' +): PassportData { + return initPassportDataParsing( + genMockPassportData( + dgHashAlgo, + eContentHashAlgo, + signatureType, + nationality, + birthDate, + expiryDate, + passportNumber, + lastName, + firstName + ) + ); +} + export function genMockPassportData( dgHashAlgo: string, eContentHashAlgo: string, @@ -113,32 +140,6 @@ export function genMockPassportData( mock: true, }; } - -export function genAndInitMockPassportData( - dgHashAlgo: string, - eContentHashAlgo: string, - signatureType: SignatureAlgorithm, - nationality: keyof typeof countryCodes, - birthDate: string, - expiryDate: string, - passportNumber: string = '15AA81234', - lastName: string = 'DUPONT', - firstName: string = 'ALPHONSE HUGHUES ALBERT' -): PassportData { - return initPassportDataParsing( - genMockPassportData( - dgHashAlgo, - eContentHashAlgo, - signatureType, - nationality, - birthDate, - expiryDate, - passportNumber, - lastName, - firstName - ) - ); -} function sign( privateKeyPem: string, dsc: string, @@ -161,7 +162,7 @@ function sign( return Array.from(signatureBytes, (c: string) => c.charCodeAt(0)); } else if (signatureAlgorithm === 'ecdsa') { const curve = (publicKeyDetails as PublicKeyDetailsECDSA).curve; - let curveForElliptic = getCurveForElliptic(curve); + const curveForElliptic = getCurveForElliptic(curve); const ec = new elliptic.ec(curveForElliptic); const privateKeyDer = Buffer.from( diff --git a/common/src/utils/passports/getMockDSC.ts b/common/src/utils/passports/getMockDSC.ts index 1381b1833..61544a80b 100644 --- a/common/src/utils/passports/getMockDSC.ts +++ b/common/src/utils/passports/getMockDSC.ts @@ -1,5 +1,5 @@ import * as mockCertificates from '../../constants/mockCertificates.js'; -import { SignatureAlgorithm } from '../types.js'; +import type { SignatureAlgorithm } from '../types.js'; function getMockDSC(signatureType: SignatureAlgorithm) { let privateKeyPem: string; diff --git a/common/src/utils/passports/index.ts b/common/src/utils/passports/index.ts index 9671ad30f..56e976990 100644 --- a/common/src/utils/passports/index.ts +++ b/common/src/utils/passports/index.ts @@ -1,19 +1,16 @@ +export type { IdDocInput } from './genMockIdDoc.js'; +export type { PassportMetadata } from './passport_parsing/parsePassportData.js'; +export { brutforceSignatureAlgorithmDsc } from './passport_parsing/brutForceDscSignature.js'; +// Re-export types export { - initPassportDataParsing, findStartPubKeyIndex, generateCommitment, generateNullifier, getNAndK, + initPassportDataParsing, } from './passport.js'; -export { genMockIdDoc, generateMockDSC, genMockIdDocAndInitDataParsing } from './genMockIdDoc.js'; - export { genAndInitMockPassportData } from './genMockPassportData.js'; +export { genMockIdDoc, genMockIdDocAndInitDataParsing, generateMockDSC } from './genMockIdDoc.js'; export { parseDscCertificateData } from './passport_parsing/parseDscCertificateData.js'; - -export { brutforceSignatureAlgorithmDsc } from './passport_parsing/brutForceDscSignature.js'; - -// Re-export types -export type { IdDocInput } from './genMockIdDoc.js'; -export type { PassportMetadata } from './passport_parsing/parsePassportData.js'; diff --git a/common/src/utils/passports/mock.ts b/common/src/utils/passports/mock.ts index b0bb7a0de..e123f7525 100644 --- a/common/src/utils/passports/mock.ts +++ b/common/src/utils/passports/mock.ts @@ -1,5 +1,3 @@ -export { genMockIdDoc, generateMockDSC, genMockIdDocAndInitDataParsing } from './genMockIdDoc.js'; - -export { genAndInitMockPassportData } from './genMockPassportData.js'; - export type { IdDocInput } from './genMockIdDoc.js'; +export { genAndInitMockPassportData } from './genMockPassportData.js'; +export { genMockIdDoc, genMockIdDocAndInitDataParsing, generateMockDSC } from './genMockIdDoc.js'; diff --git a/common/src/utils/passports/parsing.ts b/common/src/utils/passports/parsing.ts index 2691d10e5..61f8d77d1 100644 --- a/common/src/utils/passports/parsing.ts +++ b/common/src/utils/passports/parsing.ts @@ -1,8 +1,8 @@ export { - getCertificatePubKey, - formatCertificatePubKeyDSC, findStartPubKeyIndex, + formatCertificatePubKeyDSC, + getCertificatePubKey, + getNAndKCSCA, pad, padWithZeroes, - getNAndKCSCA, } from './passport.js'; diff --git a/common/src/utils/passports/passport.ts b/common/src/utils/passports/passport.ts index a73c540e5..1ad628e0b 100644 --- a/common/src/utils/passports/passport.ts +++ b/common/src/utils/passports/passport.ts @@ -1,7 +1,8 @@ import * as forge from 'node-forge'; import { poseidon5 } from 'poseidon-lite'; + +import type { hashAlgos } from '../../constants/constants.js'; import { - hashAlgos, k_csca, k_dsc, k_dsc_3072, @@ -14,7 +15,7 @@ import { n_dsc_ecdsa, } from '../../constants/constants.js'; import { bytesToBigDecimal, hexToDecimal, splitToWords } from '../bytes.js'; -import { +import type { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, @@ -28,21 +29,94 @@ import { findStartIndex, findStartIndexEC } from '../csca.js'; import { hash, packBytesAndPoseidon } from '../hash.js'; import { sha384_512Pad, shaPad } from '../shaPad.js'; import { getLeafDscTree } from '../trees.js'; -import { PassportData, SignatureAlgorithm } from '../types.js'; +import type { PassportData, SignatureAlgorithm } from '../types.js'; import { formatMrz } from './format.js'; import { parsePassportData } from './passport_parsing/parsePassportData.js'; -/// @dev will bruteforce passport and dsc signature -export function initPassportDataParsing(passportData: PassportData, skiPem: any = null) { - const passportMetadata = parsePassportData(passportData, skiPem); - passportData.passportMetadata = passportMetadata; - const dscParsed = parseCertificateSimple(passportData.dsc); - passportData.dsc_parsed = dscParsed; - if (passportData.passportMetadata.csca) { - const cscaParsed = parseCertificateSimple(passportData.passportMetadata.csca); - passportData.csca_parsed = cscaParsed; +export function extractRSFromSignature(signatureBytes: number[]): { r: string; s: string } { + const derSignature = Buffer.from(signatureBytes).toString('binary'); + const asn1 = forge.asn1.fromDer(derSignature); + const signatureAsn1 = asn1.value; + + if (signatureAsn1.length !== 2) { + throw new Error('Invalid signature format'); + } + + if (!Array.isArray(asn1.value) || asn1.value.length !== 2) { + throw new Error('Invalid signature format'); + } + const r = forge.util.createBuffer(asn1.value[0].value as string).toHex(); + const s = forge.util.createBuffer(asn1.value[1].value as string).toHex(); + + return { r, s }; +} + +export function extractSignatureFromDSC(dscCertificate: string) { + const cert = getCertificateFromPem(dscCertificate); + const dscSignature = cert.signatureValue.valueBlock.valueHexView; + return Array.from(dscSignature); +} + +export function findStartPubKeyIndex( + certificateData: CertificateData, + rawCert: any, + signatureAlgorithm: string +): [number, number] { + const { publicKeyDetails } = certificateData; + if (signatureAlgorithm === 'ecdsa') { + const { x, y } = publicKeyDetails as PublicKeyDetailsECDSA; + const [x_index, x_totalLength] = findStartIndexEC(x, rawCert); + const [y_index, y_totalLength] = findStartIndexEC(y, rawCert); + + return [x_index, x_totalLength + y_totalLength]; + } else { + // Splits to 525 words of 8 bits each + const { modulus } = publicKeyDetails as PublicKeyDetailsRSA; + return findStartIndex(modulus, rawCert); + } +} + +/// @notice Get the public key from the certificate padded as per the DSC circuit's requirements. +export function formatCertificatePubKeyDSC( + certificateData: CertificateData, + signatureAlgorithm: string +): string[] { + const { publicKeyDetails } = certificateData; + if (signatureAlgorithm === 'ecdsa') { + const { x, y } = publicKeyDetails as PublicKeyDetailsECDSA; + // const normalizedX = x.length % 2 === 0 ? x : '0' + x; + // const normalizedY = y.length % 2 === 0 ? y : '0' + y; + const fullPubKey = x + y; + + // Splits to 525 words of 8 bits each + return splitToWords(BigInt(hexToDecimal(fullPubKey)), 8, 525); + } else { + // Splits to 525 words of 8 bits each + const { modulus } = publicKeyDetails as PublicKeyDetailsRSA; + return splitToWords(BigInt(hexToDecimal(modulus)), 8, 525); + } +} + +export function formatSignatureDSCCircuit( + cscaSignatureAlgorithm: string, + cscaHashFunction: string, + cscaCertificateData: CertificateData, + signature: number[] +): string[] { + const cscaSignatureAlgorithmFullName = getSignatureAlgorithmFullName( + cscaCertificateData, + cscaSignatureAlgorithm, + cscaHashFunction + ); + const { n, k } = getNAndK(cscaSignatureAlgorithmFullName as SignatureAlgorithm); + if (cscaSignatureAlgorithm === 'ecdsa') { + const { r, s } = extractRSFromSignature(signature); + const signature_r = splitToWords(BigInt(hexToDecimal(r)), n, k); + const signature_s = splitToWords(BigInt(hexToDecimal(s)), n, k); + return [...signature_r, ...signature_s]; + } else { + return formatInput(splitToWords(BigInt(bytesToBigDecimal(signature)), n, k)); } - return passportData; } export function generateCommitment( @@ -76,50 +150,6 @@ export function generateCommitment( ]).toString(); } -export function generateNullifier(passportData: PassportData) { - const signedAttr_shaBytes = hash( - passportData.passportMetadata.signedAttrHashFunction, - Array.from(passportData.signedAttr), - 'bytes' - ); - const signedAttr_packed_hash = packBytesAndPoseidon( - (signedAttr_shaBytes as number[]).map((byte) => byte & 0xff) - ); - return signedAttr_packed_hash; -} - -export function pad(hashFunction: (typeof hashAlgos)[number]) { - return hashFunction === 'sha1' || hashFunction === 'sha224' || hashFunction === 'sha256' - ? shaPad - : sha384_512Pad; -} - -export function padWithZeroes(bytes: number[], length: number) { - return bytes.concat(new Array(length - bytes.length).fill(0)); -} - -/// @notice Get the signature of the passport and the public key of the DSC -/// @dev valid for only for the passport/dsc chain -export function getPassportSignatureInfos(passportData: PassportData) { - const passportMetadata = passportData.passportMetadata; - const signatureAlgorithmFullName = getSignatureAlgorithmFullName( - passportData.dsc_parsed, - passportMetadata.signatureAlgorithm, - passportMetadata.signedAttrHashFunction - ); - const { n, k } = getNAndK(signatureAlgorithmFullName as SignatureAlgorithm); - - return { - pubKey: getCertificatePubKey( - passportData.dsc_parsed, - passportMetadata.signatureAlgorithm, - passportMetadata.signedAttrHashFunction - ), - signature: getPassportSignature(passportData, n, k), - signatureAlgorithmFullName: signatureAlgorithmFullName, - }; -} - function getPassportSignature(passportData: PassportData, n: number, k: number): any { const { signatureAlgorithm } = passportData.dsc_parsed; if (signatureAlgorithm === 'ecdsa') { @@ -132,6 +162,18 @@ function getPassportSignature(passportData: PassportData, n: number, k: number): } } +export function generateNullifier(passportData: PassportData) { + const signedAttr_shaBytes = hash( + passportData.passportMetadata.signedAttrHashFunction, + Array.from(passportData.signedAttr), + 'bytes' + ); + const signedAttr_packed_hash = packBytesAndPoseidon( + (signedAttr_shaBytes as number[]).map((byte) => byte & 0xff) + ); + return signedAttr_packed_hash; +} + /// @notice Get the public key from the certificate /// @dev valid for both DSC and CSCA export function getCertificatePubKey( @@ -157,108 +199,6 @@ export function getCertificatePubKey( } } -/// @notice Get the public key from the certificate padded as per the DSC circuit's requirements. -export function formatCertificatePubKeyDSC( - certificateData: CertificateData, - signatureAlgorithm: string -): string[] { - const { publicKeyDetails } = certificateData; - if (signatureAlgorithm === 'ecdsa') { - const { x, y } = publicKeyDetails as PublicKeyDetailsECDSA; - // const normalizedX = x.length % 2 === 0 ? x : '0' + x; - // const normalizedY = y.length % 2 === 0 ? y : '0' + y; - const fullPubKey = x + y; - - // Splits to 525 words of 8 bits each - return splitToWords(BigInt(hexToDecimal(fullPubKey)), 8, 525); - } else { - // Splits to 525 words of 8 bits each - const { modulus } = publicKeyDetails as PublicKeyDetailsRSA; - return splitToWords(BigInt(hexToDecimal(modulus)), 8, 525); - } -} - -export function extractSignatureFromDSC(dscCertificate: string) { - const cert = getCertificateFromPem(dscCertificate); - const dscSignature = cert.signatureValue.valueBlock.valueHexView; - return Array.from(dscSignature); -} - -export function formatSignatureDSCCircuit( - cscaSignatureAlgorithm: string, - cscaHashFunction: string, - cscaCertificateData: CertificateData, - signature: number[] -): string[] { - const cscaSignatureAlgorithmFullName = getSignatureAlgorithmFullName( - cscaCertificateData, - cscaSignatureAlgorithm, - cscaHashFunction - ); - const { n, k } = getNAndK(cscaSignatureAlgorithmFullName as SignatureAlgorithm); - if (cscaSignatureAlgorithm === 'ecdsa') { - const { r, s } = extractRSFromSignature(signature); - const signature_r = splitToWords(BigInt(hexToDecimal(r)), n, k); - const signature_s = splitToWords(BigInt(hexToDecimal(s)), n, k); - return [...signature_r, ...signature_s]; - } else { - return formatInput(splitToWords(BigInt(bytesToBigDecimal(signature)), n, k)); - } -} - -export function findStartPubKeyIndex( - certificateData: CertificateData, - rawCert: any, - signatureAlgorithm: string -): [number, number] { - const { publicKeyDetails } = certificateData; - if (signatureAlgorithm === 'ecdsa') { - const { x, y } = publicKeyDetails as PublicKeyDetailsECDSA; - const [x_index, x_totalLength] = findStartIndexEC(x, rawCert); - const [y_index, y_totalLength] = findStartIndexEC(y, rawCert); - - return [x_index, x_totalLength + y_totalLength]; - } else { - // Splits to 525 words of 8 bits each - const { modulus } = publicKeyDetails as PublicKeyDetailsRSA; - return findStartIndex(modulus, rawCert); - } -} - -/// @notice Get the signature algorithm full name -/// @dev valid for both DSC and CSCA -export function getSignatureAlgorithmFullName( - certificateData: CertificateData, - signatureAlgorithm: string, - hashAlgorithm: string -): string { - const { publicKeyDetails } = certificateData; - if (signatureAlgorithm === 'ecdsa') { - return `${signatureAlgorithm}_${hashAlgorithm}_${(publicKeyDetails as PublicKeyDetailsECDSA).curve}_${publicKeyDetails.bits}`; - } else { - const { exponent } = publicKeyDetails as PublicKeyDetailsRSA; - return `${signatureAlgorithm}_${hashAlgorithm}_${exponent}_${publicKeyDetails.bits}`; - } -} - -export function extractRSFromSignature(signatureBytes: number[]): { r: string; s: string } { - const derSignature = Buffer.from(signatureBytes).toString('binary'); - const asn1 = forge.asn1.fromDer(derSignature); - const signatureAsn1 = asn1.value; - - if (signatureAsn1.length !== 2) { - throw new Error('Invalid signature format'); - } - - if (!Array.isArray(asn1.value) || asn1.value.length !== 2) { - throw new Error('Invalid signature format'); - } - const r = forge.util.createBuffer(asn1.value[0].value as string).toHex(); - const s = forge.util.createBuffer(asn1.value[1].value as string).toHex(); - - return { r, s }; -} - export function getNAndK(sigAlg: SignatureAlgorithm) { if (sigAlg === 'rsa_sha256_65537_3072') { return { n: n_dsc_3072, k: k_dsc }; // 3072/32 = 96 @@ -305,3 +245,64 @@ export function getNAndKCSCA(sigAlg: 'rsa' | 'ecdsa' | 'rsapss') { const k = sigAlg === 'ecdsa' ? k_dsc_ecdsa : k_csca; return { n, k }; } + +/// @notice Get the signature of the passport and the public key of the DSC +/// @dev valid for only for the passport/dsc chain +export function getPassportSignatureInfos(passportData: PassportData) { + const passportMetadata = passportData.passportMetadata; + const signatureAlgorithmFullName = getSignatureAlgorithmFullName( + passportData.dsc_parsed, + passportMetadata.signatureAlgorithm, + passportMetadata.signedAttrHashFunction + ); + const { n, k } = getNAndK(signatureAlgorithmFullName as SignatureAlgorithm); + + return { + pubKey: getCertificatePubKey( + passportData.dsc_parsed, + passportMetadata.signatureAlgorithm, + passportMetadata.signedAttrHashFunction + ), + signature: getPassportSignature(passportData, n, k), + signatureAlgorithmFullName: signatureAlgorithmFullName, + }; +} + +/// @notice Get the signature algorithm full name +/// @dev valid for both DSC and CSCA +export function getSignatureAlgorithmFullName( + certificateData: CertificateData, + signatureAlgorithm: string, + hashAlgorithm: string +): string { + const { publicKeyDetails } = certificateData; + if (signatureAlgorithm === 'ecdsa') { + return `${signatureAlgorithm}_${hashAlgorithm}_${(publicKeyDetails as PublicKeyDetailsECDSA).curve}_${publicKeyDetails.bits}`; + } else { + const { exponent } = publicKeyDetails as PublicKeyDetailsRSA; + return `${signatureAlgorithm}_${hashAlgorithm}_${exponent}_${publicKeyDetails.bits}`; + } +} + +/// @dev will bruteforce passport and dsc signature +export function initPassportDataParsing(passportData: PassportData, skiPem: any = null) { + const passportMetadata = parsePassportData(passportData, skiPem); + passportData.passportMetadata = passportMetadata; + const dscParsed = parseCertificateSimple(passportData.dsc); + passportData.dsc_parsed = dscParsed; + if (passportData.passportMetadata.csca) { + const cscaParsed = parseCertificateSimple(passportData.passportMetadata.csca); + passportData.csca_parsed = cscaParsed; + } + return passportData; +} + +export function pad(hashFunction: (typeof hashAlgos)[number]) { + return hashFunction === 'sha1' || hashFunction === 'sha224' || hashFunction === 'sha256' + ? shaPad + : sha384_512Pad; +} + +export function padWithZeroes(bytes: number[], length: number) { + return bytes.concat(new Array(length - bytes.length).fill(0)); +} diff --git a/common/src/utils/passports/passport_parsing/brutForceDscSignature.ts b/common/src/utils/passports/passport_parsing/brutForceDscSignature.ts index 81cf2bf4e..466b1b774 100644 --- a/common/src/utils/passports/passport_parsing/brutForceDscSignature.ts +++ b/common/src/utils/passports/passport_parsing/brutForceDscSignature.ts @@ -1,11 +1,14 @@ -import { saltLengths } from '../../../constants/constants.js'; -import { hashAlgos } from '../../../constants/constants.js'; -import { CertificateData, PublicKeyDetailsECDSA } from '../../certificate_parsing/dataStructure.js'; -import { initElliptic } from '../../certificate_parsing/elliptic.js'; import * as asn1js from 'asn1js'; import * as forge from 'node-forge'; -import { getCurveForElliptic } from '../../certificate_parsing/curves.js'; import { Certificate } from 'pkijs'; + +import { hashAlgos, saltLengths } from '../../../constants/constants.js'; +import { getCurveForElliptic } from '../../certificate_parsing/curves.js'; +import type { + CertificateData, + PublicKeyDetailsECDSA, +} from '../../certificate_parsing/dataStructure.js'; +import { initElliptic } from '../../certificate_parsing/elliptic.js'; import { hash } from '../../hash.js'; export function brutforceSignatureAlgorithmDsc(dsc: CertificateData, csca: CertificateData) { diff --git a/common/src/utils/passports/passport_parsing/brutForcePassportSignature.ts b/common/src/utils/passports/passport_parsing/brutForcePassportSignature.ts index da0c617fd..9bf800ff1 100644 --- a/common/src/utils/passports/passport_parsing/brutForcePassportSignature.ts +++ b/common/src/utils/passports/passport_parsing/brutForcePassportSignature.ts @@ -1,13 +1,14 @@ -import { PassportData } from '../../types.js'; -import { parseCertificateSimple } from '../../certificate_parsing/parseCertificateSimple.js'; -import { PublicKeyDetailsECDSA } from '../../certificate_parsing/dataStructure.js'; -import forge, { md } from 'node-forge'; import * as asn1js from 'asn1js'; -import { initElliptic } from '../../certificate_parsing/elliptic.js'; -import { getCurveForElliptic } from '../../certificate_parsing/curves.js'; +import forge, { md } from 'node-forge'; import { Certificate } from 'pkijs'; + import { hashAlgos, saltLengths } from '../../../constants/constants.js'; +import { getCurveForElliptic } from '../../certificate_parsing/curves.js'; +import type { PublicKeyDetailsECDSA } from '../../certificate_parsing/dataStructure.js'; +import { initElliptic } from '../../certificate_parsing/elliptic.js'; +import { parseCertificateSimple } from '../../certificate_parsing/parseCertificateSimple.js'; import { hash } from '../../hash.js'; +import type { PassportData } from '../../types.js'; export function brutforceSignatureAlgorithm(passportData: PassportData) { const parsedDsc = parseCertificateSimple(passportData.dsc); diff --git a/common/src/utils/passports/passport_parsing/parseDscCertificateData.ts b/common/src/utils/passports/passport_parsing/parseDscCertificateData.ts index f55fe86c2..fa618bf13 100644 --- a/common/src/utils/passports/passport_parsing/parseDscCertificateData.ts +++ b/common/src/utils/passports/passport_parsing/parseDscCertificateData.ts @@ -1,4 +1,4 @@ -import { CertificateData } from '../../certificate_parsing/dataStructure.js'; +import type { CertificateData } from '../../certificate_parsing/dataStructure.js'; import { parseCertificateSimple } from '../../certificate_parsing/parseCertificateSimple.js'; import { getCSCAFromSKI } from '../../csca.js'; import { brutforceSignatureAlgorithmDsc } from './brutForceDscSignature.js'; diff --git a/common/src/utils/passports/passport_parsing/parsePassportData.ts b/common/src/utils/passports/passport_parsing/parsePassportData.ts index 4d707370f..9d427c72a 100644 --- a/common/src/utils/passports/passport_parsing/parsePassportData.ts +++ b/common/src/utils/passports/passport_parsing/parsePassportData.ts @@ -1,16 +1,17 @@ import { hashAlgos } from '../../../constants/constants.js'; import { findSubarrayIndex } from '../../arrays.js'; -import { +import type { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, } from '../../certificate_parsing/dataStructure.js'; import { parseCertificateSimple } from '../../certificate_parsing/parseCertificateSimple.js'; import { getHashLen, hash } from '../../hash.js'; -import { PassportData } from '../../types.js'; +import type { PassportData } from '../../types.js'; import { formatMrz } from '../format.js'; import { brutforceSignatureAlgorithm } from './brutForcePassportSignature.js'; -import { DscCertificateMetaData, parseDscCertificateData } from './parseDscCertificateData.js'; +import type { DscCertificateMetaData } from './parseDscCertificateData.js'; +import { parseDscCertificateData } from './parseDscCertificateData.js'; export interface PassportMetadata { dataGroups: string; diff --git a/common/src/utils/passports/signature.ts b/common/src/utils/passports/signature.ts index b54d3f141..0e1ff9b7e 100644 --- a/common/src/utils/passports/signature.ts +++ b/common/src/utils/passports/signature.ts @@ -1,8 +1,8 @@ export { - getPassportSignatureInfos, + extractRSFromSignature, extractSignatureFromDSC, formatSignatureDSCCircuit, - getSignatureAlgorithmFullName, - extractRSFromSignature, getNAndK, + getPassportSignatureInfos, + getSignatureAlgorithmFullName, } from './passport.js'; diff --git a/common/src/utils/scope.ts b/common/src/utils/scope.ts index 8ac0c705c..af1c98425 100644 --- a/common/src/utils/scope.ts +++ b/common/src/utils/scope.ts @@ -1,6 +1,22 @@ import { poseidon2 } from 'poseidon-lite'; + import { flexiblePoseidon } from './hash.js'; +export function bigIntToString(bigInt: bigint): string { + if (bigInt === 0n) return ''; + + let result = ''; + let tempBigInt = bigInt; + + while (tempBigInt > 0n) { + const charCode = Number(tempBigInt & 0xffn); + result = String.fromCharCode(charCode) + result; + tempBigInt = tempBigInt >> 8n; + } + + return result; +} + export function formatEndpoint(endpoint: string): string { if (!endpoint) return ''; return endpoint.replace(/^https?:\/\//, '').split('/')[0]; @@ -44,18 +60,3 @@ export function stringToBigInt(str: string): bigint { return result; } - -export function bigIntToString(bigInt: bigint): string { - if (bigInt === 0n) return ''; - - let result = ''; - let tempBigInt = bigInt; - - while (tempBigInt > 0n) { - const charCode = Number(tempBigInt & 0xffn); - result = String.fromCharCode(charCode) + result; - tempBigInt = tempBigInt >> 8n; - } - - return result; -} diff --git a/common/src/utils/selfAttestation.ts b/common/src/utils/selfAttestation.ts index 8e50a6e12..c195c0fa6 100644 --- a/common/src/utils/selfAttestation.ts +++ b/common/src/utils/selfAttestation.ts @@ -1,4 +1,4 @@ -import { Groth16Proof, PublicSignals } from 'snarkjs'; +import type { Groth16Proof, PublicSignals } from 'snarkjs'; export interface SelfVerificationResult { isValid: boolean; diff --git a/common/src/utils/shaPad.ts b/common/src/utils/shaPad.ts index 5a252834d..1011b53ec 100644 --- a/common/src/utils/shaPad.ts +++ b/common/src/utils/shaPad.ts @@ -1,65 +1,15 @@ -// Copied from zk-email cuz it uses crypto so can't import it here. - -// Puts an end selector, a bunch of 0s, then the length, then fill the rest with 0s. -export function shaPad(prehash_prepad_m_array: number[], maxShaBytes: number): [number[], number] { - let prehash_prepad_m = new Uint8Array(prehash_prepad_m_array); - let length_bits = prehash_prepad_m.length * 8; // bytes to bits - let length_in_bytes = int64toBytes(length_bits); - prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(2 ** 7)); // Add the 1 on the end, length 505 - while ((prehash_prepad_m.length * 8 + length_in_bytes.length * 8) % 512 !== 0) { - prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(0)); +export function assert(cond: boolean, errorMessage: string) { + if (!cond) { + throw new Error(errorMessage); } - prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, length_in_bytes); - assert((prehash_prepad_m.length * 8) % 512 === 0, 'Padding did not complete properly!'); - let messageLen = prehash_prepad_m.length; - while (prehash_prepad_m.length < maxShaBytes) { - prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int64toBytes(0)); - } - assert( - prehash_prepad_m.length === maxShaBytes, - `Padding to max length did not complete properly! Your padded message is ${prehash_prepad_m.length} long but max is ${maxShaBytes}!` - ); - return [Array.from(prehash_prepad_m), messageLen]; } -export function sha384_512Pad( - prehash_prepad_m_array: number[], - maxShaBytes: number -): [number[], number] { - let prehash_prepad_m = new Uint8Array(prehash_prepad_m_array); - // Length in bits before padding - let length_bits = prehash_prepad_m.length * 8; - - // For SHA-384, length is stored in 128 bits (16 bytes) - let length_in_bytes = int128toBytes(length_bits); - - // Add the 1 bit (as a byte with value 128) - prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(2 ** 7)); - - // Add padding zeros until total length is congruent to 896 mod 1024 - while ((prehash_prepad_m.length * 8 + length_in_bytes.length * 8) % 1024 !== 0) { - prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(0)); - } - - // Append the length - prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, length_in_bytes); - - // Verify padding is correct (multiple of 1024 bits) - assert((prehash_prepad_m.length * 8) % 1024 === 0, 'Padding did not complete properly!'); - - let messageLen = prehash_prepad_m.length; - - // Pad to max length if needed - while (prehash_prepad_m.length < maxShaBytes) { - prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int128toBytes(0)); - } - - assert( - prehash_prepad_m.length === maxShaBytes, - `Padding to max length did not complete properly! Your padded message is ${prehash_prepad_m.length} long but max is ${maxShaBytes}!` - ); - - return [Array.from(prehash_prepad_m), messageLen]; +// Works only on 32 bit sha text lengths +export function int64toBytes(num: number): Uint8Array { + const arr = new ArrayBuffer(8); // an Int32 takes 4 bytes + const view = new DataView(arr); + view.setInt32(4, num, false); // byteOffset = 0; litteEndian = false + return new Uint8Array(arr); } // Helper function to convert 128-bit length to bytes @@ -76,31 +26,80 @@ function int128toBytes(x: number): Uint8Array { } // Works only on 32 bit sha text lengths -export function int64toBytes(num: number): Uint8Array { - let arr = new ArrayBuffer(8); // an Int32 takes 4 bytes - let view = new DataView(arr); - view.setInt32(4, num, false); // byteOffset = 0; litteEndian = false +export function int8toBytes(num: number): Uint8Array { + const arr = new ArrayBuffer(1); // an Int8 takes 4 bytes + const view = new DataView(arr); + view.setUint8(0, num); // byteOffset = 0; litteEndian = false return new Uint8Array(arr); } export function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { // sum of individual array lengths - var mergedArray = new Uint8Array(a1.length + a2.length); + const mergedArray = new Uint8Array(a1.length + a2.length); mergedArray.set(a1); mergedArray.set(a2, a1.length); return mergedArray; } -// Works only on 32 bit sha text lengths -export function int8toBytes(num: number): Uint8Array { - let arr = new ArrayBuffer(1); // an Int8 takes 4 bytes - let view = new DataView(arr); - view.setUint8(0, num); // byteOffset = 0; litteEndian = false - return new Uint8Array(arr); +export function sha384_512Pad( + prehash_prepad_m_array: number[], + maxShaBytes: number +): [number[], number] { + let prehash_prepad_m = new Uint8Array(prehash_prepad_m_array); + // Length in bits before padding + const length_bits = prehash_prepad_m.length * 8; + + // For SHA-384, length is stored in 128 bits (16 bytes) + const length_in_bytes = int128toBytes(length_bits); + + // Add the 1 bit (as a byte with value 128) + prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(2 ** 7)); + + // Add padding zeros until total length is congruent to 896 mod 1024 + while ((prehash_prepad_m.length * 8 + length_in_bytes.length * 8) % 1024 !== 0) { + prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(0)); + } + + // Append the length + prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, length_in_bytes); + + // Verify padding is correct (multiple of 1024 bits) + assert((prehash_prepad_m.length * 8) % 1024 === 0, 'Padding did not complete properly!'); + + const messageLen = prehash_prepad_m.length; + + // Pad to max length if needed + while (prehash_prepad_m.length < maxShaBytes) { + prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int128toBytes(0)); + } + + assert( + prehash_prepad_m.length === maxShaBytes, + `Padding to max length did not complete properly! Your padded message is ${prehash_prepad_m.length} long but max is ${maxShaBytes}!` + ); + + return [Array.from(prehash_prepad_m), messageLen]; } -export function assert(cond: boolean, errorMessage: string) { - if (!cond) { - throw new Error(errorMessage); +// Copied from zk-email cuz it uses crypto so can't import it here. +// Puts an end selector, a bunch of 0s, then the length, then fill the rest with 0s. +export function shaPad(prehash_prepad_m_array: number[], maxShaBytes: number): [number[], number] { + let prehash_prepad_m = new Uint8Array(prehash_prepad_m_array); + const length_bits = prehash_prepad_m.length * 8; // bytes to bits + const length_in_bytes = int64toBytes(length_bits); + prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(2 ** 7)); // Add the 1 on the end, length 505 + while ((prehash_prepad_m.length * 8 + length_in_bytes.length * 8) % 512 !== 0) { + prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(0)); } + prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, length_in_bytes); + assert((prehash_prepad_m.length * 8) % 512 === 0, 'Padding did not complete properly!'); + const messageLen = prehash_prepad_m.length; + while (prehash_prepad_m.length < maxShaBytes) { + prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int64toBytes(0)); + } + assert( + prehash_prepad_m.length === maxShaBytes, + `Padding to max length did not complete properly! Your padded message is ${prehash_prepad_m.length} long but max is ${maxShaBytes}!` + ); + return [Array.from(prehash_prepad_m), messageLen]; } diff --git a/common/src/utils/trees.ts b/common/src/utils/trees.ts index dad0bb205..41e0a0ce6 100644 --- a/common/src/utils/trees.ts +++ b/common/src/utils/trees.ts @@ -1,182 +1,30 @@ -import { IMT } from '@openpassport/zk-kit-imt'; -import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; -import { ChildNodes, SMT } from '@openpassport/zk-kit-smt'; 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, poseidon6, poseidon10 } from 'poseidon-lite'; -import { CertificateData } from './certificate_parsing/dataStructure.js'; -import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple.js'; +import { poseidon2, poseidon3, poseidon6, poseidon10, poseidon12, poseidon13 } from 'poseidon-lite'; + import { CSCA_TREE_DEPTH, DSC_TREE_DEPTH, max_csca_bytes, + max_dsc_bytes, OFAC_TREE_LEVELS, } from '../constants/constants.js'; -import { max_dsc_bytes } from '../constants/constants.js'; +import type { CertificateData } from './certificate_parsing/dataStructure.js'; +import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple.js'; import { stringToAsciiBigIntArray } from './circuits/uuid.js'; import { packBytesAndPoseidon } from './hash.js'; import { pad } from './passports/passport.js'; -import { - DscCertificateMetaData, - parseDscCertificateData, -} from './passports/passport_parsing/parseDscCertificateData.js'; +import type { DscCertificateMetaData } from './passports/passport_parsing/parseDscCertificateData.js'; +import { parseDscCertificateData } from './passports/passport_parsing/parseDscCertificateData.js'; + +import { IMT } from '@openpassport/zk-kit-imt'; +import { LeanIMT } from '@openpassport/zk-kit-lean-imt'; +import type { ChildNodes } from '@openpassport/zk-kit-smt'; +import { SMT } from '@openpassport/zk-kit-smt'; // SideEffect here countries.registerLocale(en); -/** get leaf for DSC and CSCA Trees */ -export function getLeaf(parsed: CertificateData, type: 'dsc' | 'csca'): string { - if (type === 'dsc') { - // for now, we pad it for sha - const tbsArray = Object.keys(parsed.tbsBytes).map((key) => parsed.tbsBytes[key]); - const [paddedTbsBytes, tbsBytesPaddedLength] = pad(parsed.hashAlgorithm)( - tbsArray, - max_dsc_bytes - ); - const dsc_hash = packBytesAndPoseidon(Array.from(paddedTbsBytes)); - - return poseidon2([dsc_hash, tbsArray.length]).toString(); - } else { - const tbsBytesArray = Array.from(parsed.tbsBytes); - const paddedTbsBytesArray = tbsBytesArray.concat( - new Array(max_csca_bytes - tbsBytesArray.length).fill(0) - ); - const csca_hash = packBytesAndPoseidon(paddedTbsBytesArray); - return poseidon2([csca_hash, tbsBytesArray.length]).toString(); - } -} - -export function getLeafDscTreeFromDscCertificateMetadata( - dscParsed: CertificateData, - dscMetaData: DscCertificateMetaData -): string { - // TODO: WRONG change this function using raw dsc and hashfunctions from passportMetadata - const cscaParsed = parseCertificateSimple(dscMetaData.csca); - return getLeafDscTree(dscParsed, cscaParsed); -} - -export function getLeafDscTreeFromParsedDsc(dscParsed: CertificateData): string { - return getLeafDscTreeFromDscCertificateMetadata(dscParsed, parseDscCertificateData(dscParsed)); -} - -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 getLeafCscaTree(csca_parsed: CertificateData): string { - return getLeaf(csca_parsed, 'csca'); -} - -export function getDscTreeInclusionProof( - leaf: string, - serialized_dsc_tree: string -): [string, number[], bigint[], number] { - const hashFunction = (a: any, b: any) => poseidon2([a, b]); - const tree = LeanIMT.import(hashFunction, serialized_dsc_tree); - const index = tree.indexOf(BigInt(leaf)); - if (index === -1) { - throw new Error('Your public key was not found in the registry'); - } - const { siblings, path, leaf_depth } = generateMerkleProof(tree, index, DSC_TREE_DEPTH); - return [tree.root, path, siblings, leaf_depth]; -} - -export function getCscaTreeInclusionProof(leaf: string, _serialized_csca_tree: any[][]) { - let tree = new IMT(poseidon2, CSCA_TREE_DEPTH, 0, 2); - tree.setNodes(_serialized_csca_tree); - const index = tree.indexOf(leaf); - if (index === -1) { - throw new Error('Your public key was not found in the registry'); - } - const proof = tree.createProof(index); - return [ - tree.root, - proof.pathIndices.map((index) => index.toString()), - proof.siblings.flat().map((sibling) => sibling.toString()), - ]; -} - -export function getCscaTreeRoot(serialized_csca_tree: any[][]) { - let tree = new IMT(poseidon2, CSCA_TREE_DEPTH, 0, 2); - tree.setNodes(serialized_csca_tree); - return tree.root; -} - -export function formatRoot(root: string): string { - let rootHex = BigInt(root).toString(16); - return rootHex.length % 2 === 0 ? '0x' + rootHex : '0x0' + rootHex; -} - -export function generateSMTProof(smt: SMT, leaf: bigint) { - const { entry, matchingEntry, siblings, root, membership } = smt.createProof(leaf); - const leaf_depth = siblings.length; - - let closestleaf; - if (!matchingEntry) { - // we got the 0 leaf or membership - // then check if entry[1] exists - if (!entry[1]) { - // non membership proof - closestleaf = BigInt(0); // 0 leaf - } else { - closestleaf = BigInt(entry[0]); // leaf itself (memb proof) - } - } else { - // non membership proof - closestleaf = BigInt(matchingEntry[0]); // actual closest - } - - // PATH, SIBLINGS manipulation as per binary tree in the circuit - siblings.reverse(); - while (siblings.length < OFAC_TREE_LEVELS) siblings.push(BigInt(0)); - - // ----- Useful for debugging hence leaving as comments ----- - // const binary = entry[0].toString(2) - // const bits = binary.slice(-leaf_depth); - // let indices = bits.padEnd(256, "0").split("").map(Number) - // const pathToMatch = num2Bits(256,BigInt(entry[0])) - // while(indices.length < 256) indices.push(0); - // // CALCULATED ROOT FOR TESTING - // // closestleaf, leaf_depth, siblings, indices, root : needed - // let calculatedNode = poseidon3([closestleaf,1,1]); - // console.log("Initial node while calculating",calculatedNode) - // console.log(smt.verifyProof(smt.createProof(leaf))) - // for (let i= 0; i < leaf_depth ; i++) { - // const childNodes: any = indices[i] ? [siblings[i], calculatedNode] : [calculatedNode, siblings[i]] - // console.log(indices[i],childNodes) - // calculatedNode = poseidon2(childNodes) - // } - // console.log("Actual node", root) - // console.log("calculated node", calculatedNode) - // ----------------------------------------------------------- - - return { - root, - leaf_depth, - closestleaf, - siblings, - }; -} - -export function generateMerkleProof(imt: LeanIMT, _index: number, maxleaf_depth: number) { - const { siblings: siblings, index } = imt.generateProof(_index); - const leaf_depth = siblings.length; - // The index must be converted to a list of indices, 1 for each tree level. - // The circuit tree leaf_depth is 20, so the number of siblings must be 20, even if - // the tree leaf_depth is actually 3. The missing siblings can be set to 0, as they - // won't be used to calculate the root in the circuit. - const path: number[] = []; - - for (let i = 0; i < maxleaf_depth; i += 1) { - path.push((index >> i) & 1); - if (siblings[i] === undefined) { - siblings[i] = BigInt(0); - } - } - return { siblings, path, leaf_depth }; -} // SMT trees for 3 levels of matching : // 1. Passport Number and Nationality tree : level 3 (Absolute Match) @@ -185,7 +33,7 @@ export function generateMerkleProof(imt: LeanIMT, _index: number, maxleaf_depth: // NEW: ID card specific trees export function buildSMT(field: any[], treetype: string): [number, number, SMT] { let count = 0; - let startTime = performance.now(); + const startTime = performance.now(); const hash2 = (childNodes: ChildNodes) => childNodes.length === 2 ? poseidon2(childNodes) : poseidon3(childNodes); @@ -243,6 +91,177 @@ export function buildSMT(field: any[], treetype: string): [number, number, SMT] return [count, performance.now() - startTime, tree]; } +export function formatRoot(root: string): string { + const rootHex = BigInt(root).toString(16); + return rootHex.length % 2 === 0 ? '0x' + rootHex : '0x0' + rootHex; +} + +export function generateMerkleProof(imt: LeanIMT, _index: number, maxleaf_depth: number) { + const { siblings: siblings, index } = imt.generateProof(_index); + const leaf_depth = siblings.length; + // The index must be converted to a list of indices, 1 for each tree level. + // The circuit tree leaf_depth is 20, so the number of siblings must be 20, even if + // the tree leaf_depth is actually 3. The missing siblings can be set to 0, as they + // won't be used to calculate the root in the circuit. + const path: number[] = []; + + for (let i = 0; i < maxleaf_depth; i += 1) { + path.push((index >> i) & 1); + if (siblings[i] === undefined) { + siblings[i] = BigInt(0); + } + } + return { siblings, path, leaf_depth }; +} + +export function generateSMTProof(smt: SMT, leaf: bigint) { + const { entry, matchingEntry, siblings, root, membership } = smt.createProof(leaf); + const leaf_depth = siblings.length; + + let closestleaf; + if (!matchingEntry) { + // we got the 0 leaf or membership + // then check if entry[1] exists + if (!entry[1]) { + // non membership proof + closestleaf = BigInt(0); // 0 leaf + } else { + closestleaf = BigInt(entry[0]); // leaf itself (memb proof) + } + } else { + // non membership proof + closestleaf = BigInt(matchingEntry[0]); // actual closest + } + + // PATH, SIBLINGS manipulation as per binary tree in the circuit + siblings.reverse(); + while (siblings.length < OFAC_TREE_LEVELS) siblings.push(BigInt(0)); + + // ----- Useful for debugging hence leaving as comments ----- + // const binary = entry[0].toString(2) + // const bits = binary.slice(-leaf_depth); + // let indices = bits.padEnd(256, "0").split("").map(Number) + // const pathToMatch = num2Bits(256,BigInt(entry[0])) + // while(indices.length < 256) indices.push(0); + // // CALCULATED ROOT FOR TESTING + // // closestleaf, leaf_depth, siblings, indices, root : needed + // let calculatedNode = poseidon3([closestleaf,1,1]); + // console.log("Initial node while calculating",calculatedNode) + // console.log(smt.verifyProof(smt.createProof(leaf))) + // for (let i= 0; i < leaf_depth ; i++) { + // const childNodes: any = indices[i] ? [siblings[i], calculatedNode] : [calculatedNode, siblings[i]] + // console.log(indices[i],childNodes) + // calculatedNode = poseidon2(childNodes) + // } + // console.log("Actual node", root) + // console.log("calculated node", calculatedNode) + // ----------------------------------------------------------- + + return { + root, + leaf_depth, + closestleaf, + siblings, + }; +} + +export function getCountryLeaf( + country_by: (bigint | number)[], + country_to: (bigint | number)[], + i?: number +): bigint { + if (country_by.length !== 3 || country_to.length !== 3) { + console.log('parsed passport length is not 3:', i, country_to, country_by); + return; + } + try { + const country = country_by.concat(country_to); + return poseidon6(country); + } catch (err) { + console.log('err : sanc_country hash', err, i, country_by, country_to); + } +} + +export function getCscaTreeInclusionProof(leaf: string, _serialized_csca_tree: any[][]) { + const tree = new IMT(poseidon2, CSCA_TREE_DEPTH, 0, 2); + tree.setNodes(_serialized_csca_tree); + const index = tree.indexOf(leaf); + if (index === -1) { + throw new Error('Your public key was not found in the registry'); + } + const proof = tree.createProof(index); + return [ + tree.root, + proof.pathIndices.map((index) => index.toString()), + proof.siblings.flat().map((sibling) => sibling.toString()), + ]; +} + +export function getCscaTreeRoot(serialized_csca_tree: any[][]) { + const tree = new IMT(poseidon2, CSCA_TREE_DEPTH, 0, 2); + tree.setNodes(serialized_csca_tree); + return tree.root; +} + +export function getDobLeaf(dobMrz: (bigint | number)[], i?: number): bigint { + if (dobMrz.length !== 6) { + // console.log('parsed dob length is not 6:', i, dobMrz); // Corrected length check message + return BigInt(0); // Return 0 for invalid length + } + try { + return poseidon6(dobMrz); + } catch (err) { + console.error('Error in getDobLeaf:', err, 'Index:', i, 'DOB MRZ:', dobMrz); // Use console.error + return BigInt(0); // Return 0 on error + } +} + +export function getDscTreeInclusionProof( + leaf: string, + serialized_dsc_tree: string +): [string, number[], bigint[], number] { + const hashFunction = (a: any, b: any) => poseidon2([a, b]); + const tree = LeanIMT.import(hashFunction, serialized_dsc_tree); + const index = tree.indexOf(BigInt(leaf)); + if (index === -1) { + throw new Error('Your public key was not found in the registry'); + } + const { siblings, path, leaf_depth } = generateMerkleProof(tree, index, DSC_TREE_DEPTH); + return [tree.root, path, siblings, leaf_depth]; +} + +/** get leaf for DSC and CSCA Trees */ +export function getLeaf(parsed: CertificateData, type: 'dsc' | 'csca'): string { + if (type === 'dsc') { + // for now, we pad it for sha + const tbsArray = Object.keys(parsed.tbsBytes).map((key) => parsed.tbsBytes[key]); + const [paddedTbsBytes, tbsBytesPaddedLength] = pad(parsed.hashAlgorithm)( + tbsArray, + max_dsc_bytes + ); + const dsc_hash = packBytesAndPoseidon(Array.from(paddedTbsBytes)); + + return poseidon2([dsc_hash, tbsArray.length]).toString(); + } else { + const tbsBytesArray = Array.from(parsed.tbsBytes); + const paddedTbsBytesArray = tbsBytesArray.concat( + new Array(max_csca_bytes - tbsBytesArray.length).fill(0) + ); + const csca_hash = packBytesAndPoseidon(paddedTbsBytesArray); + return poseidon2([csca_hash, tbsBytesArray.length]).toString(); + } +} + +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, @@ -395,7 +414,7 @@ function processName( } } console.log('arr', arr, 'arr.length', arr.length); - let nameArr = stringToAsciiBigIntArray(arr); + const nameArr = stringToAsciiBigIntArray(arr); // getNameLeaf will select the correct Poseidon hash based on nameArr.length return getNameLeaf(nameArr, i); } @@ -435,13 +454,13 @@ function processDob(day: string, month: string, year: string, i: number): bigint const yearSuffix = year.slice(-2); const dob = yearSuffix + mappedMonth + day; - let arr = stringToAsciiBigIntArray(dob); + const arr = stringToAsciiBigIntArray(dob); return getDobLeaf(arr, i); } function processCountry(country1: string, country2: string, i: number) { - let arr = stringToAsciiBigIntArray(country1); - let arr2 = stringToAsciiBigIntArray(country2); + const arr = stringToAsciiBigIntArray(country1); + const arr2 = stringToAsciiBigIntArray(country2); const leaf = getCountryLeaf(arr, arr2, i); if (!leaf) { @@ -451,42 +470,17 @@ function processCountry(country1: string, country2: string, i: number) { return leaf; } -export function getCountryLeaf( - country_by: (bigint | number)[], - country_to: (bigint | number)[], - i?: number -): bigint { - if (country_by.length !== 3 || country_to.length !== 3) { - console.log('parsed passport length is not 3:', i, country_to, country_by); - return; - } - try { - const country = country_by.concat(country_to); - return poseidon6(country); - } catch (err) { - console.log('err : sanc_country hash', err, i, country_by, country_to); - } +export function getLeafDscTreeFromDscCertificateMetadata( + dscParsed: CertificateData, + dscMetaData: DscCertificateMetaData +): string { + // TODO: WRONG change this function using raw dsc and hashfunctions from passportMetadata + const cscaParsed = parseCertificateSimple(dscMetaData.csca); + return getLeafDscTree(dscParsed, cscaParsed); } -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); - } +export function getLeafDscTreeFromParsedDsc(dscParsed: CertificateData): string { + return getLeafDscTreeFromDscCertificateMetadata(dscParsed, parseDscCertificateData(dscParsed)); } export function getNameDobLeaf( @@ -497,17 +491,9 @@ export function getNameDobLeaf( return generateSmallKey(poseidon2([getDobLeaf(dobMrz), getNameLeaf(nameMrz)])); } -export function getNameYobLeaf( - nameMrz: (bigint | number)[], - yobMrz: (bigint | number)[], - i?: number -): bigint { - return generateSmallKey(poseidon2([getYearLeaf(yobMrz), getNameLeaf(nameMrz)])); -} - export function getNameLeaf(nameMrz: (bigint | number)[], i?: number): bigint { - let middleChunks: bigint[] = []; - let chunks: (number | bigint)[][] = []; + const middleChunks: bigint[] = []; + const chunks: (number | bigint)[][] = []; try { // Add try-catch block if (nameMrz.length == 39) { @@ -540,15 +526,31 @@ export function getNameLeaf(nameMrz: (bigint | number)[], i?: number): bigint { } } -export function getDobLeaf(dobMrz: (bigint | number)[], i?: number): bigint { - if (dobMrz.length !== 6) { - // console.log('parsed dob length is not 6:', i, dobMrz); // Corrected length check message - return BigInt(0); // Return 0 for invalid length +export function getNameYobLeaf( + nameMrz: (bigint | number)[], + yobMrz: (bigint | number)[], + i?: number +): bigint { + 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 { - return poseidon6(dobMrz); + const fullHash = poseidon12(passport.concat(nationality)); + return generateSmallKey(fullHash); } catch (err) { - console.error('Error in getDobLeaf:', err, 'Index:', i, 'DOB MRZ:', dobMrz); // Use console.error - return BigInt(0); // Return 0 on error + console.log('err : passport', err, i, passport); } } diff --git a/common/src/utils/types.ts b/common/src/utils/types.ts index 128d22605..5385f0a92 100644 --- a/common/src/utils/types.ts +++ b/common/src/utils/types.ts @@ -1,6 +1,9 @@ -import { CertificateData } from './certificate_parsing/dataStructure.js'; -import { PassportMetadata } from './passports/passport_parsing/parsePassportData.js'; +import type { CertificateData } from './certificate_parsing/dataStructure.js'; +import type { PassportMetadata } from './passports/passport_parsing/parsePassportData.js'; +export type DocumentCategory = 'passport' | 'id_card'; + +export type DocumentType = 'passport' | 'id_card' | 'mock_passport' | 'mock_id_card'; export type PassportData = { mrz: string; dg1Hash?: number[]; @@ -18,8 +21,14 @@ export type PassportData = { mock: boolean; }; -export type DocumentType = 'passport' | 'id_card' | 'mock_passport' | 'mock_id_card'; -export type DocumentCategory = 'passport' | 'id_card'; +export type Proof = { + proof: { + a: [string, string]; + b: [[string, string], [string, string]]; + c: [string, string]; + }; + pub_signals: string[]; +}; // Define the signature algorithm in "algorithm_hashfunction_domainPapameter_keyLength" export type SignatureAlgorithm = @@ -70,15 +79,6 @@ export type SignatureAlgorithm = | 'ecdsa_sha384_brainpoolP512r1_512' | 'ecdsa_sha512_brainpoolP512r1_512'; -export type Proof = { - proof: { - a: [string, string]; - b: [[string, string], [string, string]]; - c: [string, string]; - }; - pub_signals: string[]; -}; - export function castCSCAProof(proof: any): Proof { return { proof: { diff --git a/common/tests/genMockPassportData.test.ts b/common/tests/genMockPassportData.test.ts index 0bcd1fa3d..6b1792c77 100644 --- a/common/tests/genMockPassportData.test.ts +++ b/common/tests/genMockPassportData.test.ts @@ -1,8 +1,9 @@ import { expect } from 'chai'; import { describe, it } from 'mocha'; + import { genAndInitMockPassportData } from '../src/utils/passports/genMockPassportData.js'; import { parsePassportData } from '../src/utils/passports/passport_parsing/parsePassportData.js'; -import { PassportData, SignatureAlgorithm } from '../src/utils/types.js'; +import type { PassportData, SignatureAlgorithm } from '../src/utils/types.js'; const testCases = [ { dgHashAlgo: 'sha1', eContentHashAlgo: 'sha1', sigAlg: 'rsa_sha1_65537_2048' }, diff --git a/common/tests/scope.test.ts b/common/tests/scope.test.ts index 1fc2ccf5b..baa8c67df 100644 --- a/common/tests/scope.test.ts +++ b/common/tests/scope.test.ts @@ -1,5 +1,6 @@ import { assert, expect } from 'chai'; import { describe, it } from 'mocha'; + import { bigIntToString, formatEndpoint, diff --git a/common/tsup.config.ts b/common/tsup.config.ts index 4a1118e5b..368437429 100644 --- a/common/tsup.config.ts +++ b/common/tsup.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'tsup'; import path from 'path'; +import { defineConfig } from 'tsup'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); diff --git a/contracts/test/integration/commitmentRegistration.test.ts b/contracts/test/integration/commitmentRegistration.test.ts index 1301bb8ed..5c3eec9fd 100644 --- a/contracts/test/integration/commitmentRegistration.test.ts +++ b/contracts/test/integration/commitmentRegistration.test.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { TransactionReceipt, ZeroAddress } from "ethers"; import { ethers } from "hardhat"; import { poseidon2 } from "poseidon-lite"; -import { CIRCUIT_CONSTANTS, DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants/constants"; +import { CIRCUIT_CONSTANTS, DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants"; import { ATTESTATION_ID } from "../utils/constants"; import { deploySystemFixtures } from "../utils/deployment"; import { generateDscProof, generateRegisterProof } from "../utils/generateProof"; diff --git a/contracts/test/integration/vcAndDisclose.test.ts b/contracts/test/integration/vcAndDisclose.test.ts index c15d799c8..120cbc912 100644 --- a/contracts/test/integration/vcAndDisclose.test.ts +++ b/contracts/test/integration/vcAndDisclose.test.ts @@ -11,7 +11,7 @@ import { BigNumberish } from "ethers"; import { generateRandomFieldElement, getStartOfDayTimestamp, splitHexFromBack } from "../utils/utils"; import { Formatter, CircuitAttributeHandler } from "../utils/formatter"; import { formatCountriesList, reverseBytes, reverseCountryBytes } from "@selfxyz/common/utils/circuits/formatInputs"; -import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts/forbiddenCountries"; +import { getPackedForbiddenCountries } from "@selfxyz/common/utils/sanctions"; import { countries, Country3LetterCode } from "@selfxyz/common/constants/countries"; import fs from "fs"; import path from "path"; diff --git a/contracts/test/utils/deployment.ts b/contracts/test/utils/deployment.ts index 11a2e2dab..c10d87a94 100644 --- a/contracts/test/utils/deployment.ts +++ b/contracts/test/utils/deployment.ts @@ -1,6 +1,6 @@ import { Signer } from "ethers"; import { ethers } from "hardhat"; -import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants/constants"; +import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants"; import { genAndInitMockPassportData } from "@selfxyz/common/utils/passports/genMockPassportData"; import { getCscaTreeRoot } from "@selfxyz/common/utils/trees"; import { PassportData } from "@selfxyz/common/utils/types"; diff --git a/contracts/test/utils/deploymentV2.ts b/contracts/test/utils/deploymentV2.ts index 5c1fdf935..6bfdb1805 100644 --- a/contracts/test/utils/deploymentV2.ts +++ b/contracts/test/utils/deploymentV2.ts @@ -1,7 +1,7 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { Signer } from "ethers"; -import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants/constants"; +import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants"; import { genAndInitMockPassportData } from "@selfxyz/common/utils/passports/genMockPassportData"; import { getCscaTreeRoot } from "@selfxyz/common/utils/trees"; import { PassportData } from "@selfxyz/common/utils/types"; diff --git a/contracts/test/v2/discloseId.test.ts b/contracts/test/v2/discloseId.test.ts index 9221b9f75..0cc426b84 100644 --- a/contracts/test/v2/discloseId.test.ts +++ b/contracts/test/v2/discloseId.test.ts @@ -5,7 +5,7 @@ import { poseidon2 } from "poseidon-lite"; import { generateCommitment } from "@selfxyz/common/utils/passports/passport"; import { BigNumberish } from "ethers"; import { generateRandomFieldElement, getStartOfDayTimestamp } from "../utils/utils"; -import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts/forbiddenCountries"; +import { getPackedForbiddenCountries } from "@selfxyz/common/utils/sanctions"; import { countries } from "@selfxyz/common/constants/countries"; import { deploySystemFixturesV2 } from "../utils/deploymentV2"; import { DeployedActorsV2 } from "../utils/types"; diff --git a/contracts/test/v2/disclosePassport.test.ts b/contracts/test/v2/disclosePassport.test.ts index ae9f699f1..c92e5f729 100644 --- a/contracts/test/v2/disclosePassport.test.ts +++ b/contracts/test/v2/disclosePassport.test.ts @@ -6,7 +6,7 @@ import { poseidon2 } from "poseidon-lite"; import { generateCommitment } from "@selfxyz/common/utils/passports/passport"; import { BigNumberish } from "ethers"; import { generateRandomFieldElement, getStartOfDayTimestamp } from "../utils/utils"; -import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts/forbiddenCountries"; +import { getPackedForbiddenCountries } from "@selfxyz/common/utils/sanctions"; import { countries } from "@selfxyz/common/constants/countries"; import { deploySystemFixturesV2 } from "../utils/deploymentV2"; import { DeployedActorsV2 } from "../utils/types"; diff --git a/sdk/core/index.ts b/sdk/core/index.ts index 50701a278..13579e3a1 100644 --- a/sdk/core/index.ts +++ b/sdk/core/index.ts @@ -1,5 +1,5 @@ import { SelfBackendVerifier } from './src/SelfBackendVerifier.js'; -import { countryCodes } from '@selfxyz/common/constants/constants'; +import { countryCodes } from '@selfxyz/common/constants'; import { getUniversalLink } from '@selfxyz/common/utils/appType'; import { countries } from '@selfxyz/common/constants'; import type { AttestationId, VerificationResult, VerificationConfig } from 'src/types/types.js'; diff --git a/sdk/qrcode/components/SelfQRcode.tsx b/sdk/qrcode/components/SelfQRcode.tsx index 93ceab23f..15e4eb48b 100644 --- a/sdk/qrcode/components/SelfQRcode.tsx +++ b/sdk/qrcode/components/SelfQRcode.tsx @@ -4,10 +4,7 @@ import Lottie from 'lottie-react'; import CHECK_ANIMATION from '../animations/check_animation.json' with { type: 'json' }; import X_ANIMATION from '../animations/x_animation.json' with { type: 'json' }; import LED from './LED.js'; -import { - REDIRECT_URL, - WS_DB_RELAYER, -} from '@selfxyz/common/constants/constants'; +import { REDIRECT_URL, WS_DB_RELAYER } from '@selfxyz/common/constants'; import { v4 as uuidv4 } from 'uuid'; import { QRcodeSteps } from '../utils/utils.js'; import { diff --git a/yarn.lock b/yarn.lock index e8f63c239..bcfd555a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4486,6 +4486,8 @@ __metadata: "@openpassport/zk-kit-smt": "npm:^0.0.1" "@types/js-sha1": "npm:^0.6.3" "@types/node-forge": "npm:^1.3.10" + "@typescript-eslint/eslint-plugin": "npm:^7.0.0" + "@typescript-eslint/parser": "npm:^7.0.0" asn1.js: "npm:^5.4.1" asn1js: "npm:^3.0.5" axios: "npm:^1.7.2" @@ -4494,6 +4496,12 @@ __metadata: country-emoji: "npm:^1.5.6" country-iso-3-to-2: "npm:^1.1.1" elliptic: "npm:^6.5.5" + eslint: "npm:^8.57.0" + eslint-config-prettier: "npm:^9.1.0" + eslint-plugin-import: "npm:^2.29.1" + eslint-plugin-prettier: "npm:^5.1.3" + eslint-plugin-simple-import-sort: "npm:^12.1.1" + eslint-plugin-sort-exports: "npm:^0.9.1" ethers: "npm:^6.14.4" fs: "npm:^0.0.1-security" i18n-iso-countries: "npm:^7.13.0" @@ -4678,6 +4686,7 @@ __metadata: eslint-plugin-jest: "npm:^28.11.1" eslint-plugin-prettier: "npm:^5.2.6" eslint-plugin-simple-import-sort: "npm:^12.1.1" + eslint-plugin-sort-exports: "npm:^0.9.1" ethers: "npm:^6.11.0" expo-modules-core: "npm:^2.2.1" jest: "npm:^29.6.3" @@ -9559,7 +9568,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^7.1.1, @typescript-eslint/eslint-plugin@npm:^7.18.0": +"@typescript-eslint/eslint-plugin@npm:^7.0.0, @typescript-eslint/eslint-plugin@npm:^7.1.1, @typescript-eslint/eslint-plugin@npm:^7.18.0": version: 7.18.0 resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0" dependencies: @@ -9582,7 +9591,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:^7.1.1, @typescript-eslint/parser@npm:^7.18.0": +"@typescript-eslint/parser@npm:^7.0.0, @typescript-eslint/parser@npm:^7.1.1, @typescript-eslint/parser@npm:^7.18.0": version: 7.18.0 resolution: "@typescript-eslint/parser@npm:7.18.0" dependencies: @@ -13981,6 +13990,17 @@ __metadata: languageName: node linkType: hard +"eslint-config-prettier@npm:^9.1.0": + version: 9.1.2 + resolution: "eslint-config-prettier@npm:9.1.2" + peerDependencies: + eslint: ">=7.0.0" + bin: + eslint-config-prettier: bin/cli.js + checksum: 10c0/d2e9dc913b1677764a4732433d83d258f40820458c65d0274cb9e3eaf6559b39f2136446f310c05abed065a4b3c2e901807ccf583dff76c6227eaebf4132c39a + languageName: node + linkType: hard + "eslint-import-resolver-node@npm:^0.3.9": version: 0.3.9 resolution: "eslint-import-resolver-node@npm:0.3.9" @@ -14062,7 +14082,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-import@npm:^2.31.0": +"eslint-plugin-import@npm:^2.29.1, eslint-plugin-import@npm:^2.31.0": version: 2.32.0 resolution: "eslint-plugin-import@npm:2.32.0" dependencies: @@ -14127,6 +14147,26 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-prettier@npm:^5.1.3": + version: 5.5.3 + resolution: "eslint-plugin-prettier@npm:5.5.3" + dependencies: + prettier-linter-helpers: "npm:^1.0.0" + synckit: "npm:^0.11.7" + peerDependencies: + "@types/eslint": ">=8.0.0" + eslint: ">=8.0.0" + eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" + prettier: ">=3.0.0" + peerDependenciesMeta: + "@types/eslint": + optional: true + eslint-config-prettier: + optional: true + checksum: 10c0/7524e381b400fec67dd2bd1a71779c220a5410f0063cd220d144431f291ec800bee1985709ef0dd38d666d01e0e53bec93824063912784d4021db8473fafe73e + languageName: node + linkType: hard + "eslint-plugin-prettier@npm:^5.2.6": version: 5.5.0 resolution: "eslint-plugin-prettier@npm:5.5.0" @@ -14211,6 +14251,17 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-sort-exports@npm:^0.9.1": + version: 0.9.1 + resolution: "eslint-plugin-sort-exports@npm:0.9.1" + dependencies: + minimatch: "npm:^9.0.3" + peerDependencies: + eslint: ">=5.0.0" + checksum: 10c0/f1ef9f51bcf17848b863fd6948e186361d298f7b5fabdb0b324008e0f7a1df67370df7cafd956d573e2888033afb3f190bae56c5698a600e372d2fb0f92ea7d5 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -14252,7 +14303,7 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.19.0": +"eslint@npm:^8.19.0, eslint@npm:^8.57.0": version: 8.57.1 resolution: "eslint@npm:8.57.1" dependencies: @@ -19031,7 +19082,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.3, minimatch@npm:^9.0.4": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: