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..34bb1d78c 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'; @@ -16,6 +14,8 @@ import { PassportProvider } from './src/providers/passportDataProvider'; import { RemoteConfigProvider } from './src/providers/remoteConfigProvider'; import { initSentry, wrapWithSentry } from './src/Sentry'; +import 'react-native-get-random-values'; + initSentry(); global.Buffer = Buffer; 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; -/* 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 DEFAULT_DOE = undefined; + +export const DEFAULT_PNUMBER = undefined; + +export const ENABLE_DEBUG_LOGS = process.env.ENABLE_DEBUG_LOGS === 'true'; -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; + +/* 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 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..7b6bf8e3d 100644 --- a/app/index.js +++ b/app/index.js @@ -3,10 +3,6 @@ /** * @format */ -import './src/utils/ethers'; -import 'react-native-gesture-handler'; - -import { config } from '@tamagui/config/v2-native'; import React from 'react'; import { AppRegistry, LogBox } from 'react-native'; import { createTamagui, TamaguiProvider } from 'tamagui'; @@ -14,6 +10,10 @@ 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'; + const tamaguiConfig = createTamagui(config); LogBox.ignoreLogs([ diff --git a/app/package.json b/app/package.json index 746c57cbe..4fa9269d9 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 web:build && 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:fastlane": "bundle exec ruby -Itest fastlane/test/helpers_test.rb", @@ -175,6 +176,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..a8ecb6969 --- /dev/null +++ b/app/scripts/check-duplicate-headers.cjs @@ -0,0 +1,62 @@ +#!/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('*.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/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); - - 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; -}; - -// 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); - } - } +export interface StorageBackend { + getItem(key: string): Promise; + setItem(key: string, value: string): Promise; + removeItem(key: string): Promise; +} - return {}; - } -}; +export const LOCAL_OVERRIDES_KEY = 'feature_flag_overrides'; -export const setLocalOverride = async ( +export const clearAllLocalOverrides = 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..2c9bf9277 100644 --- a/app/src/components/Disclosures.tsx +++ b/app/src/components/Disclosures.tsx @@ -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 React from 'react'; +import { XStack, YStack } from 'tamagui'; + 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 { BodyText } from '../components/typography/BodyText'; import CheckMark from '../images/icons/checkmark.svg'; 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,18 +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 { 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 function SafeAreaProvider({ children }) { - return createElement(Fragment, null, children); -} +export const SafeAreaContext = createContext(initialWindowMetrics); -export function useSafeAreaInsets() { - return { top: 0, bottom: 0, left: 0, right: 0 }; -} +export const SafeAreaInsetsContext = createContext({ + top: 0, + bottom: 0, + left: 0, + right: 0, +}); -export function useSafeAreaFrame() { - return { x: 0, y: 0, width: 0, height: 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 SafeAreaView(props) { @@ -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 type { + PublicKeyDetailsECDSA, + PublicKeyDetailsRSA, +} from '@selfxyz/common/utils'; import { brutforceSignatureAlgorithmDsc, parseCertificateSimple, - 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 { 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,231 +129,178 @@ 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; - } +export const PassportContext = createContext({ + getData: () => Promise.resolve(null), + getSelectedData: () => Promise.resolve(null), + getAllData: () => Promise.resolve({}), + getAvailableTypes: () => Promise.resolve([]), + setData: storePassportData, + getPassportDataAndSecret: () => Promise.resolve(null), + getSelectedPassportDataAndSecret: () => Promise.resolve(null), + clearPassportData: clearPassportData, + clearSpecificData: clearSpecificPassportData, + loadDocumentCatalog: safeLoadDocumentCatalog, + getAllDocuments: safeGetAllDocuments, + setSelectedDocument: setSelectedDocument, + deleteDocument: deleteDocument, + migrateFromLegacyStorage: migrateFromLegacyStorage, + getCurrentDocumentType: getCurrentDocumentType, + clearDocumentCatalogForMigrationTesting: + clearDocumentCatalogForMigrationTesting, + markCurrentDocumentAsRegistered: markCurrentDocumentAsRegistered, + updateDocumentRegistrationState: updateDocumentRegistrationState, + checkIfAnyDocumentsNeedMigration: checkIfAnyDocumentsNeedMigration, + hasAnyValidRegisteredDocument: hasAnyValidRegisteredDocument, + checkAndUpdateRegistrationStates: checkAndUpdateRegistrationStates, +}); - console.log('Initializing native modules...'); +export const PassportProvider = ({ children }: PassportProviderProps) => { + const { _getSecurely } = useAuth(); - 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; - } - } + const getData = useCallback( + () => _getSecurely(loadPassportData, str => JSON.parse(str)), + [_getSecurely], + ); - console.warn('Native modules not ready after retries'); - return false; -} + const getSelectedData = useCallback(() => { + return _getSecurely( + () => loadSelectedPassportData(), + str => JSON.parse(str), + ); + }, [_getSecurely]); -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: [] }; - } + const getAllData = useCallback(() => loadAllPassportData(), []); - // 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 getAvailableTypes = useCallback(() => getAvailableDocumentTypes(), []); - const catalogCreds = await Keychain.getGenericPassword({ - service: 'documentCatalog', - }); - if (catalogCreds !== false) { - return JSON.parse(catalogCreds.password); - } - } catch (error) { - console.log('Error loading document catalog:', error); - } + const getPassportDataAndSecret = useCallback( + () => + _getSecurely<{ passportData: PassportData; secret: string }>( + loadPassportDataAndSecret, + str => JSON.parse(str), + ), + [_getSecurely], + ); - // Return empty catalog if none exists - return { documents: [] }; -} + const getSelectedPassportDataAndSecret = useCallback(() => { + return _getSecurely<{ passportData: PassportData; secret: string }>( + () => loadSelectedPassportDataAndSecret(), + str => JSON.parse(str), + ); + }, [_getSecurely]); -export async function saveDocumentCatalog( - catalog: DocumentCatalog, -): Promise { - await Keychain.setGenericPassword('catalog', JSON.stringify(catalog), { - service: 'documentCatalog', - }); + const state: IPassportContext = useMemo( + () => ({ + getData, + getSelectedData, + getAllData, + getAvailableTypes, + setData: storePassportData, + getPassportDataAndSecret, + getSelectedPassportDataAndSecret, + clearPassportData: clearPassportData, + clearSpecificData: clearSpecificPassportData, + loadDocumentCatalog: safeLoadDocumentCatalog, + getAllDocuments: safeGetAllDocuments, + setSelectedDocument: setSelectedDocument, + deleteDocument: deleteDocument, + migrateFromLegacyStorage: migrateFromLegacyStorage, + getCurrentDocumentType: getCurrentDocumentType, + clearDocumentCatalogForMigrationTesting: + clearDocumentCatalogForMigrationTesting, + markCurrentDocumentAsRegistered: markCurrentDocumentAsRegistered, + updateDocumentRegistrationState: updateDocumentRegistrationState, + checkIfAnyDocumentsNeedMigration: checkIfAnyDocumentsNeedMigration, + hasAnyValidRegisteredDocument: hasAnyValidRegisteredDocument, + checkAndUpdateRegistrationStates: checkAndUpdateRegistrationStates, + }), + [ + getData, + getSelectedData, + getAllData, + getAvailableTypes, + getPassportDataAndSecret, + getSelectedPassportDataAndSecret, + ], + ); + + return ( + + {children} + + ); +}; + +export async function checkAndUpdateRegistrationStates(): Promise { + // Lazy import to avoid circular dependency + const { checkAndUpdateRegistrationStates: validateDocCheckAndUpdate } = + await import('../utils/proving/validateDocument'); + return validateDocCheckAndUpdate(); } -export async function loadDocumentById( - documentId: string, -): Promise { +export async function checkIfAnyDocumentsNeedMigration(): 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); - } + const catalog = await loadDocumentCatalog(); + return catalog.documents.some(doc => doc.isRegistered === undefined); } catch (error) { - console.log(`Error loading document ${documentId}:`, error); + console.warn('Error checking if documents need migration:', error); + return false; } - return null; } -export async function storeDocumentWithDeduplication( - passportData: PassportData, -): Promise { - const contentHash = calculateContentHash(passportData); +export async function clearDocumentCatalogForMigrationTesting() { + console.log('Clearing document catalog for migration testing...'); 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'); + // 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`); + } + } - // 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; - } + // 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'); } - const metadata = catalog.documents.find( - d => d.id === catalog.selectedDocumentId, + // 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.', ); - 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 }; -}> { +export async function clearPassportData() { 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 }; + // 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`); } } - return allDocs; + // Clear catalog + await saveDocumentCatalog({ documents: [] }); } -export async function setSelectedDocument(documentId: string): Promise { +export async function clearSpecificPassportData(documentType: string) { const catalog = await loadDocumentCatalog(); - const metadata = catalog.documents.find(d => d.id === documentId); + const docsToDelete = catalog.documents.filter( + d => d.documentType === documentType, + ); - if (metadata) { - catalog.selectedDocumentId = documentId; - await saveDocumentCatalog(catalog); + for (const doc of docsToDelete) { + await deleteDocument(doc.id); } } @@ -357,44 +329,38 @@ export async function deleteDocument(documentId: string): Promise { } } +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))]; } -export async function migrateFromLegacyStorage(): Promise { - console.log('Migrating from legacy storage to new architecture...'); +// Helper function to get current document type from catalog +export async function getCurrentDocumentType(): Promise { const catalog = await loadDocumentCatalog(); + if (!catalog.selectedDocumentId) return null; - // 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'); + const metadata = catalog.documents.find( + d => d.id === catalog.selectedDocumentId, + ); + return metadata?.documentType || null; } // ===== LEGACY WRAPPER FUNCTIONS (for backward compatibility) ===== @@ -415,76 +381,67 @@ function getServiceNameForDocumentType(documentType: string): string { } } -export async function loadPassportData() { - // Try new system first - const selected = await loadSelectedDocument(); - if (selected) { - return JSON.stringify(selected.data); +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; } +} - // 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; - } +/** + * 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; + } - 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, + 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 storeDocumentWithDeduplication(passportData); - await Keychain.resetGenericPassword({ service }); - return passportDataCreds.password; + 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; } - } catch (error) { - console.log('Error in legacy passport data migration:', error); } + console.warn('Native modules not ready after retries'); 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 } = {}; +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 }) => { @@ -495,12 +452,100 @@ export async function loadAllPassportData(): Promise<{ return result; } -export async function setDefaultDocumentTypeIfNeeded() { - const catalog = await loadDocumentCatalog(); +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; + } - if (!catalog.selectedDocumentId && catalog.documents.length > 0) { - await setSelectedDocument(catalog.documents[0].id); + 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() { @@ -515,64 +560,67 @@ export async function loadPassportDataAndSecret() { }); } -export async function storePassportData(passportData: PassportData) { - await storeDocumentWithDeduplication(passportData); -} - -export async function clearPassportData() { +export async function loadSelectedDocument(): Promise<{ + data: PassportData; + metadata: DocumentMetadata; +} | null> { const catalog = await loadDocumentCatalog(); + console.log('Catalog loaded'); - // 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`); + 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; } } - // Clear catalog - await saveDocumentCatalog({ documents: [] }); -} - -export async function clearSpecificPassportData(documentType: string) { - const catalog = await loadDocumentCatalog(); - const docsToDelete = catalog.documents.filter( - d => d.documentType === documentType, + const metadata = catalog.documents.find( + d => d.id === catalog.selectedDocumentId, ); + if (!metadata) { + console.log( + 'Metadata not found for selectedDocumentId:', + catalog.selectedDocumentId, + ); + return null; + } - for (const doc of docsToDelete) { - await deleteDocument(doc.id); + const data = await loadDocumentById(catalog.selectedDocumentId); + if (!data) { + console.log('Document data not found for id:', catalog.selectedDocumentId); + return null; } -} -export async function clearDocumentCatalogForMigrationTesting() { - console.log('Clearing document catalog for migration testing...'); - const catalog = await loadDocumentCatalog(); + console.log('Successfully loaded document:', metadata.documentType); + return { data, metadata }; +} - // 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`); - } +export async function loadSelectedPassportData(): Promise { + // Try new system first + const selected = await loadSelectedDocument(); + if (selected) { + return JSON.stringify(selected.data); } - // 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'); - } + // Fallback to legacy system + return await loadPassportData(); +} - // 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 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 { @@ -616,136 +664,49 @@ interface IPassportContext { 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 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 const PassportContext = createContext({ - getData: () => Promise.resolve(null), - getSelectedData: () => Promise.resolve(null), - getAllData: () => Promise.resolve({}), - getAvailableTypes: () => Promise.resolve([]), - setData: storePassportData, - getPassportDataAndSecret: () => Promise.resolve(null), - getSelectedPassportDataAndSecret: () => Promise.resolve(null), - clearPassportData: clearPassportData, - clearSpecificData: clearSpecificPassportData, - loadDocumentCatalog: safeLoadDocumentCatalog, - getAllDocuments: safeGetAllDocuments, - setSelectedDocument: setSelectedDocument, - deleteDocument: deleteDocument, - migrateFromLegacyStorage: migrateFromLegacyStorage, - getCurrentDocumentType: getCurrentDocumentType, - clearDocumentCatalogForMigrationTesting: - clearDocumentCatalogForMigrationTesting, - markCurrentDocumentAsRegistered: markCurrentDocumentAsRegistered, - updateDocumentRegistrationState: updateDocumentRegistrationState, - checkIfAnyDocumentsNeedMigration: checkIfAnyDocumentsNeedMigration, - hasAnyValidRegisteredDocument: hasAnyValidRegisteredDocument, - checkAndUpdateRegistrationStates: checkAndUpdateRegistrationStates, -}); - -export const PassportProvider = ({ children }: PassportProviderProps) => { - const { _getSecurely } = useAuth(); - - const getData = useCallback( - () => _getSecurely(loadPassportData, str => JSON.parse(str)), - [_getSecurely], - ); - - const getSelectedData = useCallback(() => { - return _getSecurely( - () => loadSelectedPassportData(), - str => JSON.parse(str), - ); - }, [_getSecurely]); - - const getAllData = useCallback(() => loadAllPassportData(), []); - - const getAvailableTypes = useCallback(() => getAvailableDocumentTypes(), []); - - const getPassportDataAndSecret = useCallback( - () => - _getSecurely<{ passportData: PassportData; secret: string }>( - loadPassportDataAndSecret, - str => JSON.parse(str), - ), - [_getSecurely], - ); +} - const getSelectedPassportDataAndSecret = useCallback(() => { - return _getSecurely<{ passportData: PassportData; secret: string }>( - () => loadSelectedPassportDataAndSecret(), - str => JSON.parse(str), - ); - }, [_getSecurely]); +export async function migrateFromLegacyStorage(): Promise { + console.log('Migrating from legacy storage to new architecture...'); + const catalog = await loadDocumentCatalog(); - const state: IPassportContext = useMemo( - () => ({ - getData, - getSelectedData, - getAllData, - getAvailableTypes, - setData: storePassportData, - getPassportDataAndSecret, - getSelectedPassportDataAndSecret, - clearPassportData: clearPassportData, - clearSpecificData: clearSpecificPassportData, - loadDocumentCatalog: safeLoadDocumentCatalog, - getAllDocuments: safeGetAllDocuments, - setSelectedDocument: setSelectedDocument, - deleteDocument: deleteDocument, - migrateFromLegacyStorage: migrateFromLegacyStorage, - getCurrentDocumentType: getCurrentDocumentType, - clearDocumentCatalogForMigrationTesting: - clearDocumentCatalogForMigrationTesting, - markCurrentDocumentAsRegistered: markCurrentDocumentAsRegistered, - updateDocumentRegistrationState: updateDocumentRegistrationState, - checkIfAnyDocumentsNeedMigration: checkIfAnyDocumentsNeedMigration, - hasAnyValidRegisteredDocument: hasAnyValidRegisteredDocument, - checkAndUpdateRegistrationStates: checkAndUpdateRegistrationStates, - }), - [ - getData, - getSelectedData, - getAllData, - getAvailableTypes, - getPassportDataAndSecret, - getSelectedPassportDataAndSecret, - ], - ); + // If catalog already has documents, skip migration + if (catalog.documents.length > 0) { + console.log('Migration already completed'); + return; + } - return ( - - {children} - - ); -}; + 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); + } + } -export const usePassport = () => { - return useContext(PassportContext); -}; + 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 { +export async function saveDocumentCatalog( + catalog: DocumentCatalog, +): Promise { + await Keychain.setGenericPassword('catalog', JSON.stringify(catalog), { + service: 'documentCatalog', + }); +} + +export async function setDefaultDocumentTypeIfNeeded() { const catalog = await loadDocumentCatalog(); - if (!catalog.selectedDocumentId) return null; - const metadata = catalog.documents.find( - d => d.id === catalog.selectedDocumentId, - ); - return metadata?.documentType || null; + 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..11549183a 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/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 { 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..3c0a365a3 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/core'; +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..248fbe5e8 100644 --- a/app/src/stores/selfAppStore.tsx +++ b/app/src/stores/selfAppStore.tsx @@ -1,9 +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 { Socket } from 'socket.io-client'; +import socketIo from 'socket.io-client'; +import { create } from 'zustand'; + 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 { create } from 'zustand'; interface SelfAppState { selfApp: SelfApp | 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 { - 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 type { 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'; +import { + APP_DATA_FOLDER_ID, + MIME_TYPES, +} from '@robinbobin/react-native-google-drive-api-wrapper'; -export function useBackupMnemonic() { - return useMemo( - () => ({ - upload, - download, - disableBackup, - }), - [], - ); -} +export const STORAGE_NAME = Platform.OS === 'ios' ? 'iCloud' : 'Google Drive'; -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() { - if (Platform.OS === 'ios') { - await ios.disableBackup(); - return; +export async function upload(mnemonic: Mnemonic) { + if (!mnemonic || !mnemonic.phrase) { + throw new Error( + 'Mnemonic not set yet. Did the user see the recovery phrase?', + ); } - const gdrive = await createGDrive(); - if (!gdrive) { - // User canceled Google sign-in; skip disabling backup gracefully. - return; + 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(), + ); } - 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 blue100 = '#DBEAFE'; +export const blue600 = '#2563EB'; +export const blue700 = '#1D4ED8'; +// OLD +export const borderColor = '#343434'; + +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 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 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'; -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 separatorColor = '#E0E0E0'; 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/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. */ @@ -26,177 +28,71 @@ const requiredFields = [ ]; /** - * @notice Utility function to check if a number is within (start, end] range. - * @param start The start of the range (exclusive). - * @param end The end of the range (inclusive). - * @param value The number to check. - * @return True if value is within the range; otherwise, false. - */ -export const numberInRange = ( - start: number, - end: number, - value: number, -): boolean => { - 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. + * @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 attestation is verified successfully. - * @throws Error if the attestation document is improperly formatted or missing required fields. + * @return A promise that resolves to true if the PCR0 value is set in the contract, or false otherwise. */ -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'); +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}`, + ); } - for (let i = 0; i < attestationDoc.cabundle.length; i++) { - if (!numberInRange(0, 1024, attestationDoc.cabundle[i].length)) { - throw new Error('Invalid cabundle'); - } + // 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}`, + ); } - if (attestationDoc.public_key) { - if (!numberInRange(0, 1024, attestationDoc.public_key.length)) { - throw new Error('Invalid public_key'); - } - } + const celoProvider = new ethers.JsonRpcProvider(RPC_URL); - if (attestationDoc.user_data) { - if (!numberInRange(-1, 512, attestationDoc.user_data.length)) { - throw new Error('Invalid user_data'); - } - } + // Create a contract instance for the PCR0Manager + const pcr0Manager = new ethers.Contract( + PCR0_MANAGER_ADDRESS, + PCR0ManagerABI, + celoProvider, + ); - if (attestationDoc.nonce) { - if (!numberInRange(-1, 512, attestationDoc.nonce.length)) { - throw new Error('Invalid nonce'); - } + 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; } +} - const certChain = attestationDoc.cabundle.map((cert: Buffer) => - derToPem(cert), - ); +// 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; - 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 āš ļø'); + // 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}`, + ); } - console.log('TEE image hash verified'); - if (!(await verifyCertChain(AWS_ROOT_PEM, [...certChain, cert]))) { - throw new Error('Invalid certificate chain'); + // Validate hex string format + if (!/^[0-9a-fA-F]+$/.test(cleanHex)) { + throw new Error('Invalid hex string: contains non-hex characters'); } - 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; -}; + // Convert to bytes + return ethers.getBytes(`0x${cleanHex}`); +} /** * @notice Extracts the public key from a TEE attestation document. @@ -211,6 +107,21 @@ export function getPublicKey(attestation: Array) { 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). + * @param end The end of the range (inclusive). + * @param value The number to check. + * @return True if value is within the range; otherwise, false. + */ +export const numberInRange = ( + start: number, + end: number, + value: number, +): boolean => { + return value > start && value <= end; +}; + /** * @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'); + } - 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; + // 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); + } } -} -// 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; + if (!(attestationDoc.cabundle.length > 0)) { + throw new Error('Invalid cabundle'); + } - // 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}`, - ); + for (let i = 0; i < attestationDoc.cabundle.length; i++) { + if (!numberInRange(0, 1024, attestationDoc.cabundle[i].length)) { + throw new Error('Invalid cabundle'); + } } - // Validate hex string format - if (!/^[0-9a-fA-F]+$/.test(cleanHex)) { - throw new Error('Invalid hex string: contains non-hex characters'); + if (attestationDoc.public_key) { + if (!numberInRange(0, 1024, attestationDoc.public_key.length)) { + throw new Error('Invalid public_key'); + } } - // Convert to bytes - return ethers.getBytes(`0x${cleanHex}`); -} + 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 { + // 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; + } +}; 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..648c8e45f 100644 --- a/app/src/utils/proving/validateDocument.ts +++ b/app/src/utils/proving/validateDocument.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 { LeanIMT } from '@openpassport/zk-kit-lean-imt'; +import { poseidon2, poseidon5 } from 'poseidon-lite'; + import { API_URL, API_URL_STAGING, @@ -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,6 +43,108 @@ export type PassportSupportStatus = | 'registration_circuit_not_supported' | 'dsc_circuit_not_supported' | 'passport_supported'; +/** + * This function checks and updates registration states for all documents and updates the `isRegistered`. + */ +export async function checkAndUpdateRegistrationStates(): Promise { + const allDocuments = await getAllDocuments(); + for (const documentId of Object.keys(allDocuments)) { + try { + await setSelectedDocument(documentId); + const selectedDocument = await loadSelectedDocument(); + if (!selectedDocument) continue; + let { data: passportData } = selectedDocument; + if (!isPassportDataValid(passportData)) { + trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { + error: 'Passport data is not valid', + documentId, + }); + console.log(`Skipping invalid document ${documentId}`); + continue; + } + const migratedPassportData = migratePassportData(passportData); + if (migratedPassportData !== passportData) { + await storePassportData(migratedPassportData); + passportData = migratedPassportData; + } + const environment = migratedPassportData.mock ? 'stg' : 'prod'; + const documentCategory = migratedPassportData.documentCategory; + const authorityKeyIdentifier = + migratedPassportData.dsc_parsed?.authorityKeyIdentifier; + if (!authorityKeyIdentifier) { + trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { + error: 'Authority key identifier is null', + documentId, + documentCategory, + mock: migratedPassportData.mock, + }); + console.log( + `Skipping document ${documentId} - no authority key identifier`, + ); + continue; + } + await useProtocolStore + .getState() + [documentCategory].fetch_all(environment, authorityKeyIdentifier); + const passportDataAndSecret = await loadPassportDataAndSecret(); + if (!passportDataAndSecret) { + console.log( + `Skipping document ${documentId} - no passport data and secret`, + ); + continue; + } + + const { secret } = JSON.parse(passportDataAndSecret); + const isRegistered = await isUserRegistered(migratedPassportData, secret); + + // Update the registration state in the document metadata + await updateDocumentRegistrationState(documentId, isRegistered); + + if (isRegistered) { + trackEvent(DocumentEvents.DOCUMENT_VALIDATED, { + documentId, + documentCategory, + mock: migratedPassportData.mock, + }); + } + + console.log( + `Updated registration state for document ${documentId}: ${isRegistered}`, + ); + } catch (error) { + console.error( + `Error checking registration state for document ${documentId}: ${error}`, + ); + trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { + error: error instanceof Error ? error.message : 'Unknown error', + documentId, + }); + } + } + + 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<{ @@ -90,113 +194,6 @@ export async function checkPassportSupported( 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, @@ -249,6 +246,43 @@ export function generateCommitmentInApp( 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(); @@ -306,6 +340,67 @@ export function isPassportDataValid(passportData: PassportData) { 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)) { @@ -325,95 +420,3 @@ export function migratePassportData(passportData: PassportData): PassportData { } 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`. - */ -export async function checkAndUpdateRegistrationStates(): Promise { - const allDocuments = await getAllDocuments(); - for (const documentId of Object.keys(allDocuments)) { - try { - await setSelectedDocument(documentId); - const selectedDocument = await loadSelectedDocument(); - if (!selectedDocument) continue; - let { data: passportData } = selectedDocument; - if (!isPassportDataValid(passportData)) { - trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { - error: 'Passport data is not valid', - documentId, - }); - console.log(`Skipping invalid document ${documentId}`); - continue; - } - const migratedPassportData = migratePassportData(passportData); - if (migratedPassportData !== passportData) { - await storePassportData(migratedPassportData); - passportData = migratedPassportData; - } - const environment = migratedPassportData.mock ? 'stg' : 'prod'; - const documentCategory = migratedPassportData.documentCategory; - const authorityKeyIdentifier = - migratedPassportData.dsc_parsed?.authorityKeyIdentifier; - if (!authorityKeyIdentifier) { - trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { - error: 'Authority key identifier is null', - documentId, - documentCategory, - mock: migratedPassportData.mock, - }); - console.log( - `Skipping document ${documentId} - no authority key identifier`, - ); - continue; - } - await useProtocolStore - .getState() - [documentCategory].fetch_all(environment, authorityKeyIdentifier); - const passportDataAndSecret = await loadPassportDataAndSecret(); - if (!passportDataAndSecret) { - console.log( - `Skipping document ${documentId} - no passport data and secret`, - ); - continue; - } - - const { secret } = JSON.parse(passportDataAndSecret); - const isRegistered = await isUserRegistered(migratedPassportData, secret); - - // Update the registration state in the document metadata - await updateDocumentRegistrationState(documentId, isRegistered); - - if (isRegistered) { - trackEvent(DocumentEvents.DOCUMENT_VALIDATED, { - documentId, - documentCategory, - mock: migratedPassportData.mock, - }); - } - - console.log( - `Updated registration state for document ${documentId}: ${isRegistered}`, - ); - } catch (error) { - console.error( - `Error checking registration state for document ${documentId}: ${error}`, - ); - trackEvent(DocumentEvents.VALIDATE_DOCUMENT_FAILED, { - error: error instanceof Error ? error.message : 'Unknown error', - documentId, - }); - } - } - - console.log('Registration state check and update completed'); -} 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..0250fbb73 100644 --- a/app/tests/__setup__/@env.js +++ b/app/tests/__setup__/@env.js @@ -1,12 +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 DEFAULT_DOB = undefined; -export const IS_TEST_BUILD = false; +export const DEFAULT_DOE = undefined; + +// 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_PNUMBER = undefined; + +export const ENABLE_DEBUG_LOGS = false; + +// 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 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; +// 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 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..3782f6d64 100644 --- a/app/tests/__setup__/notificationServiceMock.js +++ b/app/tests/__setup__/notificationServiceMock.js @@ -1,18 +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 -/* global jest */ +export const RemoteMessage = {}; + +// 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 getFCMToken = jest.fn().mockResolvedValue('mock-fcm-token'); +// 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 */ // 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/proofHistoryStore.test.ts b/app/tests/src/stores/proofHistoryStore.test.ts index 667abf064..9501bf7a2 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(), 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/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..024fdfe74 --- /dev/null +++ b/common/.eslintrc.cjs @@ -0,0 +1,104 @@ +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', + }, + 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 @@ +// 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 { - 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, 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 from constants -export type { Country3LetterCode } from './src/constants/index.js'; - -// Utils exports +// Type exports export { - initPassportDataParsing, + EndpointType, + Mode, + SelfAppBuilder, + bigIntToString, + brutforceSignatureAlgorithmDsc, + buildSMT, + calculateUserIdentifierHash, findStartPubKeyIndex, - generateCommitment, - generateNullifier, + formatEndpoint, + formatMrz, + genAndInitMockPassportData, genMockIdDoc, - generateMockDSC, genMockIdDocAndInitDataParsing, - genAndInitMockPassportData, - parseDscCertificateData, - brutforceSignatureAlgorithmDsc, - parseCertificateSimple, - initElliptic, - getSKIPEM, - formatMrz, - getCircuitNameFromPassportData, - calculateUserIdentifierHash, - getSolidityPackedUserContextData, - getLeafCscaTree, - getLeafDscTree, - buildSMT, 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 { + customHasher, flexiblePoseidon, - hash, getHashLen, - customHasher, + hash, packBytesAndPoseidon, } from './src/utils/hash.js'; diff --git a/common/package.json b/common/package.json index 9b8eacb00..b624938f2 100644 --- a/common/package.json +++ b/common/package.json @@ -367,6 +367,10 @@ "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", @@ -405,6 +409,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..c38f22e48 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 { dirname, join, resolve } from 'path'; import { existsSync } 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); 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 DEFAULT_MAJORITY = '18'; + +export const DEFAULT_RPC_URL = 'https://mainnet.optimism.io'; + +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 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 MAX_BYTES_IN_FIELD = 31; -export type document_type = 'passport' | 'id_card'; +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,12 +115,9 @@ 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_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 MAX_DATAHASHES_LEN = 320; -export const OFAC_TREE_LEVELS = 64; +export const MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH = 40; export const MAX_PADDED_ECONTENT_LEN: Partial> = { sha1: 384, @@ -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..5b2d44643 100644 --- a/common/src/constants/index.ts +++ b/common/src/constants/index.ts @@ -1,34 +1,32 @@ +export type { Country3LetterCode } from './constants.js'; export { - TREE_URL, - TREE_URL_STAGING, API_URL, API_URL_STAGING, - WS_DB_RELAYER, - WS_DB_RELAYER_STAGING, + DEFAULT_MAJORITY, + ID_CARD_ATTESTATION_ID, + PASSPORT_ATTESTATION_ID, PCR0_MANAGER_ADDRESS, RPC_URL, - PASSPORT_ATTESTATION_ID, - ID_CARD_ATTESTATION_ID, - DEFAULT_MAJORITY, + TREE_URL, + TREE_URL_STAGING, + WS_DB_RELAYER, + WS_DB_RELAYER_STAGING, attributeToPosition, attributeToPosition_ID, countryCodes, } from './constants.js'; - -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, CSCA_TREE_URL_STAGING_ID_CARD, + 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, } from './constants.js'; +export { commonNames, countries } from './countries.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,114 +72,206 @@ export const vkey_vc_and_disclose = { ], IC: [ [ - '17675992131793847472607582103290528032110944356332065253938771650575512637150', - '3334529637547487349647542974294469269353530698716058653313633946852810587219', + '8080423910694661461576427977746141048179131345871564682235127365538915251175', + '15077260046277123685110202133518064301144478802752095822556731494534324607918', '1', ], [ - '4073040853156698088579125842860781690905988024237850240402200045640356184109', - '4679018198189152660097843382950080652306538412548068412033586660232563193013', + '16011015387972163546219334947336021918538394305813640145299036166236111586389', + '19422172264639146231714658451804174650252945098828793769061895536286758622279', '1', ], [ - '15776533017793451196514777082124337882856102873453705308829629571923075424417', - '14232490066350175683786572931273695352468015232518411187366417165893912115230', + '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: [ [ - '1180945442359952286721463819274447148908421750703875389048309699338667338411', - '6539452064449720183898998074274190026127754682197287334130838477279315254881', - '1', + '6375614351688725206403948262868962793625744043794305715222011528459656738731', + '4252822878758300859123897981450591353533073413197771768651442665752259397132', ], [ - '13806944210187274185057734594353372528156342424366258241133564778474054935868', - '7800429229653987231645474546639647711164156063512193001130226490464812892063', - '1', + '10505242626370262277552901082094356697409835680220590971873171140371331206856', + '21847035105528745403288232691147584728191162732299865338377159692350059136679', ], + ['1', '0'], + ], + vk_gamma_2: [ [ - '5522822939351592924817275047393709421174176326835572758552827440001401610167', - '11245123080087310106980015850199801755152167910673823675982721730905258212233', - '1', + '10857046999023057135944570762232829481370756359578518086990519993285655852781', + '11559732032986387107991004021392285783925812861821192530917403151452391805634', ], [ - '5627481952482064829757269526369949776646066892934683965609154755505296555965', - '4545484539969611530913200236731153733781933150464540998302850238315475023119', - '1', + '8495653923123431417604973247489272438418190587263600148770280649306958101930', + '4082367875863433681332203403145435568316851327593401208105741076214120093531', ], + ['1', '0'], + ], + vk_delta_2: [ [ - '20423400711755442910136460433293470952040919967735945317336472158180565467951', - '6378416884992747212393622109218662385874688399353342355554228954581250524736', - '1', + '8403974525672515951605465754909425916978281298593354504437469807907113049853', + '12233134836151850512596961158180983853133742935319340320561432564845137384819', ], [ - '4373305143599295486105733121446353133863889257489542032576171376300323541304', - '4026684213449689055353486192784741961832172326825055022163307876785200324637', - '1', + '3209155548902127778431906050698597513646227271655778722256683596743569531044', + '16830777068052670490128170305087202969267881418665601837992321846223880096264', ], + ['1', '0'], + ], + vk_alphabeta_12: [ [ - '5068833407180337147676969453261920994101725947652623057924700035380226397907', - '11087944526730366835444251823404529324958715143332979454630299083515861157072', - '1', + [ + '2029413683389138792403550203267699914886160938906632433982220835551125967885', + '21072700047562757817161031222997517981543347628379360635925549008442030252106', + ], + [ + '5940354580057074848093997050200682056184807770593307860589430076672439820312', + '12156638873931618554171829126792193045421052652279363021382169897324752428276', + ], + [ + '7898200236362823042373859371574133993780991612861777490112507062703164551277', + '7074218545237549455313236346927434013100842096812539264420499035217050630853', + ], ], [ - '21831082589187376499865612459731026493616836999924912561891098220429445525521', - '10842131628581855605843884617846867779319917434679079988533026500378178602950', - '1', + [ + '7077479683546002997211712695946002074877511277312570035766170199895071832130', + '10093483419865920389913245021038182291233451549023025229112148274109565435465', + ], + [ + '4595479056700221319381530156280926371456704509942304414423590385166031118820', + '19831328484489333784475432780421641293929726139240675179672856274388269393268', + ], + [ + '11934129596455521040620786944827826205713621633706285934057045369193958244500', + '8037395052364110730298837004334506829870972346962140206007064471173334027475', + ], ], + ], + IC: [ [ - '14267596758527912595273989225764996478640991994242422111486793250630191081468', - '18694764448853871920117677375229746676709722363749986739355723996303104296068', + '16231288969314859968324689058413762096922845561563844884666690734569347763082', + '5910239835236935696830364945179549816839705839711095023727334010208312533092', '1', ], [ - '10612001726856359227588948695393307655456532716872467807420271676133645773510', - '1540081878138518609133189078681275557428892906987071405333678709982239141734', + '6260576084748320398294355533743833076081106851106584793995576172400792840042', + '21319331746978162427565186170902229291674424766799571029877219503610111212711', '1', ], [ - '7645334938708591281146159611078021141015093576924464549682793344782173540643', - '5041018151127409344023520612676035165867301524846720998477901879854547090576', + '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: [ [ - '8883975772061177470672187114470132395286660826025700056750575280427708604951', - '8320685858262483078856712401649683261512346806365950058502541940795537677566', - '1', + '6375614351688725206403948262868962793625744043794305715222011528459656738731', + '4252822878758300859123897981450591353533073413197771768651442665752259397132', ], [ - '19770351503660034123641636433521671083703252249281883512457304565751018243912', - '1942705439280745753504371271759370270294305194237531295574972095513458387941', - '1', + '10505242626370262277552901082094356697409835680220590971873171140371331206856', + '21847035105528745403288232691147584728191162732299865338377159692350059136679', ], + ['1', '0'], + ], + vk_gamma_2: [ [ - '15226311176844690062341353072910557091317547496535044902570312644662561307949', - '19296708345054645579869153897992480989971014206920627404347102350029660305578', - '1', + '10857046999023057135944570762232829481370756359578518086990519993285655852781', + '11559732032986387107991004021392285783925812861821192530917403151452391805634', ], [ - '5436668919010108586842015267897845254611520180011711994231132539697114737868', - '21034924479027562029885591632326051974733967422882023235118894616793689864649', - '1', + '8495653923123431417604973247489272438418190587263600148770280649306958101930', + '4082367875863433681332203403145435568316851327593401208105741076214120093531', ], + ['1', '0'], + ], + vk_delta_2: [ [ - '17617503314542022213516716240700336131305032342072938364934850750624139958532', - '7535025988359437238238236154472398858799932717035093350843709570356278900481', + '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', ], [ - '15079990665367846194463669410480448234210628628217480894032215566550223721508', - '14422402677444824910876930195543885009950058186617851677855759748496927334854', + '9406714425071299321475031445293798140218018985951799864352153321364734136304', + '16190677786472274760082919772279780726948507745938757138812410377304364738549', '1', ], [ - '15949464882252583714786988301769970813994683305898379568444041314948594948891', - '10982667165993413891579207656117800635412428118849500079522502265949839258411', + '17636847760071529621476637340355013609204110513013412005293780563117471695582', + '16269304104382842638426610869142550901209834485529174763447294867648099217552', '1', ], ], }; -export const vkey_prove_rsa_65537_sha256 = { +export const vkey_prove_rsa_65537_sha1 = { protocol: 'groth16', curve: 'bn128', nPublic: 51, @@ -212,12 +304,12 @@ export const vkey_prove_rsa_65537_sha256 = { ], vk_delta_2: [ [ - '14621056654033030159111384006506361438733969388519830162058188044378001616013', - '13712669930807027066432455963920004753804575153306478154082225172540894791697', + '7767865626282137552053116878448676615653553100613770101292138976638057195328', + '13379959127604507710191055311032733007010420304353803759588767791481393783660', ], [ - '21664748003830939002021923557411259243652793372223608877730406967860908232579', - '8648865297679023017392575432725553399779724355967143745086353612497178809510', + '16190014115494404913441211696178044944473076596471739548251287618437714007543', + '2710125808170497015419675534311358503570247628118813069894324761163552803730', ], ['1', '0'], ], @@ -253,269 +345,269 @@ export const vkey_prove_rsa_65537_sha256 = { ], IC: [ [ - '18837847330460101767470685001162782791541723452908436811770248703422385774364', - '13924593305026026107933010319328520261235500792223955967488219194416191858218', + '12777655693249209416712107993087189792780613255026639548033220239353319507514', + '16217477625934870825724954988135833456681053249558601235245038772258534028167', '1', ], [ - '18499035744135408535006498619187878301564976457052297587006457806425510673636', - '7226548414236873699673490761763172101403491901987747605307552092067988817369', + '18281429337549678783625065588205106488825779451914837329476321186876916769940', + '4701256080374580856471074902005485054228639622075051076292459849398178012004', '1', ], [ - '15076998223900447147424484817648709178376766854878255045911605115135846913714', - '1588555885165810180157418868205800830691130731692806042865483039743264904188', + '2425739937561298611971116767832743897185360524106467407711043129441892080674', + '2718633122069381145354259157371484537779233551418581110829419080409436767290', '1', ], [ - '9963925548680369694281529428493633860798787720411551209962187028714238599244', - '15230355743339633214614822894639457206342798359948816221317666466364056693778', + '19411623441769500040200042733637476578915526506552806760379860526467939488365', + '17848885463675579983626064217469622611327874683149224790578757146972644601245', '1', ], [ - '6950503983449781600992674217118280101447858351713896494323644937055764792179', - '7808371895612695836144162291280415010167996463807346279391316375701199123656', + '14676900487256424041148397883245356822883206919417322210995718994665405897598', + '7259055665299325444790049890763168144658364828059153728379504459733757201514', '1', ], [ - '13087819526106045804491969868083814032877953990371363285347311427240882433405', - '11966315196652888498918569284209954760513051208513943007355755386005245500621', + '6117651019764862467501422682514564800838858627775329687396387844429374886315', + '17111552636587283859012720520596751996224822634832520897472299395135503233946', '1', ], [ - '12282094920874016072527195101275592151196471143933884133850581178083772252468', - '15235775181834492665531005890809584611442500045185485789836774473981675243021', + '4775199183335309176074436997060802875228856520294068336837155624353298075166', + '5368013147446825132342427591112886554184872517822778283618513583767971711539', '1', ], [ - '21812893128244174490845186114318384111309631750779790216970477092597858785848', - '2589668621973063987240457489694488373626065998506803531328282066733461219789', + '17611549023318814036158949627420749596363327910934848180958945125716379678633', + '16661407052909726112631950204493267186977404996446674253994017961020993124292', '1', ], [ - '19736662517096924810330699257646097752878927062458868611606640349386574406597', - '4060072089820676848549092831416954645358719303909012118727092426282594652097', + '6630958864446664155558929014561025645498473927670144550521502327725859613088', + '10257469984330121170395704593208063739108953479394918968437955926014233018587', '1', ], [ - '11719045032278320337718855450887707488077032991245401830404333912363712928329', - '20429459895397450814127488292309624504089765803886468608012330383192001870669', + '12314871934741681433146131771411344537025697850773570714874192642287961794040', + '11470641693021346634121704531546293682033700932834166007717299571141315964296', '1', ], [ - '19004300167179661071877369317533970354840401420580304563087979100558811364752', - '14022355176806855344226798992865107732568349801838395169345899640004492990780', + '17097520470063383211553809058078460869174970589873134129415851040383130333868', + '3617410703082151598233457365000115489898359866991552656731389720357009506668', '1', ], [ - '13025111431804865430474042749794279094175996106474252922476866200732597336046', - '19194822113343343818445001979787110041399069657752328387546318167620461948345', + '7995417848192061085486637100808073274863048353859078661827065985891966756120', + '4543112048816912555547692960060663851497978469581464293790542002494818834260', '1', ], [ - '9281420432070758003455252378726458803842106759942105235771363724299995612746', - '10868451960138543140016636651288606302763103558353989956931009739891356663931', + '3756900937326680149692808490693121547443365485577720103369307683463757986098', + '14701792485185248475852326061659848677736717120698114532671189451547823594965', '1', ], [ - '6881336186311591694695019764740377864808644241540598555149034884273494350187', - '2786626651294071079928778385820392810695323510322088767264869337774915531398', + '2966021871375366762273629675645737426551227620306109328201701624367415707280', + '15997135187727574228071300496248407915497008568203566233193410028917630860464', '1', ], [ - '3868887205879058647589024065476442552380444440663584200982400886666148790886', - '11551675778288798821641950282239822965295574518488993089870363480882633402459', + '17027313269709603123828390341598331616557244050112644371416325010074486292816', + '4657784652544071504601293080754403918624898986674650386036985705478578376119', '1', ], [ - '15813415033010213936605839865891774355917576449019813469202169386353113595525', - '1870609980383477505308305636362868336100621908783112905201075702227453404515', + '5211722591651112972720538784064021558522564518224357221875632903004720346783', + '15742204565545957911014582041244770260769926583803919227059083652919049730381', '1', ], [ - '21157749399797355849483685908557759044725963489573087916410489368331599307794', - '14854788610114730057746604308209140307245160608186995032258954670144804516951', + '5091605874052523088200241842523496628084769006070972915274983313763081126766', + '21474847326844642314239645534523245713256826298739688218937419867864698959779', '1', ], [ - '16385822015903726360112736921395358523788300915122301306552678753856007139950', - '21852835770022094372574175833966771172218278214837381092449524015831270082443', + '18466057288766107000468952920760119321796410330419368326003100447101925793786', + '5920606244125934806657310787265193000131580390864481188584819879538853482281', '1', ], [ - '19248229849556541284232787022946438125257193537403487445681116918406935705772', - '6793532544675704467666654331031442502924492451846685587791069517168976618998', + '21509325652758209362413311112471261259307112982374947467932917809751309000526', + '13286705263216046341975269592279458440778273963623598404607601099248324188908', '1', ], [ - '18836493774186273716542303051216191129399492669285916094819482474147823428307', - '6509216046298943932406178827402599665125001577776608255883440730727474111293', + '8391911826807779399262868348144718222484978670070642761125918248704307215243', + '20829387450476090528086150189380641874166027520435836903168711027857834682379', '1', ], [ - '2809621426118306485690309749022510198095154893010698079349725235989206182716', - '4497399751266171421893644701436353917277499549766569195676686979408432567040', + '10308997289794566977249388187255854878260240915860603487914719765647078498291', + '12280908200199878030330620606331082617695439141243951739397701814658178859262', '1', ], [ - '6170933041071274746144995882643288027398824461699676189537821814335178617455', - '11939422270083845120808849126065865852360008142577598821317493776193010862067', + '12040539727496586957960173061521015630221765197931159078927761119588446778150', + '2540554208828202089770774675711401774701054560436821538472899158159947550723', '1', ], [ - '13907558045406644600787852336772673795175470348585502650967694884336926510609', - '10402183670236567810177022075839926456374913570620093187656925228836601635112', + '8674681186665416986529459603594515985270648914040860198530018688586468739184', + '2922648496015481382891108426388542361533195830896382924167133037160307926328', '1', ], [ - '9838642093868354522737246454481472310758813632867424849018704154654047098002', - '14170682187871718359141978372219783598112313240689881926254383115412693527146', + '12791502613501171561402018295790626037745190448272714798200223998530047903691', + '1359301284518543057703091464796057321763689133404994072689579681018356237361', '1', ], [ - '19268808621727088609886330192714416713650881167710481846410131194840574819112', - '4548738826497792767156172897040645223030132393843932100535275236775868885881', + '14257140672245886319024113480797843997942998521954322000391660420569000858100', + '931638430590457866489094365605722998198173485375039483139340820658602958580', '1', ], [ - '1232597044718469636783882051858339480293220683836150648093038756719345792733', - '4164564286518446421261975868208035039046547841927122039276997213757603100039', + '11488467566268691139971566095659365689414015135707565311303194014615872481483', + '20487239394947568711064905448392392146361011705838171243886554873644545982261', '1', ], [ - '5294620535222419533372104895954254795204154607883725935765987312986783187633', - '1510029808556715440734681023178490974005286595460173200541383072987550292273', + '5492422981890220759189319954052700692915352821686373947648192948311197008352', + '12796998191561316495015448110770097976697365157983089769123617960344434619998', '1', ], [ - '7913521827196573900757966679883707039843053961079282485559952654357012865178', - '12822272309007319462325152966219603030360768804371467102819817670179571110561', + '4866492870738445950615249676245502512313594183350835469598594973010131507294', + '17668941544395275808074254138261827419949162535979562938965487676493630502720', '1', ], [ - '11741856835780496736124514582563930032103983891200913422564159143798432491425', - '1186685996713398101844576210414267184996946163374847451849537513476110807467', + '14853384979398379314295334371899200413470291040002867448462371744205149436560', + '10541812807867053678938385613014678417111587037799277003145653793007559656467', '1', ], [ - '3848448520214659071169420881995284083054226208336229906265856219238707518565', - '12381374183899166684143029657071579470205894330864920551795150885160592446021', + '7532274798946570828439107115778581874085987532252209788649107842436685148991', + '8378961165245427388698069861458061686400463018591132493441756275800820200097', '1', ], [ - '2729354333883753415251820919113586347364808857675492186344760048485902311739', - '15156786867835927048585286604426887268249585171019125857459307501585887100974', + '17652852850353378291275142281632418914698559011300288731791689764453417590233', + '19771873863468751135021176106812718988689642760280229048376135472722047436990', '1', ], [ - '16327260115537188860691343668951285278159950664733445048928882023437126306794', - '15469859044222521064134805435371078520376095849473017636140479838581816450418', + '17438277520407223611774219376299156178695967895810991398095289034848563104314', + '3761123047764825106059710030718834660728737096571520961687041838797303469684', '1', ], [ - '9716588579885449992683804112491878761164620027004437304769643243506657789021', - '4292706684411927078028017507044791383954395251541518728702327216870791282903', + '5918452633200782136344878764415470407513083541948272866677449494484586220042', + '10604690725100593653005054223013707334150993431570760479436879031019147461045', '1', ], [ - '3831081473705850109604085868551425990958661808755750553828925508405362814908', - '7692088199943626776992448758764793963791676479101773985328943563508103917662', + '8259542419042092099816445422703058848035325374263840891089862676744764438125', + '17907757818862857645822205483286406489986719785490893298511469190864212380141', '1', ], [ - '14553842600721830593567536989022992714300892536915530699049444789457197924996', - '1034992513758432733967666097255620384731263408100321609845288946444012846161', + '20139923441584267759032148955097012875446993900262813022506144977495740234228', + '633188859083836213367398262855538919396980856067775018686782148320512937369', '1', ], [ - '8298955061619332575841977271384783772861434116506072222428896327020549590778', - '3417308972480405311337590188121737420812511616327499449644814830511455229125', + '21726639048837222608844226079734646967492836503699042863220596000459992053157', + '6424371129159802925649264208731956005303634651380896587229707562692220637803', '1', ], [ - '17492972350290531881303108441024194391834883043603735002548578305198527539792', - '20178093813925833001632123131014833838779278549203596933431582561790322377177', + '17041198497819316001571613782102427788635802538100517060467338190956934469546', + '11217287933652083481750768335932222403857834316645402517026868243277473458983', '1', ], [ - '20266961211116442799505089820504252966764415195756292869126528880324253997340', - '12721789314780990071009310805499247522750756905074142078629315686295071200692', + '19034834875875788782314876465727853947365075993855038719393084504593990363060', + '9086146571834397542121368889586301285063986605518485332147871865756094378099', '1', ], [ - '2848503208668242872636925431606615634594079979671099350197778718883635724263', - '4654302785281536218111627060020949573498492991361181450337516475942903887229', + '21093653561260126160341081475039540954188474477422498093525244396180181823670', + '13535501879576063105683047927635458524340467725253896927409746072645845079357', '1', ], [ - '8117348408366486765069198284524473502421701109551785034877096925063881017611', - '9240711853483292534294774801520720851385345818400239892620763606707852365913', + '15419644639429111779611512596775879470184968489398755763696579225869234243113', + '5888912982738459212046512719300383315430256384067733913361522421989057607660', '1', ], [ - '14726850444695139754420062135396416349332736703641747713568870234740172750594', - '19695982391909446471699547638880558431250407146813365085967041143148712990281', + '8021794565280119378214742351891355557406787634859090731496224811528919654543', + '18447147251619727046151948885356377562632348273395416712975164631737556908769', '1', ], [ - '10713887585841823429966077588975547340632721253524339816841192298502141129142', - '15875414001171419054582031537391697841529253632260086276963072228015908768572', + '3237112745142418943048154434679101631739412035974433745323987899717876176195', + '18366905927821072207610344944020849537376768169403475648963725058424582607207', '1', ], [ - '8726527790501042190955587753490550968193378562596306669177536293797796528295', - '2567561473507954539369683410861518721775690943462480886913419763755359623122', + '11103803864515131888867176971476009681201304848017317082097071924299524667237', + '11568903548692180379468059688713630672633914822600904309527038031667221969584', '1', ], [ - '20228003583559059699534488013186856436814319213991227118734829746090088629768', - '3062194835746234285630044060950549287869018179041416439415198981293605738893', + '7093177952754259965934651775168409530632125523254544611522122675834921490787', + '6527089445662238533285374041077440315301392384598470870650322831725955444547', '1', ], [ - '15326430247878492538731905607224792134839791639951162876881030896580260626530', - '3286497031007325457398280838270081812804433848003710004921976210893659583713', + '3227191841223944786060447942066038248540641877454960350047529138549493501903', + '10552339256116188971981234910367616983197445292206817485485035773727709784879', '1', ], [ - '21172455070651382026237320306432651725116860744924112007014279936313214413699', - '3124713746257151433204645213247841462846602236377570194562035843093051589347', + '989674451930098801894244326482852910581289308023468293119709273655157020723', + '11594716043590652193446947727280371336298547049038778564922528212931403014494', '1', ], [ - '586329559940161576696101534294134289580198770923725856103144741895937907876', - '8549358505942194684807970498040082081114943321990836277323952699459596852004', + '1886419919944054684680487851321230264354281745871778147085839004172555969572', + '16195110947725549843609790504068798852157786906403317729323982518077292241186', '1', ], [ - '10801780763283804426115126946120589447640285117455543080483767206343304180554', - '10884989352940730579839064046335509450313089154054617399581142310521031798261', + '15971113544867520289327044418603931629198616653529241341627397537269945935156', + '9448786879019650516172264951032976514419166206838957913033559142081219556746', '1', ], [ - '7642110729948206734175115603784267089466490166359943206653870027902551255488', - '11475699997476077360759438942765927072551540977873657634945729769964213394775', + '6091812380673627804034048532221266570681201403948345981699207852441234617237', + '4675463611373415464797065834003346370383503427109900660803562111395705316246', '1', ], [ - '6477632809823450072672399006410105825115578703752819060774243488278595863752', - '5842794419924960246061189125669290725098229148434953995106895999043038757426', + '14169123442478678228228176081430768043603215308343287642007090866096807228539', + '1425153510097175478307414428375512221296123855290017144964249395278961088177', '1', ], [ - '9851792234521580273104967216407455160006004510538086041246868797144654998160', - '11682999507634805671497034306879162257155282925402112526209517483082417200425', + '13010207937248020322435171834251494123777437279612504331350246242030974349076', + '5248384367299937265299632334272213949440138266550956975104163220131220896247', '1', ], [ - '7849825230838163134393354944881831816101066439604289649483128481654599764130', - '19425402357687722197365422640263552055101566124507367803085779467136114966243', + '1876053550778302219301374295745644650670485846429706087305814312472418356024', + '20649094119021787290717550348242625288049253059318330228537390183385105472845', '1', ], ], }; -export const vkey_prove_rsa_65537_sha1 = { +export const vkey_prove_rsa_65537_sha256 = { protocol: 'groth16', curve: 'bn128', nPublic: 51, @@ -548,12 +640,12 @@ export const vkey_prove_rsa_65537_sha1 = { ], vk_delta_2: [ [ - '7767865626282137552053116878448676615653553100613770101292138976638057195328', - '13379959127604507710191055311032733007010420304353803759588767791481393783660', + '14621056654033030159111384006506361438733969388519830162058188044378001616013', + '13712669930807027066432455963920004753804575153306478154082225172540894791697', ], [ - '16190014115494404913441211696178044944473076596471739548251287618437714007543', - '2710125808170497015419675534311358503570247628118813069894324761163552803730', + '21664748003830939002021923557411259243652793372223608877730406967860908232579', + '8648865297679023017392575432725553399779724355967143745086353612497178809510', ], ['1', '0'], ], @@ -589,263 +681,263 @@ export const vkey_prove_rsa_65537_sha1 = { ], IC: [ [ - '12777655693249209416712107993087189792780613255026639548033220239353319507514', - '16217477625934870825724954988135833456681053249558601235245038772258534028167', + '18837847330460101767470685001162782791541723452908436811770248703422385774364', + '13924593305026026107933010319328520261235500792223955967488219194416191858218', '1', ], [ - '18281429337549678783625065588205106488825779451914837329476321186876916769940', - '4701256080374580856471074902005485054228639622075051076292459849398178012004', + '18499035744135408535006498619187878301564976457052297587006457806425510673636', + '7226548414236873699673490761763172101403491901987747605307552092067988817369', '1', ], [ - '2425739937561298611971116767832743897185360524106467407711043129441892080674', - '2718633122069381145354259157371484537779233551418581110829419080409436767290', + '15076998223900447147424484817648709178376766854878255045911605115135846913714', + '1588555885165810180157418868205800830691130731692806042865483039743264904188', '1', ], [ - '19411623441769500040200042733637476578915526506552806760379860526467939488365', - '17848885463675579983626064217469622611327874683149224790578757146972644601245', + '9963925548680369694281529428493633860798787720411551209962187028714238599244', + '15230355743339633214614822894639457206342798359948816221317666466364056693778', '1', ], [ - '14676900487256424041148397883245356822883206919417322210995718994665405897598', - '7259055665299325444790049890763168144658364828059153728379504459733757201514', + '6950503983449781600992674217118280101447858351713896494323644937055764792179', + '7808371895612695836144162291280415010167996463807346279391316375701199123656', '1', ], [ - '6117651019764862467501422682514564800838858627775329687396387844429374886315', - '17111552636587283859012720520596751996224822634832520897472299395135503233946', + '13087819526106045804491969868083814032877953990371363285347311427240882433405', + '11966315196652888498918569284209954760513051208513943007355755386005245500621', '1', ], [ - '4775199183335309176074436997060802875228856520294068336837155624353298075166', - '5368013147446825132342427591112886554184872517822778283618513583767971711539', + '12282094920874016072527195101275592151196471143933884133850581178083772252468', + '15235775181834492665531005890809584611442500045185485789836774473981675243021', '1', ], [ - '17611549023318814036158949627420749596363327910934848180958945125716379678633', - '16661407052909726112631950204493267186977404996446674253994017961020993124292', + '21812893128244174490845186114318384111309631750779790216970477092597858785848', + '2589668621973063987240457489694488373626065998506803531328282066733461219789', '1', ], [ - '6630958864446664155558929014561025645498473927670144550521502327725859613088', - '10257469984330121170395704593208063739108953479394918968437955926014233018587', + '19736662517096924810330699257646097752878927062458868611606640349386574406597', + '4060072089820676848549092831416954645358719303909012118727092426282594652097', '1', ], [ - '12314871934741681433146131771411344537025697850773570714874192642287961794040', - '11470641693021346634121704531546293682033700932834166007717299571141315964296', + '11719045032278320337718855450887707488077032991245401830404333912363712928329', + '20429459895397450814127488292309624504089765803886468608012330383192001870669', '1', ], [ - '17097520470063383211553809058078460869174970589873134129415851040383130333868', - '3617410703082151598233457365000115489898359866991552656731389720357009506668', + '19004300167179661071877369317533970354840401420580304563087979100558811364752', + '14022355176806855344226798992865107732568349801838395169345899640004492990780', '1', ], [ - '7995417848192061085486637100808073274863048353859078661827065985891966756120', - '4543112048816912555547692960060663851497978469581464293790542002494818834260', + '13025111431804865430474042749794279094175996106474252922476866200732597336046', + '19194822113343343818445001979787110041399069657752328387546318167620461948345', '1', ], [ - '3756900937326680149692808490693121547443365485577720103369307683463757986098', - '14701792485185248475852326061659848677736717120698114532671189451547823594965', + '9281420432070758003455252378726458803842106759942105235771363724299995612746', + '10868451960138543140016636651288606302763103558353989956931009739891356663931', '1', ], [ - '2966021871375366762273629675645737426551227620306109328201701624367415707280', - '15997135187727574228071300496248407915497008568203566233193410028917630860464', + '6881336186311591694695019764740377864808644241540598555149034884273494350187', + '2786626651294071079928778385820392810695323510322088767264869337774915531398', '1', ], [ - '17027313269709603123828390341598331616557244050112644371416325010074486292816', - '4657784652544071504601293080754403918624898986674650386036985705478578376119', + '3868887205879058647589024065476442552380444440663584200982400886666148790886', + '11551675778288798821641950282239822965295574518488993089870363480882633402459', '1', ], [ - '5211722591651112972720538784064021558522564518224357221875632903004720346783', - '15742204565545957911014582041244770260769926583803919227059083652919049730381', + '15813415033010213936605839865891774355917576449019813469202169386353113595525', + '1870609980383477505308305636362868336100621908783112905201075702227453404515', '1', ], [ - '5091605874052523088200241842523496628084769006070972915274983313763081126766', - '21474847326844642314239645534523245713256826298739688218937419867864698959779', + '21157749399797355849483685908557759044725963489573087916410489368331599307794', + '14854788610114730057746604308209140307245160608186995032258954670144804516951', '1', ], [ - '18466057288766107000468952920760119321796410330419368326003100447101925793786', - '5920606244125934806657310787265193000131580390864481188584819879538853482281', + '16385822015903726360112736921395358523788300915122301306552678753856007139950', + '21852835770022094372574175833966771172218278214837381092449524015831270082443', '1', ], [ - '21509325652758209362413311112471261259307112982374947467932917809751309000526', - '13286705263216046341975269592279458440778273963623598404607601099248324188908', + '19248229849556541284232787022946438125257193537403487445681116918406935705772', + '6793532544675704467666654331031442502924492451846685587791069517168976618998', '1', ], [ - '8391911826807779399262868348144718222484978670070642761125918248704307215243', - '20829387450476090528086150189380641874166027520435836903168711027857834682379', + '18836493774186273716542303051216191129399492669285916094819482474147823428307', + '6509216046298943932406178827402599665125001577776608255883440730727474111293', '1', ], [ - '10308997289794566977249388187255854878260240915860603487914719765647078498291', - '12280908200199878030330620606331082617695439141243951739397701814658178859262', + '2809621426118306485690309749022510198095154893010698079349725235989206182716', + '4497399751266171421893644701436353917277499549766569195676686979408432567040', '1', ], [ - '12040539727496586957960173061521015630221765197931159078927761119588446778150', - '2540554208828202089770774675711401774701054560436821538472899158159947550723', + '6170933041071274746144995882643288027398824461699676189537821814335178617455', + '11939422270083845120808849126065865852360008142577598821317493776193010862067', '1', ], [ - '8674681186665416986529459603594515985270648914040860198530018688586468739184', - '2922648496015481382891108426388542361533195830896382924167133037160307926328', + '13907558045406644600787852336772673795175470348585502650967694884336926510609', + '10402183670236567810177022075839926456374913570620093187656925228836601635112', '1', ], [ - '12791502613501171561402018295790626037745190448272714798200223998530047903691', - '1359301284518543057703091464796057321763689133404994072689579681018356237361', + '9838642093868354522737246454481472310758813632867424849018704154654047098002', + '14170682187871718359141978372219783598112313240689881926254383115412693527146', '1', ], [ - '14257140672245886319024113480797843997942998521954322000391660420569000858100', - '931638430590457866489094365605722998198173485375039483139340820658602958580', + '19268808621727088609886330192714416713650881167710481846410131194840574819112', + '4548738826497792767156172897040645223030132393843932100535275236775868885881', '1', ], [ - '11488467566268691139971566095659365689414015135707565311303194014615872481483', - '20487239394947568711064905448392392146361011705838171243886554873644545982261', + '1232597044718469636783882051858339480293220683836150648093038756719345792733', + '4164564286518446421261975868208035039046547841927122039276997213757603100039', '1', ], [ - '5492422981890220759189319954052700692915352821686373947648192948311197008352', - '12796998191561316495015448110770097976697365157983089769123617960344434619998', + '5294620535222419533372104895954254795204154607883725935765987312986783187633', + '1510029808556715440734681023178490974005286595460173200541383072987550292273', '1', ], [ - '4866492870738445950615249676245502512313594183350835469598594973010131507294', - '17668941544395275808074254138261827419949162535979562938965487676493630502720', + '7913521827196573900757966679883707039843053961079282485559952654357012865178', + '12822272309007319462325152966219603030360768804371467102819817670179571110561', '1', ], [ - '14853384979398379314295334371899200413470291040002867448462371744205149436560', - '10541812807867053678938385613014678417111587037799277003145653793007559656467', + '11741856835780496736124514582563930032103983891200913422564159143798432491425', + '1186685996713398101844576210414267184996946163374847451849537513476110807467', '1', ], [ - '7532274798946570828439107115778581874085987532252209788649107842436685148991', - '8378961165245427388698069861458061686400463018591132493441756275800820200097', + '3848448520214659071169420881995284083054226208336229906265856219238707518565', + '12381374183899166684143029657071579470205894330864920551795150885160592446021', '1', ], [ - '17652852850353378291275142281632418914698559011300288731791689764453417590233', - '19771873863468751135021176106812718988689642760280229048376135472722047436990', + '2729354333883753415251820919113586347364808857675492186344760048485902311739', + '15156786867835927048585286604426887268249585171019125857459307501585887100974', '1', ], [ - '17438277520407223611774219376299156178695967895810991398095289034848563104314', - '3761123047764825106059710030718834660728737096571520961687041838797303469684', + '16327260115537188860691343668951285278159950664733445048928882023437126306794', + '15469859044222521064134805435371078520376095849473017636140479838581816450418', '1', ], [ - '5918452633200782136344878764415470407513083541948272866677449494484586220042', - '10604690725100593653005054223013707334150993431570760479436879031019147461045', + '9716588579885449992683804112491878761164620027004437304769643243506657789021', + '4292706684411927078028017507044791383954395251541518728702327216870791282903', '1', ], [ - '8259542419042092099816445422703058848035325374263840891089862676744764438125', - '17907757818862857645822205483286406489986719785490893298511469190864212380141', + '3831081473705850109604085868551425990958661808755750553828925508405362814908', + '7692088199943626776992448758764793963791676479101773985328943563508103917662', '1', ], [ - '20139923441584267759032148955097012875446993900262813022506144977495740234228', - '633188859083836213367398262855538919396980856067775018686782148320512937369', + '14553842600721830593567536989022992714300892536915530699049444789457197924996', + '1034992513758432733967666097255620384731263408100321609845288946444012846161', '1', ], [ - '21726639048837222608844226079734646967492836503699042863220596000459992053157', - '6424371129159802925649264208731956005303634651380896587229707562692220637803', + '8298955061619332575841977271384783772861434116506072222428896327020549590778', + '3417308972480405311337590188121737420812511616327499449644814830511455229125', '1', ], [ - '17041198497819316001571613782102427788635802538100517060467338190956934469546', - '11217287933652083481750768335932222403857834316645402517026868243277473458983', + '17492972350290531881303108441024194391834883043603735002548578305198527539792', + '20178093813925833001632123131014833838779278549203596933431582561790322377177', '1', ], [ - '19034834875875788782314876465727853947365075993855038719393084504593990363060', - '9086146571834397542121368889586301285063986605518485332147871865756094378099', + '20266961211116442799505089820504252966764415195756292869126528880324253997340', + '12721789314780990071009310805499247522750756905074142078629315686295071200692', '1', ], [ - '21093653561260126160341081475039540954188474477422498093525244396180181823670', - '13535501879576063105683047927635458524340467725253896927409746072645845079357', + '2848503208668242872636925431606615634594079979671099350197778718883635724263', + '4654302785281536218111627060020949573498492991361181450337516475942903887229', '1', ], [ - '15419644639429111779611512596775879470184968489398755763696579225869234243113', - '5888912982738459212046512719300383315430256384067733913361522421989057607660', + '8117348408366486765069198284524473502421701109551785034877096925063881017611', + '9240711853483292534294774801520720851385345818400239892620763606707852365913', '1', ], [ - '8021794565280119378214742351891355557406787634859090731496224811528919654543', - '18447147251619727046151948885356377562632348273395416712975164631737556908769', + '14726850444695139754420062135396416349332736703641747713568870234740172750594', + '19695982391909446471699547638880558431250407146813365085967041143148712990281', '1', ], [ - '3237112745142418943048154434679101631739412035974433745323987899717876176195', - '18366905927821072207610344944020849537376768169403475648963725058424582607207', + '10713887585841823429966077588975547340632721253524339816841192298502141129142', + '15875414001171419054582031537391697841529253632260086276963072228015908768572', '1', ], [ - '11103803864515131888867176971476009681201304848017317082097071924299524667237', - '11568903548692180379468059688713630672633914822600904309527038031667221969584', + '8726527790501042190955587753490550968193378562596306669177536293797796528295', + '2567561473507954539369683410861518721775690943462480886913419763755359623122', '1', ], [ - '7093177952754259965934651775168409530632125523254544611522122675834921490787', - '6527089445662238533285374041077440315301392384598470870650322831725955444547', + '20228003583559059699534488013186856436814319213991227118734829746090088629768', + '3062194835746234285630044060950549287869018179041416439415198981293605738893', '1', ], [ - '3227191841223944786060447942066038248540641877454960350047529138549493501903', - '10552339256116188971981234910367616983197445292206817485485035773727709784879', + '15326430247878492538731905607224792134839791639951162876881030896580260626530', + '3286497031007325457398280838270081812804433848003710004921976210893659583713', '1', ], [ - '989674451930098801894244326482852910581289308023468293119709273655157020723', - '11594716043590652193446947727280371336298547049038778564922528212931403014494', + '21172455070651382026237320306432651725116860744924112007014279936313214413699', + '3124713746257151433204645213247841462846602236377570194562035843093051589347', '1', ], [ - '1886419919944054684680487851321230264354281745871778147085839004172555969572', - '16195110947725549843609790504068798852157786906403317729323982518077292241186', + '586329559940161576696101534294134289580198770923725856103144741895937907876', + '8549358505942194684807970498040082081114943321990836277323952699459596852004', '1', ], [ - '15971113544867520289327044418603931629198616653529241341627397537269945935156', - '9448786879019650516172264951032976514419166206838957913033559142081219556746', + '10801780763283804426115126946120589447640285117455543080483767206343304180554', + '10884989352940730579839064046335509450313089154054617399581142310521031798261', '1', ], [ - '6091812380673627804034048532221266570681201403948345981699207852441234617237', - '4675463611373415464797065834003346370383503427109900660803562111395705316246', + '7642110729948206734175115603784267089466490166359943206653870027902551255488', + '11475699997476077360759438942765927072551540977873657634945729769964213394775', '1', ], [ - '14169123442478678228228176081430768043603215308343287642007090866096807228539', - '1425153510097175478307414428375512221296123855290017144964249395278961088177', + '6477632809823450072672399006410105825115578703752819060774243488278595863752', + '5842794419924960246061189125669290725098229148434953995106895999043038757426', '1', ], [ - '13010207937248020322435171834251494123777437279612504331350246242030974349076', - '5248384367299937265299632334272213949440138266550956975104163220131220896247', + '9851792234521580273104967216407455160006004510538086041246868797144654998160', + '11682999507634805671497034306879162257155282925402112526209517483082417200425', '1', ], [ - '1876053550778302219301374295745644650670485846429706087305814312472418356024', - '20649094119021787290717550348242625288049253059318330228537390183385105472845', + '7849825230838163134393354944881831816101066439604289649483128481654599764130', + '19425402357687722197365422640263552055101566124507367803085779467136114966243', '1', ], ], @@ -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', + '15776533017793451196514777082124337882856102873453705308829629571923075424417', + '14232490066350175683786572931273695352468015232518411187366417165893912115230', '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', + '1180945442359952286721463819274447148908421750703875389048309699338667338411', + '6539452064449720183898998074274190026127754682197287334130838477279315254881', + '1', ], [ - '3209155548902127778431906050698597513646227271655778722256683596743569531044', - '16830777068052670490128170305087202969267881418665601837992321846223880096264', + '13806944210187274185057734594353372528156342424366258241133564778474054935868', + '7800429229653987231645474546639647711164156063512193001130226490464812892063', + '1', ], - ['1', '0'], - ], - vk_alphabeta_12: [ [ - [ - '2029413683389138792403550203267699914886160938906632433982220835551125967885', - '21072700047562757817161031222997517981543347628379360635925549008442030252106', - ], - [ - '5940354580057074848093997050200682056184807770593307860589430076672439820312', - '12156638873931618554171829126792193045421052652279363021382169897324752428276', - ], - [ - '7898200236362823042373859371574133993780991612861777490112507062703164551277', - '7074218545237549455313236346927434013100842096812539264420499035217050630853', - ], + '5522822939351592924817275047393709421174176326835572758552827440001401610167', + '11245123080087310106980015850199801755152167910673823675982721730905258212233', + '1', ], [ - [ - '7077479683546002997211712695946002074877511277312570035766170199895071832130', - '10093483419865920389913245021038182291233451549023025229112148274109565435465', - ], - [ - '4595479056700221319381530156280926371456704509942304414423590385166031118820', - '19831328484489333784475432780421641293929726139240675179672856274388269393268', - ], - [ - '11934129596455521040620786944827826205713621633706285934057045369193958244500', - '8037395052364110730298837004334506829870972346962140206007064471173334027475', - ], + '5627481952482064829757269526369949776646066892934683965609154755505296555965', + '4545484539969611530913200236731153733781933150464540998302850238315475023119', + '1', ], - ], - IC: [ [ - '16231288969314859968324689058413762096922845561563844884666690734569347763082', - '5910239835236935696830364945179549816839705839711095023727334010208312533092', + '20423400711755442910136460433293470952040919967735945317336472158180565467951', + '6378416884992747212393622109218662385874688399353342355554228954581250524736', '1', ], [ - '6260576084748320398294355533743833076081106851106584793995576172400792840042', - '21319331746978162427565186170902229291674424766799571029877219503610111212711', + '4373305143599295486105733121446353133863889257489542032576171376300323541304', + '4026684213449689055353486192784741961832172326825055022163307876785200324637', '1', ], [ - '16479555690731841331208639285970255252465816317199429107438320860232249030762', - '3783712303962417058251692820237130203111118349896777989119917429903130718416', + '5068833407180337147676969453261920994101725947652623057924700035380226397907', + '11087944526730366835444251823404529324958715143332979454630299083515861157072', '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', + '21831082589187376499865612459731026493616836999924912561891098220429445525521', + '10842131628581855605843884617846867779319917434679079988533026500378178602950', + '1', ], [ - '10505242626370262277552901082094356697409835680220590971873171140371331206856', - '21847035105528745403288232691147584728191162732299865338377159692350059136679', + '14267596758527912595273989225764996478640991994242422111486793250630191081468', + '18694764448853871920117677375229746676709722363749986739355723996303104296068', + '1', ], - ['1', '0'], - ], - vk_gamma_2: [ [ - '10857046999023057135944570762232829481370756359578518086990519993285655852781', - '11559732032986387107991004021392285783925812861821192530917403151452391805634', + '10612001726856359227588948695393307655456532716872467807420271676133645773510', + '1540081878138518609133189078681275557428892906987071405333678709982239141734', + '1', ], [ - '8495653923123431417604973247489272438418190587263600148770280649306958101930', - '4082367875863433681332203403145435568316851327593401208105741076214120093531', + '7645334938708591281146159611078021141015093576924464549682793344782173540643', + '5041018151127409344023520612676035165867301524846720998477901879854547090576', + '1', ], - ['1', '0'], - ], - vk_delta_2: [ [ - '6017862818859039949402494358517294962820240623943684783826304039772640083873', - '11178871189289476718066796914086694937261254534191355355208715473729459679073', + '8883975772061177470672187114470132395286660826025700056750575280427708604951', + '8320685858262483078856712401649683261512346806365950058502541940795537677566', + '1', ], [ - '18289536515878615632378439677874060078266723798016506833131127246101859022607', - '8005285279181761514246985809032780535330313241768111893953981130952718939039', + '19770351503660034123641636433521671083703252249281883512457304565751018243912', + '1942705439280745753504371271759370270294305194237531295574972095513458387941', + '1', ], - ['1', '0'], - ], - vk_alphabeta_12: [ [ - [ - '2029413683389138792403550203267699914886160938906632433982220835551125967885', - '21072700047562757817161031222997517981543347628379360635925549008442030252106', - ], - [ - '5940354580057074848093997050200682056184807770593307860589430076672439820312', - '12156638873931618554171829126792193045421052652279363021382169897324752428276', - ], - [ - '7898200236362823042373859371574133993780991612861777490112507062703164551277', - '7074218545237549455313236346927434013100842096812539264420499035217050630853', - ], + '15226311176844690062341353072910557091317547496535044902570312644662561307949', + '19296708345054645579869153897992480989971014206920627404347102350029660305578', + '1', ], [ - [ - '7077479683546002997211712695946002074877511277312570035766170199895071832130', - '10093483419865920389913245021038182291233451549023025229112148274109565435465', - ], - [ - '4595479056700221319381530156280926371456704509942304414423590385166031118820', - '19831328484489333784475432780421641293929726139240675179672856274388269393268', - ], - [ - '11934129596455521040620786944827826205713621633706285934057045369193958244500', - '8037395052364110730298837004334506829870972346962140206007064471173334027475', - ], + '5436668919010108586842015267897845254611520180011711994231132539697114737868', + '21034924479027562029885591632326051974733967422882023235118894616793689864649', + '1', ], - ], - IC: [ [ - '8938477176893632284539660223582989287068454472173218831171935317066824606917', - '9409256007408490215082527289125535503645664704563806461206465772769775389099', + '17617503314542022213516716240700336131305032342072938364934850750624139958532', + '7535025988359437238238236154472398858799932717035093350843709570356278900481', '1', ], [ - '9406714425071299321475031445293798140218018985951799864352153321364734136304', - '16190677786472274760082919772279780726948507745938757138812410377304364738549', + '15079990665367846194463669410480448234210628628217480894032215566550223721508', + '14422402677444824910876930195543885009950058186617851677855759748496927334854', '1', ], [ - '17636847760071529621476637340355013609204110513013412005293780563117471695582', - '16269304104382842638426610869142550901209834485529174763447294867648099217552', + '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 type { + CertificateData, + PublicKeyDetailsECDSA, + PublicKeyDetailsRSA, +} from './dataStructure.js'; export { - oidMap, - mapSecpCurves, - getSecpFromNist, - getFriendlyName, extractHashFunction, + getFriendlyName, + getSecpFromNist, + mapSecpCurves, + oidMap, } from './oids.js'; - export { - getSubjectKeyIdentifier, getAuthorityKeyIdentifier, getIssuerCountryCode, + getSubjectKeyIdentifier, } from './utils.js'; - -export type { - CertificateData, - PublicKeyDetailsECDSA, - PublicKeyDetailsRSA, -} from './dataStructure.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); + // 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]); } - 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 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 certificateData; - } catch (error) { - console.error(`Error processing certificate`, error); - throw error; } -} + 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' - ); +export function getTBSBytesForge(certificate: Certificate): number[] { + return Array.from(certificate.tbsView.map((byte) => parseInt(byte.toString(16), 16))); +} - // 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]); - } +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(); - // 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(); + 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); } - } - 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; - } -}; + 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; -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]; + const authorityKeyIdentifier = getAuthorityKeyIdentifier(cert); + certificateData.authorityKeyIdentifier = authorityKeyIdentifier; - for (const pattern of patterns) { - const match = input.match(pattern); - if (match) { - // Remove any hyphens and return standardized format - return match[0].replace('-', ''); + // corner case for rsapss + if ( + certificateData.signatureAlgorithm === 'rsapss' && + (!certificateData.hashAlgorithm || certificateData.hashAlgorithm === 'unknown') + ) { + certificateData.hashAlgorithm = ( + certificateData.publicKeyDetails as PublicKeyDetailsRSAPSS + ).hashAlgorithm; } - } - 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 certificateData; + } catch (error) { + console.error(`Error processing certificate`, error); + throw 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))); } 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,5 +1,54 @@ import { attributeToPosition, attributeToPosition_ID } from '../../constants/constants.js'; -import { SelfAppDisclosureConfig } from '../appType.js'; +import type { SelfAppDisclosureConfig } from '../appType.js'; + +export function formatAndUnpackForbiddenCountriesList( + forbiddenCountriesList_packed: 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(''); + if (countryCode.length === 3) { + countries.push(countryCode); + } + } + return countries; // Return countries array instead of trimmed +} + +/*** Disclose circuits ***/ + +function trimu0000(unpackedReveal: string[]): string[] { + return unpackedReveal.filter((value) => value !== '\u0000'); +} + +export function formatAndUnpackReveal( + revealedData_packed: string[], + id_type: 'passport' | 'id' +): string[] { + const revealedData_packed_formatted_passport = [ + revealedData_packed['revealedData_packed[0]'], + revealedData_packed['revealedData_packed[1]'], + revealedData_packed['revealedData_packed[2]'], + ]; + const revealedData_packed_formatted_id = [ + revealedData_packed['revealedData_packed[0]'], + revealedData_packed['revealedData_packed[1]'], + revealedData_packed['revealedData_packed[2]'], + revealedData_packed['revealedData_packed[3]'], + ]; + return unpackReveal( + id_type === 'passport' + ? revealedData_packed_formatted_passport + : revealedData_packed_formatted_id, + id_type + ); +} /*** OpenPassport Attestation ***/ export function formatForbiddenCountriesListFromCircuitOutput( @@ -19,12 +68,6 @@ export function formatForbiddenCountriesListFromCircuitOutput( return formattedCountryList; } -/*** Disclose circuits ***/ - -function trimu0000(unpackedReveal: string[]): string[] { - return unpackedReveal.filter((value) => value !== '\u0000'); -} - export function getAttributeFromUnpackedReveal( unpackedReveal: string[], attribute: string, @@ -41,76 +84,26 @@ export function getAttributeFromUnpackedReveal( 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[], +export function revealBitmapFromAttributes( + disclosureOptions: SelfAppDisclosureConfig, id_type: 'passport' | 'id' ): string[] { - const revealedData_packed_formatted_passport = [ - revealedData_packed['revealedData_packed[0]'], - revealedData_packed['revealedData_packed[1]'], - revealedData_packed['revealedData_packed[2]'], - ]; - const revealedData_packed_formatted_id = [ - revealedData_packed['revealedData_packed[0]'], - revealedData_packed['revealedData_packed[1]'], - revealedData_packed['revealedData_packed[2]'], - revealedData_packed['revealedData_packed[3]'], - ]; - return unpackReveal( - id_type === 'passport' - ? revealedData_packed_formatted_passport - : revealedData_packed_formatted_id, - id_type - ); -} - -export function formatAndUnpackForbiddenCountriesList( - forbiddenCountriesList_packed: 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(''); - if (countryCode.length === 3) { - countries.push(countryCode); + const reveal_bitmap = Array(id_type === 'passport' ? 88 : 90).fill('0'); + const att_to_position = id_type === 'passport' ? attributeToPosition : attributeToPosition_ID; + Object.entries(disclosureOptions).forEach(([attribute, { enabled }]) => { + if (enabled && attribute in att_to_position) { + const [start, end] = att_to_position[attribute as keyof typeof att_to_position]; + reveal_bitmap.fill('1', start, end + 1); } - } - return countries; // Return countries array instead of trimmed + }); + + return reveal_bitmap; } export function revealBitmapFromMapping(attributeToReveal: { [key: string]: string }): string[] { @@ -125,18 +118,25 @@ export function revealBitmapFromMapping(attributeToReveal: { [key: string]: stri return reveal_bitmap; } -export function revealBitmapFromAttributes( - disclosureOptions: SelfAppDisclosureConfig, +export function unpackReveal( + revealedData_packed: string | string[], id_type: 'passport' | 'id' ): string[] { - const reveal_bitmap = Array(id_type === 'passport' ? 88 : 90).fill('0'); - const att_to_position = id_type === 'passport' ? attributeToPosition : attributeToPosition_ID; - Object.entries(disclosureOptions).forEach(([attribute, { enabled }]) => { - if (enabled && attribute in att_to_position) { - const [start, end] = att_to_position[attribute as keyof typeof att_to_position]; - reveal_bitmap.fill('1', start, end + 1); - } + // 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 reveal_bitmap; + 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'; - + bigIntToHex, + castFromScope, + castFromUUID, + castToAddress, + castToScope, + castToUUID, + castToUserIdentifier, + hexToUUID, + stringToAsciiBigIntArray, + validateUserId, +} from './uuid.js'; export { + formatAndUnpackForbiddenCountriesList, + formatAndUnpackReveal, formatForbiddenCountriesListFromCircuitOutput, getAttributeFromUnpackedReveal, - unpackReveal, getOlderThanFromCircuitOutput, - formatAndUnpackReveal, - formatAndUnpackForbiddenCountriesList, - revealBitmapFromMapping, revealBitmapFromAttributes, + revealBitmapFromMapping, + unpackReveal, } from './formatOutputs.js'; - export { formatCountriesList, reverseBytes, reverseCountryBytes } from './formatInputs.js'; - export { - castFromUUID, - bigIntToHex, - hexToUUID, - castToUUID, - castToUserIdentifier, - castToAddress, - castFromScope, - castToScope, - stringToAsciiBigIntArray, - validateUserId, -} from './uuid.js'; - -export type { UserIdType } from './uuid.js'; + 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,9 +1,19 @@ -export function formatCallData_register(parsedCallData: any[]) { +export function formatCallData_disclose(parsedCallData: any[]) { return { - blinded_dsc_commitment: parsedCallData[3][0], - nullifier: parsedCallData[3][1], - commitment: parsedCallData[3][2], - attestation_id: parsedCallData[3][3], + nullifier: parsedCallData[3][0], + revealedData_packed: [parsedCallData[3][1], parsedCallData[3][2], parsedCallData[3][3]], + attestation_id: parsedCallData[3][4], + merkle_root: parsedCallData[3][5], + scope: parsedCallData[3][6], + current_date: [ + parsedCallData[3][7], + parsedCallData[3][8], + parsedCallData[3][9], + parsedCallData[3][10], + parsedCallData[3][11], + parsedCallData[3][12], + ], + user_identifier: parsedCallData[3][13], a: parsedCallData[0], b: [parsedCallData[1][0], parsedCallData[1][1]], c: parsedCallData[2], @@ -19,28 +29,30 @@ export function formatCallData_dsc(parsedCallData: any[]) { }; } -export function formatCallData_disclose(parsedCallData: any[]) { +export function formatCallData_register(parsedCallData: any[]) { return { - nullifier: parsedCallData[3][0], - revealedData_packed: [parsedCallData[3][1], parsedCallData[3][2], parsedCallData[3][3]], - attestation_id: parsedCallData[3][4], - merkle_root: parsedCallData[3][5], - scope: parsedCallData[3][6], - current_date: [ - parsedCallData[3][7], - parsedCallData[3][8], - parsedCallData[3][9], - parsedCallData[3][10], - parsedCallData[3][11], - parsedCallData[3][12], - ], - user_identifier: parsedCallData[3][13], + 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; const REQUIRED_CHUNKS = 4; @@ -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 { + calculateUserIdentifierHash, + customHasher, flexiblePoseidon, - hash, getHashLen, - customHasher, - packBytesAndPoseidon, - calculateUserIdentifierHash, 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,29 +83,23 @@ export function formatAndConcatenateDataHashes( return concat; } -export function generateSignedAttr(messageDigest: number[]) { - const constructedEContent = []; - - // Detailed description is in private file r&d.ts for now - // First, the tag and length, assumed to be always the same - constructedEContent.push(...[49, 102]); - - // 1.2.840.113549.1.9.3 is RFC_3369_CONTENT_TYPE_OID - constructedEContent.push(...[48, 21, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 3]); - // 2.23.136.1.1.1 is ldsSecurityObject - constructedEContent.push(...[49, 8, 6, 6, 103, -127, 8, 1, 1, 1]); - - // 1.2.840.113549.1.9.5 is signing-time - constructedEContent.push(...[48, 28, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 5]); - // mock time of signature - constructedEContent.push(...[49, 15, 23, 13, 49, 57, 49, 50, 49, 54, 49, 55, 50, 50, 51, 56, 90]); - // 1.2.840.113549.1.9.4 is RFC_3369_MESSAGE_DIGEST_OID - constructedEContent.push(...[48, 47, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 4]); - // TAG and length of the message digest - constructedEContent.push(...[49, 34, 4, 32]); +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, '<'); +} - constructedEContent.push(...messageDigest); - return constructedEContent; +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) { @@ -128,25 +122,6 @@ export function formatMrz(mrz: string) { 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('<'); @@ -164,3 +139,28 @@ export function formatName(firstName: string, lastName: string, targetLength: nu return result; } + +export function generateSignedAttr(messageDigest: number[]) { + const constructedEContent = []; + + // Detailed description is in private file r&d.ts for now + // First, the tag and length, assumed to be always the same + constructedEContent.push(...[49, 102]); + + // 1.2.840.113549.1.9.3 is RFC_3369_CONTENT_TYPE_OID + constructedEContent.push(...[48, 21, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 3]); + // 2.23.136.1.1.1 is ldsSecurityObject + constructedEContent.push(...[49, 8, 6, 6, 103, -127, 8, 1, 1, 1]); + + // 1.2.840.113549.1.9.5 is signing-time + constructedEContent.push(...[48, 28, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 5]); + // mock time of signature + constructedEContent.push(...[49, 15, 23, 13, 49, 57, 49, 50, 49, 54, 49, 55, 50, 50, 51, 56, 90]); + // 1.2.840.113549.1.9.4 is RFC_3369_MESSAGE_DIGEST_OID + constructedEContent.push(...[48, 47, 6, 9, 42, -122, 72, -122, -9, 13, 1, 9, 4]); + // TAG and length of the message digest + constructedEContent.push(...[49, 34, 4, 32]); + + constructedEContent.push(...messageDigest); + return constructedEContent; +} 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 * 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 { genDG1 } from './dg1.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 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 { 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,132 +29,50 @@ 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; - } - return passportData; -} - -export function generateCommitment( - secret: string, - attestation_id: string, - passportData: PassportData -) { - const passportMetadata = passportData.passportMetadata; - - const dg1_packed_hash = packBytesAndPoseidon(formatMrz(passportData.mrz)); - - const eContent_shaBytes = hash( - passportMetadata.eContentHashFunction, - Array.from(passportData.eContent), - 'bytes' - ); - - const eContent_packed_hash = packBytesAndPoseidon( - (eContent_shaBytes as number[]).map((byte) => byte & 0xff) - ); - - const dsc_hash = getLeafDscTree(passportData.dsc_parsed, passportData.csca_parsed); - // Log the values used to generate the commitment - - return poseidon5([ - secret, - attestation_id, - dg1_packed_hash, - eContent_packed_hash, - dsc_hash, - ]).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 extractRSFromSignature(signatureBytes: number[]): { r: string; s: string } { + const derSignature = Buffer.from(signatureBytes).toString('binary'); + const asn1 = forge.asn1.fromDer(derSignature); + const signatureAsn1 = asn1.value; -export function padWithZeroes(bytes: number[], length: number) { - return bytes.concat(new Array(length - bytes.length).fill(0)); -} + if (signatureAsn1.length !== 2) { + throw new Error('Invalid signature format'); + } -/// @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); + 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 { - pubKey: getCertificatePubKey( - passportData.dsc_parsed, - passportMetadata.signatureAlgorithm, - passportMetadata.signedAttrHashFunction - ), - signature: getPassportSignature(passportData, n, k), - signatureAlgorithmFullName: signatureAlgorithmFullName, - }; + return { r, s }; } -function getPassportSignature(passportData: PassportData, n: number, k: number): any { - const { signatureAlgorithm } = passportData.dsc_parsed; - if (signatureAlgorithm === 'ecdsa') { - const { r, s } = extractRSFromSignature(passportData.encryptedDigest); - 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 splitToWords(BigInt(bytesToBigDecimal(passportData.encryptedDigest)), n, k); - } +export function extractSignatureFromDSC(dscCertificate: string) { + const cert = getCertificateFromPem(dscCertificate); + const dscSignature = cert.signatureValue.valueBlock.valueHexView; + return Array.from(dscSignature); } -/// @notice Get the public key from the certificate -/// @dev valid for both DSC and CSCA -export function getCertificatePubKey( +export function findStartPubKeyIndex( certificateData: CertificateData, - signatureAlgorithm: string, - hashFunction: string -): any { - const signatureAlgorithmFullName = getSignatureAlgorithmFullName( - certificateData, - signatureAlgorithm, - hashFunction - ); - const { n, k } = getNAndK(signatureAlgorithmFullName as SignatureAlgorithm); + rawCert: any, + signatureAlgorithm: string +): [number, number] { const { publicKeyDetails } = certificateData; if (signatureAlgorithm === 'ecdsa') { const { x, y } = publicKeyDetails as PublicKeyDetailsECDSA; - const x_dsc = splitToWords(BigInt(hexToDecimal(x)), n, k); - const y_dsc = splitToWords(BigInt(hexToDecimal(y)), n, k); - return [...x_dsc, ...y_dsc]; + 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 splitToWords(BigInt(hexToDecimal(modulus)), n, k); + return findStartIndex(modulus, rawCert); } } @@ -178,12 +97,6 @@ export function formatCertificatePubKeyDSC( } } -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, @@ -206,57 +119,84 @@ export function formatSignatureDSCCircuit( } } -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); +export function generateCommitment( + secret: string, + attestation_id: string, + passportData: PassportData +) { + const passportMetadata = passportData.passportMetadata; - return [x_index, x_totalLength + y_totalLength]; + const dg1_packed_hash = packBytesAndPoseidon(formatMrz(passportData.mrz)); + + const eContent_shaBytes = hash( + passportMetadata.eContentHashFunction, + Array.from(passportData.eContent), + 'bytes' + ); + + const eContent_packed_hash = packBytesAndPoseidon( + (eContent_shaBytes as number[]).map((byte) => byte & 0xff) + ); + + const dsc_hash = getLeafDscTree(passportData.dsc_parsed, passportData.csca_parsed); + // Log the values used to generate the commitment + + return poseidon5([ + secret, + attestation_id, + dg1_packed_hash, + eContent_packed_hash, + dsc_hash, + ]).toString(); +} + +function getPassportSignature(passportData: PassportData, n: number, k: number): any { + const { signatureAlgorithm } = passportData.dsc_parsed; + if (signatureAlgorithm === 'ecdsa') { + const { r, s } = extractRSFromSignature(passportData.encryptedDigest); + const signature_r = splitToWords(BigInt(hexToDecimal(r)), n, k); + const signature_s = splitToWords(BigInt(hexToDecimal(s)), n, k); + return [...signature_r, ...signature_s]; } else { - // Splits to 525 words of 8 bits each - const { modulus } = publicKeyDetails as PublicKeyDetailsRSA; - return findStartIndex(modulus, rawCert); + return splitToWords(BigInt(bytesToBigDecimal(passportData.encryptedDigest)), n, k); } } -/// @notice Get the signature algorithm full name +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 getSignatureAlgorithmFullName( +export function getCertificatePubKey( certificateData: CertificateData, signatureAlgorithm: string, - hashAlgorithm: string -): string { + hashFunction: string +): any { + const signatureAlgorithmFullName = getSignatureAlgorithmFullName( + certificateData, + signatureAlgorithm, + hashFunction + ); + const { n, k } = getNAndK(signatureAlgorithmFullName as SignatureAlgorithm); const { publicKeyDetails } = certificateData; if (signatureAlgorithm === 'ecdsa') { - return `${signatureAlgorithm}_${hashAlgorithm}_${(publicKeyDetails as PublicKeyDetailsECDSA).curve}_${publicKeyDetails.bits}`; + const { x, y } = publicKeyDetails as PublicKeyDetailsECDSA; + const x_dsc = splitToWords(BigInt(hexToDecimal(x)), n, k); + const y_dsc = splitToWords(BigInt(hexToDecimal(y)), n, k); + return [...x_dsc, ...y_dsc]; } 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 { modulus } = publicKeyDetails as PublicKeyDetailsRSA; + return splitToWords(BigInt(hexToDecimal(modulus)), n, k); } - 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) { @@ -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,25 +1,44 @@ -// 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)); - } - 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)); +export function assert(cond: boolean, errorMessage: string) { + if (!cond) { + throw new Error(errorMessage); } - 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 +function int128toBytes(x: number): Uint8Array { + const buffer = new ArrayBuffer(16); + const view = new DataView(buffer); + + // Write high 64 bits + view.setBigUint64(0, BigInt(0), false); + // Write low 64 bits + view.setBigUint64(8, BigInt(x), false); + + return new Uint8Array(buffer); +} + +// Works only on 32 bit sha text lengths +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 + const mergedArray = new Uint8Array(a1.length + a2.length); + mergedArray.set(a1); + mergedArray.set(a2, a1.length); + return mergedArray; } export function sha384_512Pad( @@ -28,10 +47,10 @@ export function sha384_512Pad( ): [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; + const 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); + 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)); @@ -47,7 +66,7 @@ export function sha384_512Pad( // 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; + const messageLen = prehash_prepad_m.length; // Pad to max length if needed while (prehash_prepad_m.length < maxShaBytes) { @@ -62,45 +81,25 @@ export function sha384_512Pad( return [Array.from(prehash_prepad_m), messageLen]; } -// Helper function to convert 128-bit length to bytes -function int128toBytes(x: number): Uint8Array { - const buffer = new ArrayBuffer(16); - const view = new DataView(buffer); - - // Write high 64 bits - view.setBigUint64(0, BigInt(0), false); - // Write low 64 bits - view.setBigUint64(8, BigInt(x), false); - - return new Uint8Array(buffer); -} - -// 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 - 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); - 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 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,114 +1,119 @@ -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(); - } -} +// SMT trees for 3 levels of matching : +// 1. Passport Number and Nationality tree : level 3 (Absolute Match) +// 2. Name and date of birth combo tree : level 2 (High Probability Match) +// 3. Name and year of birth combo tree : level 1 (Partial Match) +// NEW: ID card specific trees +export function buildSMT(field: any[], treetype: string): [number, number, SMT] { + let count = 0; + const startTime = performance.now(); -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); -} + const hash2 = (childNodes: ChildNodes) => + childNodes.length === 2 ? poseidon2(childNodes) : poseidon3(childNodes); + const tree = new SMT(hash2, true); -export function getLeafDscTreeFromParsedDsc(dscParsed: CertificateData): string { - return getLeafDscTreeFromDscCertificateMetadata(dscParsed, parseDscCertificateData(dscParsed)); -} + for (let i = 0; i < field.length; i++) { + const entry = field[i]; -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(); -} + // Optimization: Log progress less frequently + if (i !== 0 && i % 100 === 0) { + console.log('Processing', treetype, 'number', i, 'out of', field.length); + } -export function getLeafCscaTree(csca_parsed: CertificateData): string { - return getLeaf(csca_parsed, 'csca'); -} + let leaf = BigInt(0); + // Determine document type based on treetype for name processing + let docType: 'passport' | 'id_card' = 'passport'; // Default to passport + if (treetype.endsWith('_id_card')) { + docType = 'id_card'; + } -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]; -} + if (treetype == 'passport_no_and_nationality') { + leaf = processPassportNoAndNationality(entry.Pass_No, entry.Pass_Country, i); + } else if (treetype == 'name_and_dob') { + leaf = processNameAndDob(entry, i, 'passport'); // Explicitly passport + } else if (treetype == 'name_and_yob') { + leaf = processNameAndYob(entry, i, 'passport'); // Explicitly passport + } else if (treetype == 'name_and_dob_id_card') { + // New ID card type + leaf = processNameAndDob(entry, i, 'id_card'); + } else if (treetype == 'name_and_yob_id_card') { + // New ID card type + leaf = processNameAndYob(entry, i, 'id_card'); + } else if (treetype == 'country') { + const keys = Object.keys(entry); + leaf = processCountry(keys[0], entry[keys[0]], i); + } -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'); + if (leaf == BigInt(0)) { + // Skip entries that couldn't be processed (e.g., missing data) + continue; + } + + // Check for duplicates *after* processing, as different inputs might yield the same hash + if (tree.createProof(leaf).membership) { + // console.log('Duplicate leaf generated, skipping entry:', i, entry); // Optional: log duplicates + continue; + } + + count += 1; + tree.add(leaf, BigInt(1)); } - 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; + console.log('Total', treetype, 'entries added:', count, 'out of', field.length); + console.log(treetype, 'tree built in', (performance.now() - startTime).toFixed(2), 'ms'); + return [count, performance.now() - startTime, tree]; } export function formatRoot(root: string): string { - let rootHex = BigInt(root).toString(16); + 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; @@ -160,87 +165,101 @@ export function generateSMTProof(smt: SMT, leaf: bigint) { }; } -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); - } +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); } - return { siblings, path, leaf_depth }; } -// SMT trees for 3 levels of matching : -// 1. Passport Number and Nationality tree : level 3 (Absolute Match) -// 2. Name and date of birth combo tree : level 2 (High Probability Match) -// 3. Name and year of birth combo tree : level 1 (Partial Match) -// NEW: ID card specific trees -export function buildSMT(field: any[], treetype: string): [number, number, SMT] { - let count = 0; - let startTime = performance.now(); - - const hash2 = (childNodes: ChildNodes) => - childNodes.length === 2 ? poseidon2(childNodes) : poseidon3(childNodes); - const tree = new SMT(hash2, true); - - for (let i = 0; i < field.length; i++) { - const entry = field[i]; - - // Optimization: Log progress less frequently - if (i !== 0 && i % 100 === 0) { - console.log('Processing', treetype, 'number', i, 'out of', field.length); - } +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()), + ]; +} - let leaf = BigInt(0); - // Determine document type based on treetype for name processing - let docType: 'passport' | 'id_card' = 'passport'; // Default to passport - if (treetype.endsWith('_id_card')) { - docType = 'id_card'; - } +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; +} - if (treetype == 'passport_no_and_nationality') { - leaf = processPassportNoAndNationality(entry.Pass_No, entry.Pass_Country, i); - } else if (treetype == 'name_and_dob') { - leaf = processNameAndDob(entry, i, 'passport'); // Explicitly passport - } else if (treetype == 'name_and_yob') { - leaf = processNameAndYob(entry, i, 'passport'); // Explicitly passport - } else if (treetype == 'name_and_dob_id_card') { - // New ID card type - leaf = processNameAndDob(entry, i, 'id_card'); - } else if (treetype == 'name_and_yob_id_card') { - // New ID card type - leaf = processNameAndYob(entry, i, 'id_card'); - } else if (treetype == 'country') { - const keys = Object.keys(entry); - leaf = processCountry(keys[0], entry[keys[0]], i); - } +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 + } +} - if (leaf == BigInt(0)) { - // Skip entries that couldn't be processed (e.g., missing data) - continue; - } +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]; +} - // Check for duplicates *after* processing, as different inputs might yield the same hash - if (tree.createProof(leaf).membership) { - // console.log('Duplicate leaf generated, skipping entry:', i, entry); // Optional: log duplicates - continue; - } +/** 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)); - count += 1; - tree.add(leaf, BigInt(1)); + 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(); } +} - console.log('Total', treetype, 'entries added:', count, 'out of', field.length); - console.log(treetype, 'tree built in', (performance.now() - startTime).toFixed(2), 'ms'); - return [count, performance.now() - startTime, tree]; +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( @@ -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/yarn.lock b/yarn.lock index c319a2b31..d57c08c8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4496,6 +4496,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" @@ -4504,6 +4506,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" @@ -4687,6 +4695,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" @@ -9569,7 +9578,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: @@ -9592,7 +9601,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: @@ -13991,6 +14000,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" @@ -14072,7 +14092,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: @@ -14137,6 +14157,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" @@ -14221,6 +14261,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" @@ -14262,7 +14313,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: @@ -19041,7 +19092,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: