Merge pull request #1818 from selfxyz/release/staging-2026-03-06

Release to Staging v2.9.16 - 2026-03-06
This commit is contained in:
Justin Hernandez
2026-03-09 22:10:43 -07:00
committed by GitHub
425 changed files with 27808 additions and 6682 deletions

View File

@@ -1,22 +0,0 @@
[
{
"fileName": "main-overview.mdc",
"path": ".cursor/rules",
"description": "Complete system overview of the identity verification platform, covering the passport/ID verification workflow, zero-knowledge proof system, and compliance mechanisms"
},
{
"fileName": "mobile-sdk-migration.mdc",
"path": ".cursor/rules",
"description": "Comprehensive migration strategy and testing-first approach for porting identity verification logic from app to mobile-sdk-alpha package with detailed checklist and validation workflow"
},
{
"fileName": "technical-specification.mdc",
"path": ".cursor/rules",
"description": "Consolidated technical implementation specification for zero-knowledge proof circuits, data models, verification workflows, and implementation requirements with performance constraints"
},
{
"fileName": "compliance-verification.mdc",
"path": ".cursor/rules",
"description": "Critical compliance verification requirements for OFAC checks, age verification, and forbidden country validation with specific implementation details and constraints"
}
]

19
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,19 @@
## Summary
<!-- Brief description of changes -->
## Test plan
<!-- How was this tested? -->
---
### Native Consolidation Checklist
<!-- Check items that apply to this PR. Delete section if not touching native code. -->
- [ ] CONTRACTS.md reviewed - no unintended contract changes
- [ ] Layer 1 bridge contract tests pass (`cd app && yarn jest:run` / `yarn workspace @selfxyz/rn-sdk-test-app test`)
- [ ] Layer 3 builds pass (app iOS, RN test app iOS, RN test app Android)
- [ ] Layer 4 manual smoke test signed off (if consolidation PR)
- [ ] No new native business logic added (logic belongs in TypeScript)

View File

@@ -0,0 +1,25 @@
name: Find iOS Simulator
description: Finds an available iPhone simulator on the runner and outputs its UUID.
outputs:
id:
description: UUID of the first available iPhone simulator
value: ${{ steps.find.outputs.id }}
runs:
using: composite
steps:
- id: find
shell: bash
run: |
SIM_ID=$(xcrun simctl list devices available -j | python3 -c "
import json, sys
data = json.load(sys.stdin)
for runtime, devices in data['devices'].items():
if 'iOS' in runtime:
for d in devices:
if 'iPhone' in d['name'] and d['isAvailable']:
print(d['udid']); sys.exit(0)
sys.exit(1)")
echo "Found simulator: $SIM_ID"
echo "id=$SIM_ID" >> "$GITHUB_OUTPUT"

View File

@@ -5,12 +5,24 @@ permissions:
on:
pull_request:
paths: ["packages/kmp-sdk/**", "packages/kmp-test-app/**"]
paths:
- "packages/kmp-sdk/**"
- "packages/kmp-sdk-test-app/**"
- "packages/kmp-minipay-sample/**"
- ".github/workflows/kmp-ci.yml"
- ".github/actions/**"
push:
branches: [dev, staging, main]
paths: ["packages/kmp-sdk/**", "packages/kmp-test-app/**"]
paths:
- "packages/kmp-sdk/**"
- "packages/kmp-sdk-test-app/**"
- "packages/kmp-minipay-sample/**"
- ".github/workflows/kmp-ci.yml"
- ".github/actions/**"
jobs:
# ── KMP SDK ──────────────────────────────────────────────
kmp-sdk-tests:
runs-on: ubuntu-latest
timeout-minutes: 60
@@ -34,12 +46,68 @@ jobs:
name: kmp-sdk-test-results
path: packages/kmp-sdk/shared/build/reports/tests/
kmp-sdk-android-build:
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
working-directory: packages/kmp-sdk
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: ./.github/actions/cache-gradle
- uses: gradle/actions/setup-gradle@v4
with:
cache-disabled: true
- run: ./gradlew :shared:assembleDebug
kmp-sdk-ios-framework:
runs-on: namespace-profile-apple-silicon-6cpu
timeout-minutes: 60
defaults:
run:
working-directory: packages/kmp-sdk
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: ./.github/actions/cache-gradle
- uses: gradle/actions/setup-gradle@v4
with:
cache-disabled: true
- run: ./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
kmp-sdk-ios-test:
runs-on: namespace-profile-apple-silicon-6cpu
timeout-minutes: 60
defaults:
run:
working-directory: packages/kmp-sdk
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: ./.github/actions/cache-gradle
- uses: gradle/actions/setup-gradle@v4
with:
cache-disabled: true
- run: ./gradlew :shared:iosSimulatorArm64Test
# ── KMP Test App ─────────────────────────────────────────
kmp-test-app-tests:
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
working-directory: packages/kmp-test-app
working-directory: packages/kmp-sdk-test-app
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
@@ -55,4 +123,134 @@ jobs:
if: always()
with:
name: kmp-test-app-test-results
path: packages/kmp-test-app/composeApp/build/reports/tests/
path: packages/kmp-sdk-test-app/composeApp/build/reports/tests/
kmp-test-app-android-build:
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
working-directory: packages/kmp-sdk-test-app
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: ./.github/actions/cache-gradle
- uses: gradle/actions/setup-gradle@v4
with:
cache-disabled: true
- run: ./gradlew :composeApp:assembleDebug
kmp-test-app-ios-build:
runs-on: namespace-profile-apple-silicon-6cpu
timeout-minutes: 60
defaults:
run:
working-directory: packages/kmp-sdk-test-app
steps:
- uses: actions/checkout@v4
- name: Generate token for private dependencies
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Configure git for HTTPS dependency fetch
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: ./.github/actions/cache-gradle
- uses: gradle/actions/setup-gradle@v4
with:
cache-disabled: true
- name: Build KMP framework for iOS
working-directory: packages/kmp-sdk
run: ./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
- name: Install CocoaPods dependencies
working-directory: packages/kmp-sdk-test-app/iosApp
run: pod install
- name: Find iOS Simulator
id: sim
uses: ./.github/actions/find-ios-simulator
- name: Build iOS app
working-directory: packages/kmp-sdk-test-app/iosApp
run: |
xcodebuild -workspace iosApp.xcworkspace \
-scheme iosApp \
-sdk iphonesimulator \
-destination "id=${{ steps.sim.outputs.id }}" \
ONLY_ACTIVE_ARCH=YES \
build
# ── KMP Minipay Sample ──────────────────────────────────
kmp-minipay-android-build:
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
working-directory: packages/kmp-minipay-sample
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: ./.github/actions/cache-gradle
- uses: gradle/actions/setup-gradle@v4
with:
cache-disabled: true
- run: ./gradlew :composeApp:assembleDebug
kmp-minipay-ios-build:
runs-on: namespace-profile-apple-silicon-6cpu
timeout-minutes: 60
defaults:
run:
working-directory: packages/kmp-minipay-sample
steps:
- uses: actions/checkout@v4
- name: Generate token for private dependencies
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Configure git for HTTPS dependency fetch
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- uses: ./.github/actions/cache-gradle
- uses: gradle/actions/setup-gradle@v4
with:
cache-disabled: true
- name: Build KMP framework for iOS
working-directory: packages/kmp-sdk
run: ./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
- name: Find iOS Simulator
id: sim
uses: ./.github/actions/find-ios-simulator
- name: Resolve SPM dependencies
working-directory: packages/kmp-minipay-sample/iosApp
run: |
xcodebuild -project iosApp.xcodeproj \
-resolvePackageDependencies
- name: Build iOS app
working-directory: packages/kmp-minipay-sample/iosApp
run: |
xcodebuild -project iosApp.xcodeproj \
-scheme iosApp \
-sdk iphonesimulator \
-destination "id=${{ steps.sim.outputs.id }}" \
ONLY_ACTIVE_ARCH=YES \
ARCHS=arm64 \
SWIFT_ENABLE_EXPLICIT_MODULES=NO \
build

View File

@@ -202,319 +202,3 @@ jobs:
# Run jest through yarn to avoid the build:deps step since CI already built dependencies
yarn test:ci
working-directory: ./app
build-ios:
# This is mostly covered in mobile-e2e.yml so we don't need to run it here frequently
if: github.event_name == 'workflow_dispatch'
# runs-on: macos-latest-large
runs-on: namespace-profile-apple-silicon-6cpu
needs: build-deps
timeout-minutes: 60
env:
# iOS project configuration - hardcoded for CI stability
IOS_PROJECT_NAME: "Self"
IOS_PROJECT_SCHEME: "OpenPassport"
steps:
- uses: actions/checkout@v6
- name: Read and sanitize Node.js version
shell: bash
run: |
if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then
echo "❌ .nvmrc is missing or empty"; exit 1;
fi
VERSION="$(tr -d '\r\n' < .nvmrc)"
VERSION="${VERSION#v}"
if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
echo "Invalid .nvmrc content: '$VERSION'"; exit 1;
fi
echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV"
echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV"
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Enable Corepack
run: corepack enable
- name: Activate Yarn 4.12.0
run: corepack prepare yarn@4.12.0 --activate
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ env.XCODE_VERSION }}
- name: Configure Xcode path
run: |
echo "🔧 Configuring Xcode path to fix iOS SDK issues..."
# Fix for macOS 15 runner iOS SDK issues
# See: https://github.com/actions/runner-images/issues/12758
sudo xcode-select --switch /Applications/Xcode_${{ env.XCODE_VERSION }}.app
echo "✅ Xcode path configured"
# Verify Xcode setup
echo "Xcode version:"
xcodebuild -version
echo "Xcode path:"
xcode-select -p
- name: Check Java installation
run: |
echo "INSTALL_JAVA=false" >> "$GITHUB_ENV"
if command -v java &> /dev/null && java -version &> /dev/null; then
echo "Java already installed: $(java -version 2>&1 | head -n 1)"
else
echo "Java not found or not working, will install..."
echo "INSTALL_JAVA=true" >> "$GITHUB_ENV"
fi
- name: Setup Java environment
if: env.INSTALL_JAVA == 'true'
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ env.RUBY_VERSION }}
bundler-cache: false
- name: Cache Yarn
uses: ./.github/actions/cache-yarn
with:
path: |
.yarn/cache
node_modules
app/node_modules
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}
- name: Cache Ruby gems
uses: ./.github/actions/cache-bundler
with:
path: app/vendor/bundle
lock-file: app/Gemfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
- name: Cache Pods
uses: ./.github/actions/cache-pods
with:
path: |
app/ios/Pods
~/Library/Caches/CocoaPods
lockfile: app/ios/Podfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}
- name: Cache Xcode build
uses: actions/cache@v4
with:
path: |
app/ios/build
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/com.apple.dt.Xcode
key: ${{ runner.os }}-xcode-${{ env.XCODE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock', 'app/ios/OpenPassport.xcworkspace/contents.xcworkspacedata', 'app/ios/Self.xcworkspace/contents.xcworkspacedata') }}
restore-keys: |
${{ runner.os }}-xcode-${{ env.XCODE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }}-
${{ runner.os }}-xcode-${{ env.XCODE_VERSION }}-
- name: Cache Xcode Index
uses: actions/cache@v4
with:
path: app/ios/build/Index.noindex
key: ${{ runner.os }}-xcode-index-${{ env.XCODE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-xcode-index-${{ env.XCODE_VERSION }}-
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install Mobile Dependencies
uses: ./.github/actions/yarn-install
- name: Cache Built Dependencies
id: built-deps
uses: ./.github/actions/cache-built-deps
with:
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}
- name: Build dependencies (cache miss)
# if: steps.built-deps.outputs.cache-hit != 'true'
env:
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token || '' }}
run: |
echo "Cache miss for built dependencies. Building now..."
yarn workspace @selfxyz/mobile-app run build:deps
- name: Install Ruby Dependencies
run: |
echo "Installing Ruby dependencies..."
bundle config set --local path 'vendor/bundle'
bundle install --jobs 4 --retry 3
working-directory: ./app
- name: Install iOS Dependencies
uses: nick-fields/retry@v3
with:
timeout_minutes: 20
max_attempts: 3
retry_wait_seconds: 10
command: |
cd app/ios
bundle exec bash scripts/pod-install-with-cache-fix.sh
env:
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Resolve iOS workspace
run: |
WORKSPACE_OPEN="ios/OpenPassport.xcworkspace"
WORKSPACE_SELF="ios/Self.xcworkspace"
if xcodebuild -list -workspace "$WORKSPACE_OPEN" 2>/dev/null | grep -q "OpenPassport"; then
WORKSPACE_PATH="$WORKSPACE_OPEN"
else
WORKSPACE_PATH="$WORKSPACE_SELF"
fi
echo "WORKSPACE_PATH=$WORKSPACE_PATH" >> "$GITHUB_ENV"
echo "Resolved workspace: $WORKSPACE_PATH"
working-directory: ./app
- name: Verify iOS Workspace
run: |
echo "Verifying iOS workspace setup..."
if [ -z "$WORKSPACE_PATH" ]; then
echo "❌ WORKSPACE_PATH is not set"
exit 1
fi
if [ ! -d "$WORKSPACE_PATH" ]; then
echo "❌ Workspace not found at: $WORKSPACE_PATH"
echo "Available workspaces:"
find ios -name "*.xcworkspace" -type d
exit 1
fi
if [ ! -d "ios/Pods" ]; then
echo "❌ Pods directory is missing"
exit 1
fi
# Verify scheme exists by listing available schemes
echo "Verifying scheme availability..."
AVAILABLE_SCHEMES=$(xcodebuild -list -workspace "$WORKSPACE_PATH" 2>/dev/null | grep -A 200 "Schemes:" | grep -v "Schemes:" | xargs)
echo "Available schemes (first 20): $(echo $AVAILABLE_SCHEMES | cut -d' ' -f1-20)..."
if [[ ! "$AVAILABLE_SCHEMES" =~ ${{ env.IOS_PROJECT_SCHEME }} ]]; then
echo "❌ Scheme '${{ env.IOS_PROJECT_SCHEME }}' not found"
echo "Full scheme list:"
xcodebuild -list -workspace "$WORKSPACE_PATH" 2>/dev/null | grep -A 200 "Schemes:" | grep -v "Schemes:" | head -50
exit 1
fi
echo "✅ iOS workspace is properly configured"
echo "✅ Using workspace: $WORKSPACE_PATH"
echo "✅ Using scheme: ${{ env.IOS_PROJECT_SCHEME }}"
working-directory: ./app
- name: Build iOS
run: |
echo "Building iOS app for simulator (no signing required)..."
echo "Project: ${{ env.IOS_PROJECT_NAME }}, Scheme: ${{ env.IOS_PROJECT_SCHEME }}"
# Build for iOS Simulator to avoid code signing issues in CI
xcodebuild -workspace "$WORKSPACE_PATH" \
-scheme ${{ env.IOS_PROJECT_SCHEME }} \
-configuration Release \
-sdk iphonesimulator \
-destination "generic/platform=iOS Simulator" \
-derivedDataPath ios/build \
-jobs "$(sysctl -n hw.ncpu)" \
-parallelizeTargets \
-quiet || { echo "❌ iOS build failed"; exit 1; }
echo "✅ iOS build succeeded"
working-directory: ./app
build-android:
runs-on: ubuntu-latest
needs: build-deps
# This is mostly covered in mobile-e2e.yml so we don't need to run it here frequently
if: github.event_name == 'workflow_dispatch'
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- name: Read and sanitize Node.js version
shell: bash
run: |
if [ ! -f .nvmrc ] || [ -z "$(cat .nvmrc)" ]; then
echo "❌ .nvmrc is missing or empty"; exit 1;
fi
VERSION="$(tr -d '\r\n' < .nvmrc)"
VERSION="${VERSION#v}"
if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
echo "Invalid .nvmrc content: '$VERSION'"; exit 1;
fi
echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV"
echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV"
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Enable Corepack
run: corepack enable
- name: Activate Yarn 4.12.0
run: corepack prepare yarn@4.12.0 --activate
- name: Cache Yarn
uses: ./.github/actions/cache-yarn
with:
path: |
.yarn/cache
node_modules
app/node_modules
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}
- name: Free up disk space
uses: ./.github/actions/free-disk-space
- name: Cache Gradle
uses: ./.github/actions/cache-gradle
with:
cache-version: ${{ env.GH_CACHE_VERSION }}
- name: Setup Java environment
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
accept-android-sdk-licenses: true
- name: Cache NDK
id: ndk-cache
uses: actions/cache@v4
with:
path: ${{ env.ANDROID_HOME }}/ndk/${{ env.ANDROID_NDK_VERSION }}
key: ${{ runner.os }}-ndk-${{ env.ANDROID_NDK_VERSION }}
- name: Install NDK
if: steps.ndk-cache.outputs.cache-hit != 'true'
uses: nick-fields/retry@v3
with:
timeout_minutes: 15
max_attempts: 3
retry_wait_seconds: 10
command: sdkmanager "ndk;${{ env.ANDROID_NDK_VERSION }}"
- name: Install Mobile Dependencies
uses: ./.github/actions/yarn-install
- name: Cache Built Dependencies
id: built-deps
uses: ./.github/actions/cache-built-deps
with:
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.NODE_VERSION_SANITIZED }}
- name: Build dependencies (cache miss)
if: steps.built-deps.outputs.cache-hit != 'true'
run: |
echo "Cache miss for built dependencies. Building now..."
yarn workspace @selfxyz/mobile-app run build:deps
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Setup Android private modules
run: |
cd ${{ env.APP_PATH }}
PLATFORM=android node scripts/setup-private-modules.cjs
env:
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Android (with AAPT2 symlink fix)
run: yarn android:ci
working-directory: ./app
- name: Clean up Gradle build artifacts
uses: ./.github/actions/cleanup-gradle-artifacts

View File

@@ -690,6 +690,9 @@ jobs:
CI_IOS_BUILD: ${{ needs.bump-version.outputs.ios_build }}
CI_ANDROID_BUILD: ${{ needs.bump-version.outputs.android_build }}
ENABLE_DEBUG_LOGS: ${{ secrets.ENABLE_DEBUG_LOGS }}
GOOGLE_SIGNIN_ANDROID_CLIENT_ID: ${{ secrets.GOOGLE_SIGNIN_ANDROID_CLIENT_ID }}
GOOGLE_SIGNIN_IOS_CLIENT_ID: ${{ secrets.GOOGLE_SIGNIN_IOS_CLIENT_ID }}
GOOGLE_SIGNIN_WEB_CLIENT_ID: ${{ secrets.GOOGLE_SIGNIN_WEB_CLIENT_ID }}
GRAFANA_LOKI_PASSWORD: ${{ secrets.GRAFANA_LOKI_PASSWORD }}
GRAFANA_LOKI_URL: ${{ secrets.GRAFANA_LOKI_URL }}
GRAFANA_LOKI_USERNAME: ${{ secrets.GRAFANA_LOKI_USERNAME }}
@@ -1165,6 +1168,8 @@ jobs:
ANDROID_PACKAGE_NAME: ${{ secrets.ANDROID_PACKAGE_NAME }}
ENABLE_DEBUG_LOGS: ${{ secrets.ENABLE_DEBUG_LOGS }}
GOOGLE_SIGNIN_ANDROID_CLIENT_ID: ${{ secrets.GOOGLE_SIGNIN_ANDROID_CLIENT_ID }}
GOOGLE_SIGNIN_IOS_CLIENT_ID: ${{ secrets.GOOGLE_SIGNIN_IOS_CLIENT_ID }}
GOOGLE_SIGNIN_WEB_CLIENT_ID: ${{ secrets.GOOGLE_SIGNIN_WEB_CLIENT_ID }}
GRAFANA_LOKI_PASSWORD: ${{ secrets.GRAFANA_LOKI_PASSWORD }}
GRAFANA_LOKI_URL: ${{ secrets.GRAFANA_LOKI_URL }}
GRAFANA_LOKI_USERNAME: ${{ secrets.GRAFANA_LOKI_USERNAME }}

View File

@@ -40,10 +40,8 @@ on:
workflow_dispatch:
jobs:
android-e2e:
name: Android E2E Tests Demo App
# Currently build-only for Android. E2E steps are preserved but skipped (if: false).
# To re-enable full E2E: change `if: false` to `if: true` on emulator steps.
android-build:
name: Android Build Validation Demo App
concurrency:
group: ${{ github.workflow }}-android-${{ github.ref }}
cancel-in-progress: true
@@ -108,23 +106,6 @@ jobs:
max_attempts: 3
retry_wait_seconds: 5
command: yarn install --immutable --silent
- name: Validate Maestro test file
if: false # Skip for build-only test - keep logic for future E2E
run: |
[ -f packages/mobile-sdk-demo/tests/e2e/launch.android.flow.yaml ] || { echo "❌ Android E2E test file missing"; exit 1; }
- name: Cache Maestro
if: false # Skip for build-only test - keep logic for future E2E
id: cache-maestro
uses: actions/cache@v4
with:
path: ~/.maestro
key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }}
- name: Install Maestro
if: false # Skip for build-only test - keep logic for future E2E
run: curl -Ls "https://get.maestro.mobile.dev" | bash
- name: Add Maestro to path
if: false # Skip for build-only test - keep logic for future E2E
run: echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name: Free up disk space
uses: ./.github/actions/free-disk-space
- name: Setup Java environment
@@ -145,11 +126,6 @@ jobs:
max_attempts: 3
retry_wait_seconds: 10
command: sdkmanager "ndk;${{ env.ANDROID_NDK_VERSION }}"
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Build dependencies
run: |
echo "Building dependencies..."
@@ -178,37 +154,6 @@ jobs:
echo "📱 APK size: $APK_SIZE bytes"
echo "🎉 Build verification completed successfully!"
echo " Emulator testing is temporarily disabled - build testing only"
- name: Run Maestro tests on Android
if: false # Skip emulator/E2E for build-only test - keep logic for future E2E
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.ANDROID_API_LEVEL }}
arch: x86_64
target: google_apis
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -camera-front none -memory 8192 -cores 4 -accel on
disable-animations: true
script: |
echo "Installing app on emulator..."
APK_PATH="packages/mobile-sdk-demo/android/app/build/outputs/apk/debug/app-debug.apk"
[ -f "$APK_PATH" ] || { echo "❌ APK not found at $APK_PATH"; exit 1; }
adb install -r "$APK_PATH" || { echo "❌ App installation failed"; exit 1; }
echo "✅ App installed successfully"
echo "⏰ Giving the emulator a moment to settle..."
sleep 5
echo "🎭 Running Maestro tests..."
export MAESTRO_DRIVER_STARTUP_TIMEOUT=180000
maestro test packages/mobile-sdk-demo/tests/e2e/launch.android.flow.yaml --format junit --output packages/mobile-sdk-demo/maestro-results-android.xml
- name: Upload Android test results
if: false # Skip for build-only test - keep logic for future E2E
uses: actions/upload-artifact@v4
with:
name: maestro-results-android
path: packages/mobile-sdk-demo/maestro-results-android.xml
if-no-files-found: warn
ios-e2e:
timeout-minutes: 60

113
.github/workflows/rn-sdk-test-app-ci.yml vendored Normal file
View File

@@ -0,0 +1,113 @@
name: RN SDK Test App CI
permissions:
contents: read
on:
pull_request:
paths:
- "packages/rn-sdk/**"
- "packages/rn-sdk-test-app/**"
- "packages/kmp-sdk/**"
- "packages/self-sdk-swift/**"
- ".github/workflows/rn-sdk-test-app-ci.yml"
- ".github/actions/**"
push:
branches: [dev, staging, main]
paths:
- "packages/rn-sdk/**"
- "packages/rn-sdk-test-app/**"
- "packages/kmp-sdk/**"
- "packages/self-sdk-swift/**"
- ".github/workflows/rn-sdk-test-app-ci.yml"
- ".github/actions/**"
jobs:
types:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Check for nested require() in tests
run: |
if grep -rE "require\(['\"]react(-native)?['\"])" packages/rn-sdk/src/__tests__/ 2>/dev/null; then
echo "❌ Found nested require() patterns that cause OOM in CI"
exit 1
fi
echo "✅ No nested require() patterns found"
- name: Build webview-bridge
run: yarn workspace @selfxyz/webview-bridge build
- name: Typecheck rn-sdk
run: yarn workspace @selfxyz/rn-sdk typecheck
- name: Test rn-sdk
run: yarn workspace @selfxyz/rn-sdk test
- name: Build rn-sdk types
run: yarn workspace @selfxyz/rn-sdk tsup
- name: Typecheck rn-sdk-test-app
run: yarn workspace @selfxyz/rn-sdk-test-app types
android-build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- name: Generate debug keystore
run: |
keytool -genkeypair -v -keystore packages/rn-sdk-test-app/android/app/debug.keystore \
-alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 \
-storepass android -keypass android \
-dname "CN=Android Debug,O=Android,C=US"
- name: Publish KMP SDK to mavenLocal
run: |
cd packages/kmp-sdk && ./gradlew :shared:publishToMavenLocal --quiet
- name: Build Android debug APK
run: |
chmod +x packages/rn-sdk-test-app/android/gradlew
cd packages/rn-sdk-test-app/android && ./gradlew assembleDebug --quiet --parallel --build-cache --no-configuration-cache
ios-build:
runs-on: namespace-profile-apple-silicon-6cpu
timeout-minutes: 45
steps:
- uses: actions/checkout@v6
- name: Generate token for private dependencies
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Configure git for HTTPS dependency fetch
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build webview-bridge
run: yarn workspace @selfxyz/webview-bridge build
- name: Install CocoaPods dependencies
working-directory: packages/rn-sdk-test-app/ios
run: pod install
- name: Find iOS Simulator
id: sim
uses: ./.github/actions/find-ios-simulator
- name: Build iOS app
working-directory: packages/rn-sdk-test-app/ios
run: |
xcodebuild -workspace SelfRNTestApp.xcworkspace \
-scheme SelfRNTestApp \
-sdk iphonesimulator \
-destination "id=${{ steps.sim.outputs.id }}" \
ONLY_ACTIVE_ARCH=YES \
build

45
.github/workflows/swift-sdk-ci.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Swift SDK CI
permissions:
contents: read
on:
pull_request:
paths:
- "packages/self-sdk-swift/**"
- ".github/workflows/swift-sdk-ci.yml"
- ".github/actions/**"
push:
branches: [dev, staging, main]
paths:
- "packages/self-sdk-swift/**"
- ".github/workflows/swift-sdk-ci.yml"
- ".github/actions/**"
jobs:
build:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
runs-on: namespace-profile-apple-silicon-6cpu
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Generate token for private dependencies
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Configure git for HTTPS dependency fetch
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
- name: Resolve Swift package dependencies
working-directory: packages/self-sdk-swift
run: swift package resolve
- name: Build Swift package for iOS Simulator
working-directory: packages/self-sdk-swift
run: |
xcodebuild build \
-scheme SelfSdkSwift \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
ONLY_ACTIVE_ARCH=NO

44
.github/workflows/webview-app-ci.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: WebView App CI
permissions:
contents: read
on:
pull_request:
paths:
- "packages/webview-app/**"
- "packages/webview-bridge/**"
- ".github/workflows/webview-app-ci.yml"
- ".github/actions/**"
push:
branches: [dev, staging, main]
paths:
- "packages/webview-app/**"
- "packages/webview-bridge/**"
- ".github/workflows/webview-app-ci.yml"
- ".github/actions/**"
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build webview-bridge
run: yarn workspace @selfxyz/webview-bridge build
- name: Build webview-app
run: yarn workspace @selfxyz/webview-app build
types:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build webview-bridge
run: yarn workspace @selfxyz/webview-bridge build
- name: Typecheck
run: yarn workspace @selfxyz/webview-app typecheck

48
.github/workflows/webview-bridge-ci.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: WebView Bridge CI
permissions:
contents: read
on:
pull_request:
paths:
- "packages/webview-bridge/**"
- ".github/workflows/webview-bridge-ci.yml"
- ".github/actions/**"
push:
branches: [dev, staging, main]
paths:
- "packages/webview-bridge/**"
- ".github/workflows/webview-bridge-ci.yml"
- ".github/actions/**"
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
run: yarn workspace @selfxyz/webview-bridge build
types:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Typecheck
run: yarn workspace @selfxyz/webview-bridge typecheck
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Test
run: yarn workspace @selfxyz/webview-bridge test

9
.gitignore vendored
View File

@@ -31,6 +31,11 @@ app/platforms/
packages/mobile-sdk-alpha/licenses/
packages/mobile-sdk-alpha/platforms/
# Renamed KMP test app (old folder, build artifacts only)
packages/kmp-test-app/.gradle/
packages/kmp-test-app/build/
packages/kmp-test-app/**/build/
# Private Android modules (cloned at build time)
app/android/android-passport-nfc-reader/
@@ -43,3 +48,7 @@ app/android/android-passport-nfc-reader/
contracts/out/
contracts/cache_forge/
contracts/broadcast/
# Keep RN test app config files tracked (global gitignore may ignore *.config.*)
!packages/rn-sdk-test-app/metro.config.cjs
!packages/rn-sdk-test-app/react-native.config.cjs

View File

@@ -23,6 +23,8 @@ minVersion = "v8.25.0"
description = "global allow lists"
paths = [
'''gitleaks\.toml''',
'''new-common/src/data/mockCertificates\.ts$''',
'''new-common/src/testing/mockAadhaarCert\.ts$''',
'''(?i)\.(?:bmp|gif|jpe?g|png|svg|tiff?)$''',
'''(?i)\.(?:eot|[ot]tf|woff2?)$''',
'''(?i)\.(?:docx?|xlsx?|pdf|bin|socket|vsidx|v2|suo|wsuo|.dll|pdb|exe|gltf)$''',

View File

@@ -46,6 +46,7 @@ Before creating a PR, ensure:
- [ ] PR description includes context for AI reviewers
- [ ] Complex changes have inline comments explaining intent
- [ ] Security-sensitive changes flagged for special review
- [ ] Review/spec text is signal-only: remove non-actionable praise, back-patting, and generic commentary; keep concrete issues, risks, decisions, owners, next steps, and validation evidence
#### Follow-up Planning
@@ -206,18 +207,29 @@ These workspace-specific files override or extend the root instructions for thei
The `specs/` folder contains architecture and implementation specs for the Self SDK project (WebView engine + native shells). These specs are designed to serve as both human documentation and AI agent prompts.
### Spec Structure & Naming Rules
- Do not create one-file folders. If a folder would contain only one markdown file, keep that file at the parent project level.
- File names should describe doc type, not repeat project name when already inside the project folder.
- Preferred project-level names: `INDEX.md`, `OVERVIEW.md`, `PLAN.md`, `STATUS.md`, `HANDOFF.md`, `REVIEW.md`, `ARCHITECTURE.md`, `INITIATIVE.md`.
- `INDEX.md` is navigation only (entrypoint/table of contents for that folder).
- `OVERVIEW.md` is substantive context (architecture/scope/status summary), not just a link list.
- Do not use `INDEX.md` and `OVERVIEW.md` as synonyms for the same purpose.
- Workstream docs under `workstreams/<scope>/` use `SPEC.md` (context + implementation in one file).
- PR execution docs belong under `workstreams/<scope>/plans/<BACKLOG-ID>-<slug>.md`; use one plan file per PR.
- Use suffixed variants (for example `SPEC-<TOPIC>.md`) only when multiple specs of the same type are required in the same folder.
- When renaming/moving spec files, update all references in `specs/`, `AGENTS.md`, and `CLAUDE.md` in the same change.
**Start here:** [specs/README.md](./specs/README.md) — table of contents and reading order.
Key files:
- `specs/SDK-OVERVIEW.md` — Architecture, bridge protocol, module table, decision matrix
- `specs/WAVE-PLAN.md` — Dependency-ordered execution plan for parallel agent work
- `specs/SPEC-GUIDE.md` — How to write specs
- `specs/PROJECT-RULES.md` — Project-specific rules and guardrails
- `specs/person*/OVERVIEW.md` — Workstream orientation (what you own, dependencies)
- `specs/person*/SPEC.md` — Implementation details (chunks, code changes, I/O examples)
- `specs/projects/sdk/INDEX.md` — SDK project entry point, workstream links
- `specs/projects/sdk/OVERVIEW.md` — Architecture, bridge protocol, module table, execution status
- `specs/projects/sdk/workstreams/*/SPEC.md` — Durable workstream context, invariants, backlog, active plan links
- `specs/projects/sdk/workstreams/*/plans/*.md` — PR-sized execution plans
**Before implementing SDK work:** Read `specs/PROJECT-RULES.md` and the relevant workstream's `SPEC.md`. These specs contain explicit constraints ("You will NOT..."), validation commands, and file ownership boundaries that prevent common mistakes.
**Before implementing SDK work:** Read `CLAUDE.md` Key Rules and the relevant workstream `SPEC.md` under `specs/projects/sdk/workstreams/`. These specs contain explicit constraints ("You will NOT..."), validation commands, and file ownership boundaries that prevent common mistakes.
## Scope

View File

@@ -26,9 +26,23 @@ nvm use && corepack enable && yarn install
- **Native handlers are thin wrappers.** No business logic in Kotlin or Swift. All logic lives in TypeScript.
- **Keychain is always native-managed.** No web fallbacks for secure storage. This is a security boundary.
- **No “slop comments.”** Only add comments when they convey non-obvious intent or constraints. Never add generic or chatty comments.
- **Signal over praise in docs/reviews.** Remove feel-good or back-patting text that does not change decisions or actions. Keep only actionable content: concrete issues, risks, decisions, owners, next steps, and validation evidence.
- **Spec naming and structure must be context-first.** Use doc-type file names (for example `OVERVIEW.md`, `SPEC.md`) and do not repeat project prefixes in file names. Use descriptive labels in markdown links — `[SDK Overview](./OVERVIEW.md)` not `[OVERVIEW.md](./OVERVIEW.md)` — so the link text is meaningful without folder context.
- **No singleton spec folders.** Do not create a folder that exists only to hold one markdown file; keep single docs at the nearest meaningful project/shared root.
- **Workstream spec names are fixed.** Under `workstreams/<scope>/`, use `SPEC.md` (context + implementation in one file); use `SPEC-<TOPIC>.md` only when multiple implementation specs are needed in that same folder.
- **Use the two-layer spec model.** `INDEX.md` and `OVERVIEW.md` are stable project context. Each workstream `SPEC.md` is durable context plus backlog. PR execution lives in `workstreams/<scope>/plans/<BACKLOG-ID>-<slug>.md`.
- **Test value over mock wiring.** Prefer tests that validate behavior. Avoid tests that only assert mocks were called unless that is the behavior being validated.
- **PR size target:** 1k3k LOC changed. Smaller is fine for focused fixes. If >3k, add a brief justification for why it cant be split.
- **No generated artifacts in source PRs.** Do not commit build outputs or generated assets unless the build system requires them for runtime or distribution.
- **Each chunk = one PR.** Don't bundle chunks into mega PRs. Keeps reviews fast, reverts clean, and progress visible.
- **TypeScript is the primary surface area.** All core logic (proving machine, state machines, stores, UI) lives in TypeScript in the WebView. Kotlin and Swift exist only for hardware access (NFC, camera, biometrics), OS-level APIs (keychain, lifecycle), and crypto signing/key-gen. Before writing any native code, ask: "Can this run in the WebView?" If yes or maybe, it belongs in TypeScript.
- **Maximize code reuse through `mobile-sdk-alpha`.** Before adding code to `webview-app`, `kmp-sdk`, or `app/`, check if `mobile-sdk-alpha` already has it or should have it. Types, interfaces, constants, parsing, validation, formatting, state machines, and stores belong in the SDK. Migrate shared code to `mobile-sdk-alpha` before building WebView UI that needs it.
- **Bridge protocol is the only coupling.** Native shells and the WebView share a JSON contract, not code. New native handlers must follow the bridge protocol exactly — no custom messaging, no side channels, no platform-specific extensions. The WebView must not know which native shell it's running inside.
- **Adapter interfaces are the coupling layer.** WebView code imports adapter interfaces from SDK core. Native shells implement bridge handlers. Nobody imports code across the bridge boundary.
- **Fail closed on security-critical boundaries.** Default-deny for protocol compatibility, remote bundle loading, and verification session lifecycle. Reject unknown protocol versions, block remote `devServerUrl` in production.
- **No regressions in the RN app.** Every change to `mobile-sdk-alpha` must be backwards-compatible with the existing Self Wallet app.
- **Specs stay current.** When implementation deviates from the spec, update the spec. A stale spec is worse than no spec.
- **Constraint tie-breaker.** If rules conflict: correctness and security first, then scope/clarity (small PRs, small files), then reuse. Document the tradeoff in the spec.
## Specs & Planning
@@ -36,29 +50,46 @@ nvm use && corepack enable && yarn install
### Spec System (`specs/`)
| File | Purpose | When to Read |
| -------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------ |
| [README.md](./specs/README.md) | Table of contents, reading order | First. Always. |
| [SPEC-GUIDE.md](./specs/SPEC-GUIDE.md) | How to write specs (three-tier system, review checklist, AI agent guidelines) | Before writing or reviewing any spec |
| [TEMPLATES.md](./specs/TEMPLATES.md) | Copy-paste templates for all three tiers | When creating a new spec |
| [PROJECT-RULES.md](./specs/PROJECT-RULES.md) | Project-specific rules and guardrails | Before starting any implementation work |
| [SDK-OVERVIEW.md](./specs/SDK-OVERVIEW.md) | Architecture, bridge protocol, module table, decision matrix | For system-level context |
| [WAVE-PLAN.md](./specs/WAVE-PLAN.md) | Dependency-ordered execution plan | When planning which chunks to execute next |
| File | Purpose | When to Read |
| ------------------------------------------------ | ------------------------------------------- | ------------------------ |
| [Specs README](./specs/README.md) | Table of contents, reading order | First. Always. |
| [Templates](./specs/framework/TEMPLATES.md) | Copy-paste templates for all three tiers | When creating a new spec |
| [SDK Overview](./specs/projects/sdk/OVERVIEW.md) | Architecture, bridge protocol, module table | For system-level context |
Workstream specs live in `specs/person*-*/` with `OVERVIEW.md` (stable orientation) and `SPEC.md` (living implementation details).
Workstream specs live in `specs/projects/sdk/workstreams/*/` with `SPEC.md` (living implementation details).
### Spec-Reading Protocol (for chunk execution)
To execute a chunk:
1. Read `specs/projects/sdk/INDEX.md` — find your workstream
2. Read the workstream `SPEC.md` — find your chunk
3. If you need architecture context, read the project `OVERVIEW.md`
That's it. Do not read framework docs unless you are writing a new spec.
### Planning Protocol
1. **Read** `specs/PROJECT-RULES.md` and the relevant workstream specs — understand the current state and constraints
2. **Write a plan to disk** — use the appropriate tier from `specs/TEMPLATES.md`:
- **Large features / new workstreams:** Create a full implementation spec (`specs/person-scope/SPEC.md`)
- **Medium features / multi-chunk work:** Create a session plan file in `specs/` or update the relevant SPEC.md
- **Small features / single-chunk fixes:** Add a chunk to an existing SPEC.md, or create a minimal plan in the spec folder
1. **Read** the relevant workstream specs and this file's Key Rules — understand the current state and constraints
2. **Write a plan to disk** — use the appropriate tier from `specs/framework/TEMPLATES.md`:
- **Large features / new workstreams:** Create a full implementation spec (`specs/projects/sdk/workstreams/<scope>/SPEC.md`)
- **Medium features / multi-chunk work:** Create a plan file in `workstreams/<scope>/plans/` named `<BACKLOG-ID>-<slug>.md` and link it from the backlog in the relevant `SPEC.md`
- **Small features / single-chunk fixes:** Create a minimal plan file in `workstreams/<scope>/plans/` named `<BACKLOG-ID>-<slug>.md` or add the chunk to an existing active plan
3. **Include in every plan:** scope of work, files modified, I/O examples, validation command, definition of done
4. **Then implement** — update chunk status as you complete work
5. **After completion:** Mark chunks done in both SPEC.md and OVERVIEW.md status checklists
5. **After completion:** Mark chunks done in SPEC.md status tables. Review status checklists at session start — if something is marked "Done" that isn't, or "Pending" that's in progress, fix it first.
Quick-start prompts for creating new specs are in [SPEC-GUIDE.md](./specs/SPEC-GUIDE.md#quick-start).
### Spec-Writing Guidelines
When writing specs, follow these principles so they work as AI agent prompts:
- **Use second person.** "You are making X portable" not "X should be made portable."
- **Be explicit about constraints.** "You will NOT modify..." not just "Focus on..."
- **Provide exact file paths with line numbers.** `src/proving/provingMachine.ts:543` not "the proving machine file."
- **State the validation command.** Agents will run it. If it's not there, they'll skip validation.
- **One chunk = one self-contained prompt.** The chunk must include enough context to execute without reading the full spec.
- **One PR = one plan file.** A plan file is the execution handoff. It must be self-contained enough that a new agent can pick it up after session loss.
- **Use `--remote` for M and L chunks.** Medium and large chunks benefit from `claude --remote` so work continues in the background.
### Why Even Minor Features

View File

@@ -258,5 +258,5 @@ See `.cursor/rules/test-memory-optimization.mdc` for comprehensive guidelines, e
The Self Wallet app serves as a **test environment** for the SDK refactor. For SDK architecture context:
- **[SDK Specs](../specs/README.md)** — Table of contents and reading order
- **[SDK Overview](../specs/SDK-OVERVIEW.md)** — System architecture, bridge protocol, decision matrix
- **[SDK Overview](../specs/projects/sdk/OVERVIEW.md)** — System architecture, bridge protocol, decision matrix
- **[SDK Project Index](../specs/projects/sdk/INDEX.md)** — Workstream links and entry point

View File

@@ -23,8 +23,8 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1220.0)
aws-sdk-core (3.242.0)
aws-partitions (1.1223.0)
aws-sdk-core (3.243.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -35,8 +35,8 @@ GEM
aws-sdk-kms (1.122.0)
aws-sdk-core (~> 3, >= 3.241.4)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.213.0)
aws-sdk-core (~> 3, >= 3.241.4)
aws-sdk-s3 (1.215.0)
aws-sdk-core (~> 3, >= 3.243.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
@@ -237,7 +237,7 @@ GEM
i18n (1.14.8)
concurrent-ruby (~> 1.0)
jmespath (1.6.2)
json (2.18.1)
json (2.19.0)
jwt (2.10.2)
base64
logger (1.7.0)
@@ -271,7 +271,7 @@ GEM
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.2.1)
retriable (3.3.0)
rexml (3.4.4)
rouge (3.28.0)
ruby-macho (2.5.1)

View File

@@ -2,9 +2,31 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
// tsup wraps require() as __require() in ESM builds for externalized modules.
// Metro's dependency collector only recognizes standard require() calls, so __require()
// calls are invisible to bundling. This plugin converts them back to require() so Metro
// can resolve and include the assets (e.g. .lottie files from the SDK dist).
function rewriteDunderRequire() {
return {
visitor: {
CallExpression(path) {
if (
path.node.callee.type === 'Identifier' &&
path.node.callee.name === '__require' &&
path.node.arguments.length === 1 &&
path.node.arguments[0].type === 'StringLiteral'
) {
path.node.callee.name = 'require';
}
},
},
};
}
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
rewriteDunderRequire,
[
'module-resolver',
{

View File

@@ -1,6 +1,8 @@
ENABLE_DEBUG_LOGS=
GITLEAKS_LICENSE=
GOOGLE_SIGNIN_ANDROID_CLIENT_ID=
GOOGLE_SIGNIN_IOS_CLIENT_ID=
GOOGLE_SIGNIN_WEB_CLIENT_ID=
GRAFANA_LOKI_URL=
GRAFANA_LOKI_USERNAME=
GRAFANA_LOKI_PASSWORD=
@@ -9,4 +11,3 @@ MIXPANEL_NFC_PROJECT_TOKEN=
SEGMENT_KEY=
SENTRY_DSN=
SUMSUB_TEE_URL=
IS_TEST_BUILD=

View File

@@ -13,6 +13,9 @@ export const ENABLE_DEBUG_LOGS = process.env.ENABLE_DEBUG_LOGS === 'true';
export const GOOGLE_SIGNIN_ANDROID_CLIENT_ID =
process.env.GOOGLE_SIGNIN_ANDROID_CLIENT_ID;
export const GOOGLE_SIGNIN_IOS_CLIENT_ID =
process.env.GOOGLE_SIGNIN_IOS_CLIENT_ID;
export const GOOGLE_SIGNIN_WEB_CLIENT_ID =
process.env.GOOGLE_SIGNIN_WEB_CLIENT_ID;

View File

@@ -1,4 +1,7 @@
ANDROID_KEYSTORE=
GOOGLE_SIGNIN_ANDROID_CLIENT_ID=
GOOGLE_SIGNIN_IOS_CLIENT_ID=
GOOGLE_SIGNIN_WEB_CLIENT_ID=
ANDROID_KEYSTORE_PASSWORD=
ANDROID_KEY_ALIAS=
ANDROID_KEY_PASSWORD=

View File

@@ -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
// LiveMRZScannerView.swift
import SwiftUI
import QKMRZParser
@@ -13,177 +11,11 @@ struct LiveMRZScannerView: View {
var onScanComplete: ((QKMRZResult) -> Void)? = nil
var onScanResultAsDict: (([String: Any]) -> Void)? = nil
func singleCorrectDocumentNumberInMRZ(result: String, docNumber: String, parser: QKMRZParser) -> QKMRZResult? {
let replacements: [Character: [Character]] = [
// "0": ["O", "D"],
// "1": ["I"],
"O": ["0"],
"D": ["0"],
"I": ["1"],
"L": ["1"],
"S": ["5"],
"G": ["6"],
// "2": ["Z"], "Z": ["2"],
// "8": ["B"], "B": ["8"]
]
let lines = result.components(separatedBy: "\n")
guard lines.count >= 2 else { return nil }
for (i, char) in docNumber.enumerated() {
if let subs = replacements[char] {
for sub in subs {
var chars = Array(docNumber)
chars[i] = sub
let candidate = String(chars)
if let range = lines[1].range(of: docNumber) {
var newLine = lines[1]
let start = newLine.distance(from: newLine.startIndex, to: range.lowerBound)
var lineChars = Array(newLine)
let docNumChars = Array(candidate)
for j in 0..<min(docNumber.count, docNumChars.count) {
lineChars[start + j] = docNumChars[j]
}
newLine = String(lineChars)
var newLines = lines
newLines[1] = newLine
let correctedMRZ = newLines.joined(separator: "\n")
// print("Trying candidate: \(candidate), correctedMRZ: \(correctedMRZ)")
if let correctedResult = parser.parse(mrzString: correctedMRZ) {
if correctedResult.isDocumentNumberValid {
return correctedResult
}
}
}
}
}
}
return nil
}
private func mapVisionResultToDictionary(_ result: QKMRZResult) -> [String: Any] {
return [
"documentType": result.documentType,
"countryCode": result.countryCode,
"surnames": result.surnames,
"givenNames": result.givenNames,
"documentNumber": result.documentNumber,
"nationalityCountryCode": result.nationalityCountryCode,
"dateOfBirth": result.birthdate?.description ?? "",
"sex": result.sex ?? "",
"expiryDate": result.expiryDate?.description ?? "",
"personalNumber": result.personalNumber,
"personalNumber2": result.personalNumber2 ?? "",
"isDocumentNumberValid": result.isDocumentNumberValid,
"isBirthdateValid": result.isBirthdateValid,
"isExpiryDateValid": result.isExpiryDateValid,
"isPersonalNumberValid": result.isPersonalNumberValid ?? false,
"allCheckDigitsValid": result.allCheckDigitsValid
]
}
private func correctBelgiumDocumentNumber(result: String) -> String? {
// Belgium TD1 format: IDBEL000001115<7027
let line1RegexPattern = "IDBEL(?<doc9>[A-Z0-9]{9})<(?<doc3>[A-Z0-9<]{3})(?<checkDigit>\\d)"
guard let line1Regex = try? NSRegularExpression(pattern: line1RegexPattern) else { return nil }
let line1Matcher = line1Regex.firstMatch(in: result, options: [], range: NSRange(location: 0, length: result.count))
if let line1Matcher = line1Matcher {
let doc9Range = line1Matcher.range(withName: "doc9")
let doc3Range = line1Matcher.range(withName: "doc3")
let checkDigitRange = line1Matcher.range(withName: "checkDigit")
let doc9 = (result as NSString).substring(with: doc9Range)
let doc3 = (result as NSString).substring(with: doc3Range)
let checkDigit = (result as NSString).substring(with: checkDigitRange)
if let cleanedDoc = cleanBelgiumDocumentNumber(doc9: doc9, doc3: doc3, checkDigit: checkDigit) {
let correctedMRZLine = "IDBEL\(cleanedDoc)\(checkDigit)"
return correctedMRZLine
}
}
return nil
}
private func cleanBelgiumDocumentNumber(doc9: String, doc3: String, checkDigit: String) -> String? {
// For Belgium TD1 format: IDBEL000001115<7027
// doc9 = "000001115" (9 digits)
// doc3 = "702" (3 digits after <)
// checkDigit = "7" (single check digit)
var cleanDoc9 = doc9
// Strip first 3 characters
let startIndex = cleanDoc9.index(cleanDoc9.startIndex, offsetBy: 3)
cleanDoc9 = String(cleanDoc9[startIndex...])
let fullDocumentNumber = cleanDoc9 + doc3
return fullDocumentNumber
}
private func isValidMRZResult(_ result: QKMRZResult) -> Bool {
return result.isDocumentNumberValid && result.isExpiryDateValid && result.isBirthdateValid
}
private func handleValidMRZResult(_ result: QKMRZResult) {
parsedMRZ = result
scanComplete = true
onScanComplete?(result)
onScanResultAsDict?(mapVisionResultToDictionary(result))
}
private func processBelgiumDocument(result: String, parser: QKMRZParser) -> QKMRZResult? {
print("[LiveMRZScannerView] Processing Belgium document")
guard let correctedBelgiumLine = correctBelgiumDocumentNumber(result: result) else {
print("[LiveMRZScannerView] Failed to correct Belgium document number")
return nil
}
// print("[LiveMRZScannerView] Belgium corrected line: \(correctedBelgiumLine)")
// Split MRZ into lines and replace the first line
let lines = result.components(separatedBy: "\n")
guard lines.count >= 3 else {
print("[LiveMRZScannerView] Invalid MRZ format - not enough lines")
return nil
}
let originalFirstLine = lines[0]
// print("[LiveMRZScannerView] Original first line: \(originalFirstLine)")
// Pad the corrected line to 30 characters (TD1 format)
let paddedCorrectedLine = correctedBelgiumLine.padding(toLength: 30, withPad: "<", startingAt: 0)
// print("[LiveMRZScannerView] Padded corrected line: \(paddedCorrectedLine)")
// Reconstruct the MRZ with the corrected first line
var correctedLines = lines
correctedLines[0] = paddedCorrectedLine
let correctedMRZString = correctedLines.joined(separator: "\n")
// print("[LiveMRZScannerView] Corrected MRZ string: \(correctedMRZString)")
guard let belgiumMRZResult = parser.parse(mrzString: correctedMRZString) else {
print("[LiveMRZScannerView] Belgium MRZ result is not valid")
return nil
}
// print("[LiveMRZScannerView] Belgium MRZ result: \(belgiumMRZResult)")
// Try the corrected MRZ first
if isValidMRZResult(belgiumMRZResult) {
return belgiumMRZResult
}
// If document number is still invalid, try single character correction
if !belgiumMRZResult.isDocumentNumberValid {
if let correctedResult = singleCorrectDocumentNumberInMRZ(result: correctedMRZString, docNumber: belgiumMRZResult.documentNumber, parser: parser) {
// print("[LiveMRZScannerView] Single correction successful: \(correctedResult)")
if isValidMRZResult(correctedResult) {
return correctedResult
}
}
}
return nil
onScanResultAsDict?(MrzResultMapper.toDictionary(result))
}
var body: some View {
@@ -191,35 +23,27 @@ struct LiveMRZScannerView: View {
CameraView(
frameHandler: { image, roi in
if scanComplete { return }
MRZScanner.scan(image: image, roi: roi) { result, boxes in
MrzScanEngine.scan(image: image, roi: roi) { result, boxes in
recognizedText = result
lastMRZDetection = Date()
// print("[LiveMRZScannerView] result: \(result)")
let parser = QKMRZParser(ocrCorrection: false)
if let mrzResult = parser.parse(mrzString: result) {
let doc = mrzResult
// print("[LiveMRZScannerView] doc: \(doc)")
guard !scanComplete else { return }
// Check if already valid
if doc.allCheckDigitsValid {
if mrzResult.allCheckDigitsValid {
handleValidMRZResult(mrzResult)
return
}
// Handle Belgium documents (only if not already valid)
if doc.countryCode == "BEL" {
if let belgiumResult = processBelgiumDocument(result: result, parser: parser) {
if mrzResult.countryCode == "BEL" {
if let belgiumResult = MrzOcrCorrection.processBelgiumDocument(mrzString: result, parser: parser) {
handleValidMRZResult(belgiumResult)
}
return
}
// Handle other documents with invalid document numbers
if !doc.isDocumentNumberValid {
if let correctedResult = singleCorrectDocumentNumberInMRZ(result: result, docNumber: doc.documentNumber, parser: parser) {
// print("[LiveMRZScannerView] correctedDoc: \(correctedResult)")
if !mrzResult.isDocumentNumberValid {
if let correctedResult = MrzOcrCorrection.singleCorrectDocumentNumber(mrzString: result, docNumber: mrzResult.documentNumber, parser: parser) {
if correctedResult.allCheckDigitsValid {
handleValidMRZResult(correctedResult)
}

View File

@@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import QKMRZParser
struct MrzOcrCorrection {
static func isValid(_ result: QKMRZResult) -> Bool {
return result.isDocumentNumberValid && result.isExpiryDateValid && result.isBirthdateValid
}
static func singleCorrectDocumentNumber(mrzString: String, docNumber: String, parser: QKMRZParser) -> QKMRZResult? {
let replacements: [Character: [Character]] = [
"O": ["0"],
"D": ["0"],
"I": ["1"],
"L": ["1"],
"S": ["5"],
"G": ["6"],
]
let lines = mrzString.components(separatedBy: "\n")
guard lines.count >= 2 else { return nil }
// TD1 (3-line): doc number is on line 0; TD3 (2-line): doc number is on line 1
let docNumberLineIndex = lines.count == 3 ? 0 : 1
for (i, char) in docNumber.enumerated() {
if let subs = replacements[char] {
for sub in subs {
var chars = Array(docNumber)
chars[i] = sub
let candidate = String(chars)
if let range = lines[docNumberLineIndex].range(of: docNumber) {
var newLine = lines[docNumberLineIndex]
let start = newLine.distance(from: newLine.startIndex, to: range.lowerBound)
var lineChars = Array(newLine)
let docNumChars = Array(candidate)
for j in 0..<min(docNumber.count, docNumChars.count) {
lineChars[start + j] = docNumChars[j]
}
newLine = String(lineChars)
var newLines = lines
newLines[docNumberLineIndex] = newLine
let correctedMRZ = newLines.joined(separator: "\n")
if let correctedResult = parser.parse(mrzString: correctedMRZ) {
if correctedResult.isDocumentNumberValid {
return correctedResult
}
}
}
}
}
}
return nil
}
static func processBelgiumDocument(mrzString: String, parser: QKMRZParser) -> QKMRZResult? {
guard let correctedLine = correctBelgiumDocumentNumber(mrzString: mrzString) else {
return nil
}
let lines = mrzString.components(separatedBy: "\n")
guard lines.count >= 3 else { return nil }
let paddedLine = correctedLine.padding(toLength: 30, withPad: "<", startingAt: 0)
var correctedLines = lines
correctedLines[0] = paddedLine
let correctedMRZString = correctedLines.joined(separator: "\n")
guard let belgiumResult = parser.parse(mrzString: correctedMRZString) else {
return nil
}
if isValid(belgiumResult) {
return belgiumResult
}
if !belgiumResult.isDocumentNumberValid {
if let correctedResult = singleCorrectDocumentNumber(mrzString: correctedMRZString, docNumber: belgiumResult.documentNumber, parser: parser) {
if isValid(correctedResult) {
return correctedResult
}
}
}
return nil
}
// MARK: - Private
private static func correctBelgiumDocumentNumber(mrzString: String) -> String? {
let line1RegexPattern = "IDBEL(?<doc9>[A-Z0-9]{9})<(?<doc3>[A-Z0-9<]{3})(?<checkDigit>\\d)"
guard let line1Regex = try? NSRegularExpression(pattern: line1RegexPattern) else { return nil }
let line1Matcher = line1Regex.firstMatch(in: mrzString, options: [], range: NSRange(mrzString.startIndex..., in: mrzString))
if let line1Matcher = line1Matcher {
let doc9Range = line1Matcher.range(withName: "doc9")
let doc3Range = line1Matcher.range(withName: "doc3")
let checkDigitRange = line1Matcher.range(withName: "checkDigit")
let doc9 = (mrzString as NSString).substring(with: doc9Range)
let doc3 = (mrzString as NSString).substring(with: doc3Range)
let checkDigit = (mrzString as NSString).substring(with: checkDigitRange)
let startIndex = doc9.index(doc9.startIndex, offsetBy: 3)
let cleanDoc9 = String(doc9[startIndex...])
let fullDocumentNumber = cleanDoc9 + doc3
return "IDBEL\(fullDocumentNumber)\(checkDigit)"
}
return nil
}
}

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import QKMRZParser
struct MrzResultMapper {
static func toDictionary(_ result: QKMRZResult) -> [String: Any] {
return [
"documentType": result.documentType,
"countryCode": result.countryCode,
"surnames": result.surnames,
"givenNames": result.givenNames,
"documentNumber": result.documentNumber,
"nationalityCountryCode": result.nationalityCountryCode,
"dateOfBirth": result.birthdate?.description ?? "",
"sex": result.sex ?? "",
"expiryDate": result.expiryDate?.description ?? "",
"personalNumber": result.personalNumber,
"personalNumber2": result.personalNumber2 ?? "",
"isDocumentNumberValid": result.isDocumentNumberValid,
"isBirthdateValid": result.isBirthdateValid,
"isExpiryDateValid": result.isExpiryDateValid,
"isPersonalNumberValid": result.isPersonalNumberValid ?? false,
"allCheckDigitsValid": result.allCheckDigitsValid
]
}
}

View File

@@ -2,14 +2,10 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
//
// MRZScanner.swift
import Vision
import UIKit
struct SelfMRZScanner {
struct MrzScanEngine {
static func scan(image: UIImage, roi: CGRect? = nil, completion: @escaping (String, [CGRect]) -> Void) {
guard let cgImage = image.cgImage else {
DispatchQueue.main.async {
@@ -31,37 +27,30 @@ struct SelfMRZScanner {
return
}
// print("Found \(observations.count) text observations")
var mrzLines: [String] = []
var boxes: [CGRect] = []
// Sort lines from top to bottom
let sortedObservations = observations.sorted { $0.boundingBox.minY > $1.boundingBox.minY }
for (index, obs) in sortedObservations.enumerated() {
for (_, obs) in sortedObservations.enumerated() {
if let candidate = obs.topCandidates(1).first {
let text = candidate.string
let confidence = candidate.confidence
// print("Line \(index): '\(text)' (confidence: \(confidence), position: \(obs.boundingBox))")
// Check if this looks like an MRZ line (either contains "<" or matches MRZ pattern)
// TD1 format (ID cards): 30 chars, TD3 format (passports): 44 chars
if text.contains("<") ||
text.matches(pattern: "^[A-Z0-9<]{30}$") || //TD1 //case where there's no '<' in MRZ
text.matches(pattern: "^[A-Z0-9<]{44}$") //TD3
{
// print("Matched MRZ pattern: \(text)")
if text.matches(pattern: "^[A-Z0-9<]{30}$") ||
text.matches(pattern: "^[A-Z0-9<]{44}$") {
if let currentWidth = mrzLines.first?.count, currentWidth != text.count {
mrzLines = []
boxes = []
}
mrzLines.append(text)
boxes.append(obs.boundingBox)
// Check if we have a complete MRZ
if (mrzLines.count == 2 && mrzLines.allSatisfy { $0.count == 44 }) || // TD3 - passport
(mrzLines.count == 3 && mrzLines.allSatisfy { $0.count == 30 }) { // TD1 - ID card
if (mrzLines.count == 2 && mrzLines.allSatisfy { $0.count == 44 }) ||
(mrzLines.count == 3 && mrzLines.allSatisfy { $0.count == 30 }) {
break
}
} else {
print("Did not match MRZ pattern: \(text)")
}
}
}
@@ -80,15 +69,12 @@ struct SelfMRZScanner {
request.usesLanguageCorrection = false
request.recognitionLanguages = ["en"]
// Use provided ROI. If not use as bottom 20%
if let roi = roi {
// print("[MRZScanner] Using provided ROI: \(roi) (image size: \(cgImage.width)x\(cgImage.height))")
request.regionOfInterest = roi
} else {
let imageHeight = CGFloat(cgImage.height)
let roiHeight = imageHeight * 0.2 // Bottom 20%
let roiHeight = imageHeight * 0.2
let defaultRoi = CGRect(x: 0, y: 0, width: 1.0, height: roiHeight / imageHeight)
// print("[MRZScanner] Using default ROI: \(defaultRoi) (image size: \(cgImage.width)x\(cgImage.height), roi height: \(roiHeight))")
request.regionOfInterest = defaultRoi
}

View File

@@ -31,6 +31,10 @@
<array>
<string>proofofpassport</string>
<string>com.warroom.proofofpassport</string>
<!-- TODO: Replace with reversed iOS client ID from Google Cloud Console -->
<!-- Format: com.googleusercontent.apps.YOUR-IOS-CLIENT-ID -->
<!-- See: app/.env for instructions on creating the iOS client ID -->
<string>com.googleusercontent.apps.YOUR_CLIENT_ID</string>
</array>
</dict>
</array>

View File

@@ -37,5 +37,9 @@
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>

View File

@@ -39,5 +39,9 @@
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>

View File

@@ -86,7 +86,7 @@ target "Self" do
# Explicitly declare Mixpanel to ensure it's available even in E2E builds
# (NFCPassportReader also includes Mixpanel, but is skipped during E2E testing)
pod "Mixpanel-swift", :modular_headers => true
pod "Mixpanel-swift", "~> 5.0.0", :modular_headers => true
pod "QKMRZScanner"
pod "lottie-ios"
@@ -101,11 +101,8 @@ target "Self" do
# Flipper設定は削除
)
pod "Firebase", :modular_headers => true
pod "FirebaseCore", :modular_headers => true
pod "FirebaseCoreInternal", :modular_headers => true
pod "GoogleUtilities", :modular_headers => true
pod "FirebaseMessaging"
# Firebase pods removed - handled automatically by RNFirebase autolinking
# This resolves GoogleUtilities 7.x vs 8.x version conflicts
if flipper_enabled
pod "RCT-Folly", :podspec => "#{config[:reactNativePath]}/third-party-podspecs/RCT-Folly.podspec"

View File

@@ -5,6 +5,10 @@ PODS:
- AppAuth/Core (2.0.0)
- AppAuth/ExternalUserAgent (2.0.0):
- AppAuth/Core
- AppCheckCore (11.2.0):
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- boost (1.84.0)
- BVLinearGradient (2.8.3):
- React-Core
@@ -37,6 +41,10 @@ PODS:
- ExpoModulesCore
- Expo (52.0.49):
- ExpoModulesCore
- ExpoAdapterGoogleSignIn (16.1.2):
- ExpoModulesCore
- GoogleSignIn (~> 9.0)
- React-Core
- ExpoAsset (11.0.5):
- ExpoModulesCore
- ExpoFileSystem (18.0.12):
@@ -72,141 +80,86 @@ PODS:
- fast_float (6.1.4)
- FBLazyVector (0.77.0)
- FingerprintPro (2.13.0)
- Firebase (10.24.0):
- Firebase/Core (= 10.24.0)
- Firebase/Core (10.24.0):
- Firebase/CoreOnly (11.11.0):
- FirebaseCore (~> 11.11.0)
- Firebase/Messaging (11.11.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 10.24.0)
- Firebase/CoreOnly (10.24.0):
- FirebaseCore (= 10.24.0)
- Firebase/Messaging (10.24.0):
- FirebaseMessaging (~> 11.11.0)
- Firebase/RemoteConfig (11.11.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 10.24.0)
- Firebase/RemoteConfig (10.24.0):
- Firebase/CoreOnly
- FirebaseRemoteConfig (~> 10.24.0)
- FirebaseABTesting (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseAnalytics (10.24.0):
- FirebaseAnalytics/AdIdSupport (= 10.24.0)
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseAnalytics/AdIdSupport (10.24.0):
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleAppMeasurement (= 10.24.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseCore (10.24.0):
- FirebaseCoreInternal (~> 10.0)
- GoogleUtilities/Environment (~> 7.12)
- GoogleUtilities/Logger (~> 7.12)
- FirebaseCoreExtension (10.29.0):
- FirebaseCore (~> 10.0)
- FirebaseCoreInternal (10.29.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- FirebaseInstallations (10.29.0):
- FirebaseCore (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
- PromisesObjC (~> 2.1)
- FirebaseMessaging (10.24.0):
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- GoogleDataTransport (~> 9.3)
- GoogleUtilities/AppDelegateSwizzler (~> 7.8)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/Reachability (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseRemoteConfig (10.24.0):
- FirebaseABTesting (~> 10.0)
- FirebaseCore (~> 10.0)
- FirebaseInstallations (~> 10.0)
- FirebaseRemoteConfigInterop (~> 10.23)
- FirebaseSharedSwift (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- FirebaseRemoteConfigInterop (10.29.0)
- FirebaseSharedSwift (10.29.0)
- FirebaseRemoteConfig (~> 11.11.0)
- FirebaseABTesting (11.11.0):
- FirebaseCore (~> 11.11.0)
- FirebaseCore (11.11.0):
- FirebaseCoreInternal (~> 11.11.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreExtension (11.11.0):
- FirebaseCore (~> 11.11.0)
- FirebaseCoreInternal (11.11.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.11.0):
- FirebaseCore (~> 11.11.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.11.0):
- FirebaseCore (~> 11.11.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Reachability (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- nanopb (~> 3.30910.0)
- FirebaseRemoteConfig (11.11.0):
- FirebaseABTesting (~> 11.0)
- FirebaseCore (~> 11.11.0)
- FirebaseInstallations (~> 11.0)
- FirebaseRemoteConfigInterop (~> 11.0)
- FirebaseSharedSwift (~> 11.0)
- GoogleUtilities/Environment (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseRemoteConfigInterop (11.15.0)
- FirebaseSharedSwift (11.15.0)
- fmt (11.0.2)
- glog (0.3.5)
- GoogleAppMeasurement (10.24.0):
- GoogleAppMeasurement/AdIdSupport (= 10.24.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleAppMeasurement/AdIdSupport (10.24.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 10.24.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleAppMeasurement/WithoutAdIdSupport (10.24.0):
- GoogleUtilities/AppDelegateSwizzler (~> 7.11)
- GoogleUtilities/MethodSwizzler (~> 7.11)
- GoogleUtilities/Network (~> 7.11)
- "GoogleUtilities/NSData+zlib (~> 7.11)"
- nanopb (< 2.30911.0, >= 2.30908.0)
- GoogleDataTransport (9.4.1):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30911.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities (7.13.3):
- GoogleUtilities/AppDelegateSwizzler (= 7.13.3)
- GoogleUtilities/Environment (= 7.13.3)
- GoogleUtilities/ISASwizzler (= 7.13.3)
- GoogleUtilities/Logger (= 7.13.3)
- GoogleUtilities/MethodSwizzler (= 7.13.3)
- GoogleUtilities/Network (= 7.13.3)
- "GoogleUtilities/NSData+zlib (= 7.13.3)"
- GoogleUtilities/Privacy (= 7.13.3)
- GoogleUtilities/Reachability (= 7.13.3)
- GoogleUtilities/SwizzlerTestHelpers (= 7.13.3)
- GoogleUtilities/UserDefaults (= 7.13.3)
- GoogleUtilities/AppDelegateSwizzler (7.13.3):
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleSignIn (9.1.0):
- AppAuth (~> 2.0)
- AppCheckCore (~> 11.0)
- GTMAppAuth (~> 5.0)
- GTMSessionFetcher/Core (~> 3.3)
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (7.13.3):
- GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Privacy
- PromisesObjC (< 3.0, >= 1.2)
- GoogleUtilities/ISASwizzler (7.13.3):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (7.13.3):
- GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/MethodSwizzler (7.13.3):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/Network (7.13.3):
- GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (7.13.3)":
- "GoogleUtilities/NSData+zlib (8.1.0)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (7.13.3)
- GoogleUtilities/Reachability (7.13.3):
- GoogleUtilities/Privacy (8.1.0)
- GoogleUtilities/Reachability (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/SwizzlerTestHelpers (7.13.3):
- GoogleUtilities/MethodSwizzler
- GoogleUtilities/UserDefaults (7.13.3):
- GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GTMAppAuth (5.0.0):
- AppAuth/Core (~> 2.0)
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
- GTMSessionFetcher/Core (3.5.0)
- hermes-engine (0.77.0):
- hermes-engine/Pre-built (= 0.77.0)
- hermes-engine/Pre-built (0.77.0)
@@ -223,11 +176,11 @@ PODS:
- Mixpanel-swift (5.0.0):
- Mixpanel-swift/Complete (= 5.0.0)
- Mixpanel-swift/Complete (5.0.0)
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- NFCPassportReader (2.1.1):
- Mixpanel-swift (~> 5.0.0)
- OpenSSL-Universal (= 1.1.1900)
@@ -1517,7 +1470,7 @@ PODS:
- React-Core
- react-native-nfc-manager (3.17.2):
- React-Core
- react-native-passkey (3.3.1):
- react-native-passkey (3.3.2):
- DoubleConversion
- glog
- hermes-engine
@@ -1916,6 +1869,8 @@ PODS:
- React-logger (= 0.77.0)
- React-perflogger (= 0.77.0)
- React-utils (= 0.77.0)
- RNAppleAuthentication (2.5.1):
- React-Core
- RNCAsyncStorage (2.2.0):
- DoubleConversion
- glog
@@ -1960,16 +1915,16 @@ PODS:
- Yoga
- RNDeviceInfo (15.0.1):
- React-Core
- RNFBApp (19.3.0):
- Firebase/CoreOnly (= 10.24.0)
- RNFBApp (21.14.0):
- Firebase/CoreOnly (= 11.11.0)
- React-Core
- RNFBMessaging (19.3.0):
- Firebase/Messaging (= 10.24.0)
- RNFBMessaging (21.14.0):
- Firebase/Messaging (= 11.11.0)
- FirebaseCoreExtension
- React-Core
- RNFBApp
- RNFBRemoteConfig (19.3.0):
- Firebase/RemoteConfig (= 10.24.0)
- RNFBRemoteConfig (21.14.0):
- Firebase/RemoteConfig (= 11.11.0)
- React-Core
- RNFBApp
- RNGestureHandler (2.22.1):
@@ -1993,6 +1948,28 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNGoogleSignin (16.1.2):
- DoubleConversion
- glog
- GoogleSignIn (~> 9.0)
- hermes-engine
- RCT-Folly (= 2024.11.18.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNInAppBrowser (3.7.0):
- React-Core
- RNKeychain (10.0.0):
@@ -2126,7 +2103,7 @@ PODS:
- ReactCommon/turbomodule/core
- Sentry/HybridSDK (= 8.53.2)
- Yoga
- RNSVG (15.12.1):
- RNSVG (15.14.0):
- DoubleConversion
- glog
- hermes-engine
@@ -2146,9 +2123,9 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNSVG/common (= 15.12.1)
- RNSVG/common (= 15.14.0)
- Yoga
- RNSVG/common (15.12.1):
- RNSVG/common (15.14.0):
- DoubleConversion
- glog
- hermes-engine
@@ -2188,6 +2165,7 @@ DEPENDENCIES:
- EXApplication (from `../node_modules/expo-application/ios`)
- EXConstants (from `../node_modules/expo-constants/ios`)
- Expo (from `../node_modules/expo`)
- "ExpoAdapterGoogleSignIn (from `../node_modules/@react-native-google-signin/google-signin/expo/ios`)"
- ExpoAsset (from `../node_modules/expo-asset/ios`)
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
- ExpoFont (from `../node_modules/expo-font/ios`)
@@ -2195,16 +2173,11 @@ DEPENDENCIES:
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
- fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- Firebase
- FirebaseCore
- FirebaseCoreInternal
- FirebaseMessaging
- fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- GoogleUtilities
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- lottie-ios
- Mixpanel-swift
- Mixpanel-swift (~> 5.0.0)
- "NFCPassportReader (from `git@github.com:selfxyz/NFCPassportReader.git`, commit `9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b`)"
- QKMRZScanner
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
@@ -2280,6 +2253,7 @@ DEPENDENCIES:
- ReactAppDependencyProvider (from `build/generated/ios`)
- ReactCodegen (from `build/generated/ios`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNAppleAuthentication (from `../node_modules/@invertase/react-native-apple-authentication`)"
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
@@ -2287,6 +2261,7 @@ DEPENDENCIES:
- "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)"
- "RNFBRemoteConfig (from `../node_modules/@react-native-firebase/remote-config`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- "RNGoogleSignin (from `../node_modules/@react-native-google-signin/google-signin`)"
- RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNLocalize (from `../node_modules/react-native-localize`)
@@ -2304,10 +2279,10 @@ SPEC REPOS:
- IdensicMobileSDK
trunk:
- AppAuth
- AppCheckCore
- FingerprintPro
- Firebase
- FirebaseABTesting
- FirebaseAnalytics
- FirebaseCore
- FirebaseCoreExtension
- FirebaseCoreInternal
@@ -2316,9 +2291,11 @@ SPEC REPOS:
- FirebaseRemoteConfig
- FirebaseRemoteConfigInterop
- FirebaseSharedSwift
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleSignIn
- GoogleUtilities
- GTMAppAuth
- GTMSessionFetcher
- lottie-ios
- LottieFiles-dotLottie-iOS
- Mixpanel-swift
@@ -2346,6 +2323,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-constants/ios"
Expo:
:path: "../node_modules/expo"
ExpoAdapterGoogleSignIn:
:path: "../node_modules/@react-native-google-signin/google-signin/expo/ios"
ExpoAsset:
:path: "../node_modules/expo-asset/ios"
ExpoFileSystem:
@@ -2512,6 +2491,8 @@ EXTERNAL SOURCES:
:path: build/generated/ios
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
RNAppleAuthentication:
:path: "../node_modules/@invertase/react-native-apple-authentication"
RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
@@ -2526,6 +2507,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/remote-config"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNGoogleSignin:
:path: "../node_modules/@react-native-google-signin/google-signin"
RNInAppBrowser:
:path: "../node_modules/react-native-inappbrowser-reborn"
RNKeychain:
@@ -2559,6 +2542,7 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
AppAuth: 1c1a8afa7e12f2ec3a294d9882dfa5ab7d3cb063
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70
dotlottie-react-native: 056445614fe969f8d8d90a744944089261e6a620
@@ -2566,6 +2550,7 @@ SPEC CHECKSUMS:
EXApplication: 4c72f6017a14a65e338c5e74fca418f35141e819
EXConstants: fcfc75800824ac2d5c592b5bc74130bad17b146b
Expo: 4bb70893882e6382b41d1e910d7226c6a1b85f0a
ExpoAdapterGoogleSignIn: ab4d9fc38cb91077a4138d178395525ec65d0c2e
ExpoAsset: 48386d40d53a8c1738929b3ed509bcad595b5516
ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655
ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188
@@ -2574,28 +2559,29 @@ SPEC CHECKSUMS:
fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
FBLazyVector: 2bc03a5cf64e29c611bbc5d7eb9d9f7431f37ee6
FingerprintPro: 2f419138022451a72f783db9c94967f5a68e9977
Firebase: 91fefd38712feb9186ea8996af6cbdef41473442
FirebaseABTesting: d87f56707159bae64e269757a6e963d490f2eebe
FirebaseAnalytics: b5efc493eb0f40ec560b04a472e3e1a15d39ca13
FirebaseCore: 11dc8a16dfb7c5e3c3f45ba0e191a33ac4f50894
FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f
FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934
FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd
FirebaseMessaging: 4d52717dd820707cc4eadec5eb981b4832ec8d5d
FirebaseRemoteConfig: 95dddc50496b37eef199dadce850d5652b534b43
FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d
FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e
Firebase: 6a8f201c61eda24e98f1ce2b44b1b9c2caf525cc
FirebaseABTesting: 8551c24eb28e300ce697f8eb72c1a519bb96eb40
FirebaseCore: 2321536f9c423b1f857e047a82b8a42abc6d9e2c
FirebaseCoreExtension: 3a64994969dd05f4bcb7e6896c654eded238e75b
FirebaseCoreInternal: 31ee350d87b30a9349e907f84bf49ef8e6791e5a
FirebaseInstallations: 781e0e37aa0e1c92b44d00e739aba79ad31b2dba
FirebaseMessaging: c7be9357fd8ba33bc45b9a6c3cdff0b466e1e2a4
FirebaseRemoteConfig: ca2e03fdd86e31d79ded53e24fa4ac719494dc35
FirebaseRemoteConfigInterop: 1c6135e8a094cc6368949f5faeeca7ee8948b8aa
FirebaseSharedSwift: e17c654ef1f1a616b0b33054e663ad1035c8fd40
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
GoogleAppMeasurement: f3abf08495ef2cba7829f15318c373b8d9226491
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleSignIn: fcee2257188d5eda57a5e2b6a715550ffff9206d
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
hermes-engine: 1f783c3d53940aed0d2c84586f0b7a85ab7827ef
IdensicMobileSDK: 00b13320e1b1e0574e68475bd0fbc7cd30fce26e
lottie-ios: 8f959969761e9c45d70353667d00af0e5b9cadb3
LottieFiles-dotLottie-iOS: e9b34e7cff6d04f5affd97336c2dab934b86e6fb
Mixpanel-swift: e9bef28a9648faff384d5ba6f48ecc2787eb24c0
nanopb: 438bc412db1928dac798aa6fd75726007be04262
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
NFCPassportReader: 48873f856f91215dbfa1eaaec20eae639672862e
OpenSSL-Universal: 84efb8a29841f2764ac5403e0c4119a28b713346
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
@@ -2639,7 +2625,7 @@ SPEC CHECKSUMS:
react-native-mobilesdk-module: 08c16fea2be97669f8e4c38153106e5fe698126a
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
react-native-nfc-manager: c8891e460b4943b695d63f7f4effc6345bbefc83
react-native-passkey: 84eaf6d62d3f9cbb8bc3c837dcb9ca794eec5140
react-native-passkey: 8818f842d1b80e45c06e906a5c85964719782bf5
react-native-safe-area-context: 5b5d3eb6ec9ef848f16c064a4eab4a92c7d7895e
react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed
react-native-webview: 05734d99f1e422c5ddfeefbd083d53abd78fccb1
@@ -2672,20 +2658,22 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 6e8d68583f39dc31ee65235110287277eb8556ef
ReactCodegen: 58a974a1a86362975fd49596480c5f0f17ee06a2
ReactCommon: e686c5766f0ebe5293be5a3957b833645cdac8ad
RNAppleAuthentication: a89c9804592b38ed4ab11f0aee68d05ba12ad432
RNCAsyncStorage: 6a8127b6987dc9fbce778669b252b14c8355c7ce
RNCClipboard: 9f7b908de4bf4353871fb454c15fc03db4917b88
RNDeviceInfo: 36d7f232bfe7c9b5c494cb7793230424ed32c388
RNFBApp: 4097f75673f8b42a7cd1ba17e6ea85a94b45e4d1
RNFBMessaging: 92325b0d5619ac90ef023a23cfd16fd3b91d0a88
RNFBRemoteConfig: a569bacaa410acfcaba769370e53a787f80fd13b
RNFBApp: 4105e54d9ca4a1c10893a032268470f670181110
RNFBMessaging: 6857871d9dff8f26b0c325fc7d97ba69cb77d213
RNFBRemoteConfig: 8d3675f18c052483ce294bb97b857428467fb41e
RNGestureHandler: 36aca36e4ef19f55dbf97239199d00fd58494e34
RNGoogleSignin: 60c3f470558dbff0ae54f2f164ef82a89d3eb561
RNInAppBrowser: 6d3eb68d471b9834335c664704719b8be1bfdb20
RNKeychain: 35beaa17938f7d8e4990d8a38fad5f8a748fc47c
RNLocalize: 67cd0eece3ba20fb5dae7625d77f02e88d3d9573
RNReactNativeHapticFeedback: eb5395b503c7a8f10de5e6722ef8afd3c61bc4f5
RNScreens: b0811b109e1a0b8b579f3348018e177bee374840
RNSentry: 98ab9f6a16c9596e36565ccf1a5871323f334766
RNSVG: f79679fe33eb77562fe7d6e3fbb0c8855829e549
RNSVG: d926926b169d8b81eb06aeb69734076e1dd566a3
segment-analytics-react-native: 0eae155b0e9fa560fa6b17d78941df64537c35b7
Sentry: 59993bffde4a1ac297ba6d268dc4bbce068d7c1b
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
@@ -2694,6 +2682,6 @@ SPEC CHECKSUMS:
SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb
Yoga: c34725819ab0a5962e85455b9e56679b306910ee
PODFILE CHECKSUM: 63b07a2aa49988e11d648524199e37c6a96de7aa
PODFILE CHECKSUM: a95943ec849e3235c1bfecf266b2a6c6ffa3d0d6
COCOAPODS: 1.16.2

View File

@@ -29,9 +29,11 @@
97E31F23A5A11A2C115FE2BB /* Pods_Self.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0823092D57FC544FD63682A /* Pods_Self.framework */; };
AE6147EC2DC95A8D00445C0F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = AE6147EB2DC95A8D00445C0F /* GoogleService-Info.plist */; };
B49D2B112E28AA7900946F64 /* IBMPlexMono-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */; };
BB000002000000000000001A /* MrzOcrCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB000002000000000000001B /* MrzOcrCorrection.swift */; };
BB000003000000000000001A /* MrzResultMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB000003000000000000001B /* MrzResultMapper.swift */; };
BF1044812DD53540009B3688 /* LiveMRZScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1044802DD53540009B3688 /* LiveMRZScannerView.swift */; };
BF1044832DD5354F009B3688 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1044822DD5354F009B3688 /* CameraView.swift */; };
BF1044852DD53570009B3688 /* MRZScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1044842DD53570009B3688 /* MRZScanner.swift */; };
BF1044852DD53570009B3688 /* MrzScanEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1044842DD53570009B3688 /* MrzScanEngine.swift */; };
BF5649262F43B1EB00DE07A1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5649252F43B1E700DE07A1 /* AppDelegate.swift */; };
BF6F0D552E38ED81008EA85C /* SelfAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6F0D542E38ED81008EA85C /* SelfAnalytics.swift */; };
BFBA0C772E339D2B00E82A52 /* NativeLoggerBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBA0C762E339D2B00E82A52 /* NativeLoggerBridge.swift */; };
@@ -74,9 +76,11 @@
A78F43717F170EC139960991 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Self/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
AE6147EB2DC95A8D00445C0F /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
B49D2B102E28AA7900946F64 /* IBMPlexMono-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "IBMPlexMono-Regular.otf"; path = "../src/assets/fonts/IBMPlexMono-Regular.otf"; sourceTree = SOURCE_ROOT; };
BB000002000000000000001B /* MrzOcrCorrection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MrzOcrCorrection.swift; sourceTree = "<group>"; };
BB000003000000000000001B /* MrzResultMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MrzResultMapper.swift; sourceTree = "<group>"; };
BF1044802DD53540009B3688 /* LiveMRZScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveMRZScannerView.swift; sourceTree = "<group>"; };
BF1044822DD5354F009B3688 /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; };
BF1044842DD53570009B3688 /* MRZScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MRZScanner.swift; sourceTree = "<group>"; };
BF1044842DD53570009B3688 /* MrzScanEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MrzScanEngine.swift; sourceTree = "<group>"; };
BF5649252F43B1E700DE07A1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
BF6F0D542E38ED81008EA85C /* SelfAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfAnalytics.swift; sourceTree = "<group>"; };
BFBA0C762E339D2B00E82A52 /* NativeLoggerBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeLoggerBridge.swift; sourceTree = "<group>"; };
@@ -110,7 +114,9 @@
BF6F0D542E38ED81008EA85C /* SelfAnalytics.swift */,
BFBA0C782E33A01F00E82A52 /* NativeLoggerBridge.m */,
BFBA0C762E339D2B00E82A52 /* NativeLoggerBridge.swift */,
BF1044842DD53570009B3688 /* MRZScanner.swift */,
BF1044842DD53570009B3688 /* MrzScanEngine.swift */,
BB000002000000000000001B /* MrzOcrCorrection.swift */,
BB000003000000000000001B /* MrzResultMapper.swift */,
BF1044822DD5354F009B3688 /* CameraView.swift */,
BF1044802DD53540009B3688 /* LiveMRZScannerView.swift */,
AE6147EB2DC95A8D00445C0F /* GoogleService-Info.plist */,
@@ -435,7 +441,9 @@
905B70072A72774000AFA232 /* PassportReader.m in Sources */,
BFBA0C792E33A01F00E82A52 /* NativeLoggerBridge.m in Sources */,
16E6646E2B8D292500FDD6A0 /* QKMRZScannerViewRepresentable.swift in Sources */,
BF1044852DD53570009B3688 /* MRZScanner.swift in Sources */,
BF1044852DD53570009B3688 /* MrzScanEngine.swift in Sources */,
BB000002000000000000001A /* MrzOcrCorrection.swift in Sources */,
BB000003000000000000001A /* MrzResultMapper.swift in Sources */,
165E76BF2B8DC53A0000FA90 /* MRZScannerModule.m in Sources */,
905B70052A72767900AFA232 /* PassportReader.swift in Sources */,
BF5649262F43B1EB00DE07A1 /* AppDelegate.swift in Sources */,

View File

@@ -30,7 +30,6 @@ const NativeModules = {
scanPassport: jest.fn(),
trackEvent: jest.fn(),
flush: jest.fn(),
reset: jest.fn(),
},
ReactNativeBiometrics: {
isSensorAvailable: jest.fn().mockResolvedValue({
@@ -716,8 +715,56 @@ jest.mock('@sentry/react-native', () => ({
}),
}));
jest.mock('@react-native-google-signin/google-signin', () => ({
GoogleSignin: {
configure: jest.fn(),
hasPlayServices: jest.fn().mockResolvedValue(true),
signIn: jest.fn().mockResolvedValue({
type: 'success',
data: {
user: {
id: 'mock-google-user-id',
name: 'Mock User',
email: 'mock@example.com',
},
},
}),
signOut: jest.fn().mockResolvedValue(null),
getCurrentUser: jest.fn().mockResolvedValue(null),
getTokens: jest.fn().mockResolvedValue({ idToken: 'mock-token' }),
},
GoogleSigninButton: 'GoogleSigninButton',
statusCodes: {
SIGN_IN_CANCELLED: 'SIGN_IN_CANCELLED',
IN_PROGRESS: 'IN_PROGRESS',
PLAY_SERVICES_NOT_AVAILABLE: 'PLAY_SERVICES_NOT_AVAILABLE',
},
}));
jest.mock('@invertase/react-native-apple-authentication', () => ({
__esModule: true,
default: {
performRequest: jest.fn().mockResolvedValue({
user: 'mock-apple-user-id',
fullName: { givenName: 'Mock', familyName: 'User' },
email: 'mock@example.com',
}),
getCredentialStateForUser: jest.fn().mockResolvedValue(1),
onCredentialRevoked: jest.fn(() => jest.fn()),
isSupported: true,
State: { AUTHORIZED: 1 },
Error: { CANCELED: 1001 },
},
AppleButton: 'AppleButton',
AppleRequestScope: { EMAIL: 0, FULL_NAME: 1 },
AppleRequestOperation: { LOGIN: 1 },
}));
jest.mock('@env', () => ({
ENABLE_DEBUG_LOGS: 'false',
GOOGLE_SIGNIN_ANDROID_CLIENT_ID: 'mock-google-client-id',
GOOGLE_SIGNIN_IOS_CLIENT_ID: 'mock-google-ios-client-id',
GOOGLE_SIGNIN_WEB_CLIENT_ID: 'mock-google-web-client-id',
MIXPANEL_NFC_PROJECT_TOKEN: 'test-token',
}));
@@ -912,8 +959,8 @@ jest.mock('react-native-nfc-manager', () => ({
// Mock react-native-passport-reader
jest.mock('react-native-passport-reader', () => {
const mockScanPassport = jest.fn();
// Mock the parameter count for scanPassport (iOS native method takes 9 parameters)
Object.defineProperty(mockScanPassport, 'length', { value: 9 });
// Mock the parameter count for scanPassport (iOS native method takes 10 parameters)
Object.defineProperty(mockScanPassport, 'length', { value: 10 });
const mockPassportReader = {
configure: jest.fn(),
@@ -939,15 +986,14 @@ jest.mock('react-native-passport-reader', () => {
// Mock @/integrations/nfc/passportReader to properly expose the interface expected by tests
jest.mock('./src/integrations/nfc/passportReader', () => {
const mockScanPassport = jest.fn();
// Mock the parameter count for scanPassport (iOS native method takes 9 parameters)
Object.defineProperty(mockScanPassport, 'length', { value: 9 });
// Mock the parameter count for scanPassport (iOS native method takes 10 parameters)
Object.defineProperty(mockScanPassport, 'length', { value: 10 });
const mockPassportReader = {
configure: jest.fn(),
scanPassport: mockScanPassport,
trackEvent: jest.fn(),
flush: jest.fn(),
reset: jest.fn(),
};
return {

View File

@@ -6,11 +6,11 @@
"scripts": {
"analyze:bundle:android": "yarn build:deps && node ./scripts/bundle-analyze-ci.cjs android",
"analyze:bundle:ios": "yarn build:deps && node ./scripts/bundle-analyze-ci.cjs ios",
"animations:convert": "node ./scripts/convert-to-dotlottie.mjs",
"analyze:tree-shaking": "node ./scripts/analyze-tree-shaking.cjs imports",
"analyze:tree-shaking:web": "yarn web:build && node ./scripts/analyze-tree-shaking.cjs web",
"android": "yarn build:deps && yarn setup:android-deps && react-native run-android",
"android:ci": "./scripts/mobile-ci-build-android.sh",
"animations:convert": "node ./scripts/convert-to-dotlottie.mjs",
"build:deps": "yarn workspaces foreach --from @selfxyz/mobile-app --topological --recursive run build",
"bump-version:major": "npm version major && yarn sync-versions",
"bump-version:minor": "npm version minor && yarn sync-versions",
@@ -88,20 +88,20 @@
"dependencies": {
"@babel/runtime": "^7.28.6",
"@ethersproject/shims": "^5.8.0",
"@invertase/react-native-apple-authentication": "^2.5.1",
"@lottiefiles/dotlottie-react": "^0.17.15",
"@lottiefiles/dotlottie-react-native": "0.5.0",
"@noble/hashes": "^1.5.0",
"@openpassport/zk-kit-imt": "^0.0.5",
"@openpassport/zk-kit-lean-imt": "^0.0.6",
"@openpassport/zk-kit-smt": "^0.0.1",
"@peculiar/x509": "^1.14.3",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-clipboard/clipboard": "1.16.3",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-firebase/app": "^19.0.1",
"@react-native-firebase/messaging": "^19.0.1",
"@react-native-firebase/remote-config": "^19.0.1",
"@react-native-firebase/app": "^21.14.0",
"@react-native-firebase/messaging": "^21.14.0",
"@react-native-firebase/remote-config": "^21.14.0",
"@react-native-google-signin/google-signin": "^16.1.1",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.2.0",
"@robinbobin/react-native-google-drive-api-wrapper": "^2.2.3",
@@ -133,11 +133,6 @@
"ethers": "^6.11.0",
"expo": "~52.0.40",
"expo-application": "~6.0.2",
"hash.js": "^1.1.7",
"js-sha1": "^0.7.0",
"js-sha256": "^0.11.1",
"js-sha512": "^0.9.0",
"lottie-react": "^2.4.1",
"node-forge": "^1.3.3",
"pkijs": "^3.3.3",
"poseidon-lite": "^0.2.0",
@@ -166,7 +161,7 @@
"react-native-safe-area-context": "^5.6.2",
"react-native-screens": "4.15.3",
"react-native-sqlite-storage": "^6.0.1",
"react-native-svg": "15.12.1",
"react-native-svg": "15.14.0",
"react-native-svg-web": "1.0.9",
"react-native-url-polyfill": "^3.0.0",
"react-native-web": "^0.21.2",
@@ -198,19 +193,16 @@
"@tamagui/vite-plugin": "1.126.14",
"@testing-library/react-native": "^13.3.3",
"@tsconfig/react-native": "^3.0.6",
"@types/bn.js": "^5.2.0",
"@types/dompurify": "^3.2.0",
"@types/elliptic": "^6.4.18",
"@types/jest": "^30.0.0",
"@types/node": "^22.18.3",
"@types/node-forge": "^1.3.14",
"@types/path-browserify": "^1",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"@types/react-native-dotenv": "^0.2.0",
"@types/react-native-sqlite-storage": "^6.0.5",
"@types/react-native-web": "^0",
"@types/react-test-renderer": "^18",
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"@vitejs/plugin-react-swc": "^4.2.2",
@@ -228,9 +220,8 @@
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sort-exports": "^0.9.1",
"hermes-eslint": "^0.19.1",
"hermes-eslint": "^0.33.3",
"jest": "^30.2.0",
"path-browserify": "^1.0.1",
"prettier": "^3.5.3",
"prop-types": "^15.8.1",
"react-native-svg-transformer": "^1.5.2",
@@ -238,7 +229,6 @@
"rollup-plugin-visualizer": "^6.0.5",
"stream-browserify": "^3.0.0",
"ts-morph": "^22.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-plugin-svgr": "^4.5.0"

View File

@@ -0,0 +1,6 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.64 9.20443C17.64 8.56625 17.5827 7.95262 17.4764 7.36353H9V10.8449H13.8436C13.635 11.9699 13.0009 12.9231 12.0477 13.5613V15.8194H14.9564C16.6582 14.2526 17.64 11.9453 17.64 9.20443Z" fill="#4285F4"/>
<path d="M8.99976 18C11.4298 18 13.467 17.1941 14.9561 15.8195L12.0475 13.5613C11.2416 14.1013 10.2107 14.4204 8.99976 14.4204C6.65567 14.4204 4.67158 12.8372 3.96385 10.71H0.957031V13.0418C2.43794 15.9831 5.48158 18 8.99976 18Z" fill="#34A853"/>
<path d="M3.96409 10.71C3.78409 10.17 3.68182 9.59318 3.68182 9C3.68182 8.40682 3.78409 7.83 3.96409 7.29V4.95818H0.957273C0.347727 6.17318 0 7.54773 0 9C0 10.4523 0.347727 11.8268 0.957273 13.0418L3.96409 10.71Z" fill="#FBBC05"/>
<path d="M8.99976 3.57955C10.3211 3.57955 11.5075 4.03364 12.4402 4.92545L15.0216 2.34409C13.4629 0.891818 11.4257 0 8.99976 0C5.48158 0 2.43794 2.01682 0.957031 4.95818L3.96385 7.29C4.67158 5.16273 6.65567 3.57955 8.99976 3.57955Z" fill="#EA4335"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -13,6 +13,7 @@ import DevHapticFeedbackScreen from '@/screens/dev/DevHapticFeedbackScreen';
import DevLoadingScreen from '@/screens/dev/DevLoadingScreen';
import DevPrivateKeyScreen from '@/screens/dev/DevPrivateKeyScreen';
import DevSettingsScreen from '@/screens/dev/DevSettingsScreen';
import SocialLoginDemoScreen from '@/screens/dev/SocialLoginDemoScreen';
const devHeaderOptions: NativeStackNavigationOptions = {
headerStyle: {
@@ -81,6 +82,13 @@ const devScreens = {
title: 'Dev Loading Screen',
} as NativeStackNavigationOptions,
},
SocialLoginDemo: {
screen: SocialLoginDemoScreen,
options: {
...devHeaderOptions,
title: 'Social Login Demo',
} as NativeStackNavigationOptions,
},
};
export default devScreens;

View File

@@ -71,6 +71,7 @@ export type AppRoutesParamList = {
export type DevRoutesParamList = {
CreateMock: undefined;
MockDataDeepLink: undefined;
SocialLoginDemo: undefined;
};
// =============================================================================

View File

@@ -0,0 +1,388 @@
// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import React, { useEffect, useRef, useState } from 'react';
import { Alert, Platform } from 'react-native';
import { Button, ScrollView, Text, XStack, YStack } from 'tamagui';
import { GOOGLE_SIGNIN_IOS_CLIENT_ID, GOOGLE_SIGNIN_WEB_CLIENT_ID } from '@env';
import appleAuth, {
AppleButton,
AppleRequestOperation,
AppleRequestScope,
} from '@invertase/react-native-apple-authentication';
import {
GoogleSignin,
statusCodes,
} from '@react-native-google-signin/google-signin';
import {
red500,
slate100,
slate200,
slate500,
slate600,
white,
} from '@selfxyz/mobile-sdk-alpha/constants/colors';
import { dinot } from '@selfxyz/mobile-sdk-alpha/constants/fonts';
import GoogleIcon from '@/assets/icons/google.svg';
type SocialUser = {
provider: 'google' | 'apple';
id?: string;
name?: string;
email?: string;
tokensRetrieved?: boolean;
};
const formatFullName = (fullName?: {
givenName?: string | null;
familyName?: string | null;
middleName?: string | null;
}) => {
if (!fullName) {
return undefined;
}
const nameParts = [
fullName.givenName,
fullName.middleName,
fullName.familyName,
].filter(Boolean);
return nameParts.length > 0 ? nameParts.join(' ') : undefined;
};
const SocialLoginDemoScreen: React.FC = () => {
const authInFlightRef = useRef(false);
const [user, setUser] = useState<SocialUser | null>(null);
const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const isNative = Platform.OS === 'ios' || Platform.OS === 'android';
const appleAvailable = Platform.OS === 'ios' && appleAuth.isSupported;
useEffect(() => {
if (!isNative) {
return;
}
GoogleSignin.configure({
webClientId: GOOGLE_SIGNIN_WEB_CLIENT_ID,
iosClientId: GOOGLE_SIGNIN_IOS_CLIENT_ID,
});
let isMounted = true;
const loadCurrentUser = async () => {
try {
const currentUser = await GoogleSignin.getCurrentUser();
if (currentUser?.user && isMounted) {
setUser({
provider: 'google',
id: currentUser.user.id,
name: currentUser.user.name ?? undefined,
email: currentUser.user.email ?? undefined,
});
}
} catch (error) {
const code = (error as { code?: string }).code;
console.warn('Silent Google sign-in failed', code ?? 'unknown');
}
};
loadCurrentUser().catch(() => {});
return () => {
isMounted = false;
};
}, [isNative]);
useEffect(() => {
if (!appleAvailable) {
return undefined;
}
return appleAuth.onCredentialRevoked(async () => {
setUser(null);
});
}, [appleAvailable]);
const handleError = (title: string, message: string) => {
setErrorMessage(message);
Alert.alert(title, message);
};
const handleGoogleSignIn = async () => {
if (loading || authInFlightRef.current) {
return;
}
authInFlightRef.current = true;
setLoading(true);
setErrorMessage(null);
try {
if (Platform.OS === 'android') {
await GoogleSignin.hasPlayServices();
}
const response = await GoogleSignin.signIn();
if (response.type !== 'success') {
return;
}
const tokens = await GoogleSignin.getTokens();
setUser({
provider: 'google',
id: response.data.user.id,
name: response.data.user.name ?? undefined,
email: response.data.user.email ?? undefined,
tokensRetrieved: Boolean(tokens.accessToken),
});
} catch (error: unknown) {
const code = (error as { code?: string }).code;
if (code === statusCodes.SIGN_IN_CANCELLED) {
return;
}
if (code === statusCodes.IN_PROGRESS) {
handleError('Google Sign-In', 'Sign-in already in progress.');
return;
}
if (code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
handleError('Google Sign-In', 'Google Play Services not available.');
return;
}
handleError(
'Google Sign-In',
'Unable to sign in with Google. Please try again.',
);
} finally {
authInFlightRef.current = false;
setLoading(false);
}
};
const handleAppleSignIn = async () => {
if (loading || authInFlightRef.current) {
return;
}
if (!appleAvailable) {
handleError('Apple Sign-In', 'Apple Sign-In is not supported here.');
return;
}
authInFlightRef.current = true;
setLoading(true);
setErrorMessage(null);
try {
const appleAuthRequestResponse = await appleAuth.performRequest({
requestedOperation: AppleRequestOperation.LOGIN,
requestedScopes: [AppleRequestScope.EMAIL, AppleRequestScope.FULL_NAME],
});
const credentialState = await appleAuth.getCredentialStateForUser(
appleAuthRequestResponse.user,
);
if (credentialState !== appleAuth.State.AUTHORIZED) {
handleError(
'Apple Sign-In',
'Apple credential state is no longer valid.',
);
return;
}
// Apple identity token retrieved successfully - available for backend integration
setUser({
provider: 'apple',
id: appleAuthRequestResponse.user,
name: formatFullName(appleAuthRequestResponse.fullName ?? undefined),
email: appleAuthRequestResponse.email ?? undefined,
});
} catch (error: unknown) {
const code = (error as { code?: string }).code;
if (code === appleAuth.Error.CANCELED) {
return;
}
handleError(
'Apple Sign-In',
'Unable to sign in with Apple. Please try again.',
);
} finally {
authInFlightRef.current = false;
setLoading(false);
}
};
const handleSignOut = async () => {
if (loading || authInFlightRef.current) {
return;
}
setLoading(true);
setErrorMessage(null);
try {
if (user?.provider === 'google') {
await GoogleSignin.signOut();
}
setUser(null);
} catch {
handleError('Sign Out', 'Unable to sign out. Please try again.');
} finally {
setLoading(false);
}
};
return (
<ScrollView
backgroundColor={slate100}
contentContainerStyle={{ padding: 16, gap: 16 }}
>
<YStack gap="$4">
<YStack gap="$2">
<Text fontSize="$7" fontFamily={dinot} color={slate600}>
Social Login Demo
</Text>
<Text fontSize="$4" color={slate500}>
Use these buttons to test Google and Apple sign-in flows. Tokens can
be retrieved for backend integration.
</Text>
</YStack>
<YStack gap="$3">
<Button
style={{ backgroundColor: white, height: 44 }}
borderColor={slate200}
borderWidth={1}
borderRadius="$2"
padding={0}
onPress={handleGoogleSignIn}
disabled={loading}
pressStyle={{ opacity: 0.8 }}
>
<XStack
width="100%"
justifyContent="center"
alignItems="center"
paddingHorizontal="$4"
height="100%"
gap="$3"
>
{!loading && <GoogleIcon width={18} height={18} />}
<Text
fontSize="$5"
color={slate600}
fontFamily={dinot}
fontWeight="600"
>
{loading ? 'Signing in...' : 'Sign in with Google'}
</Text>
</XStack>
</Button>
{appleAvailable ? (
<YStack>
<AppleButton
buttonStyle={AppleButton.Style.BLACK}
buttonType={AppleButton.Type.SIGN_IN}
style={{ width: '100%', height: 44 }}
onPress={handleAppleSignIn}
/>
</YStack>
) : (
<Text fontSize="$4" color={slate500}>
Apple Sign-In is only available on iOS devices.
</Text>
)}
{user && (
<Button
style={{ backgroundColor: red500, height: 44 }}
borderColor={red500}
borderRadius="$2"
padding={0}
onPress={handleSignOut}
disabled={loading}
pressStyle={{ opacity: 0.8 }}
>
<XStack
width="100%"
justifyContent="center"
alignItems="center"
height="100%"
>
<Text
fontSize="$5"
color={white}
fontFamily={dinot}
fontWeight="600"
>
{loading && user ? 'Logging out...' : 'Log Out'}
</Text>
</XStack>
</Button>
)}
</YStack>
<YStack gap="$3">
<Text fontSize="$5" fontFamily={dinot} color={slate600}>
Status
</Text>
{loading && (
<Text fontSize="$4" color={slate500}>
Signing in...
</Text>
)}
{errorMessage && (
<Text fontSize="$4" color={red500}>
{errorMessage}
</Text>
)}
{user ? (
<YStack
backgroundColor={white}
borderColor={slate200}
borderRadius="$3"
borderWidth={1}
padding="$4"
gap="$2"
>
<Text fontSize="$4" color={slate600} fontFamily={dinot}>
Provider: {user.provider}
</Text>
<Text fontSize="$4" color={slate600}>
Name: {user.name ?? 'Not provided'}
</Text>
<Text fontSize="$4" color={slate600}>
Email: {user.email ?? 'Not provided'}
</Text>
<Text fontSize="$4" color={slate600}>
ID: {user.id ?? 'Not provided'}
</Text>
{user.tokensRetrieved && (
<Text fontSize="$4" color={slate600}>
Tokens: Retrieved
</Text>
)}
</YStack>
) : (
<Text fontSize="$4" color={slate500}>
No user signed in yet.
</Text>
)}
</YStack>
</YStack>
</ScrollView>
);
};
export default SocialLoginDemoScreen;

View File

@@ -53,6 +53,29 @@ export const DebugShortcutsSection: React.FC<DebugShortcutsSectionProps> = ({
<ChevronRight color={slate500} strokeWidth={2.5} />
</XStack>
</Button>
<Button
style={{ backgroundColor: 'white' }}
borderColor={slate200}
borderRadius="$2"
height="$5"
padding={0}
onPress={() => {
navigation.navigate('SocialLoginDemo');
}}
>
<XStack
width="100%"
justifyContent="space-between"
paddingVertical="$3"
paddingLeft="$4"
paddingRight="$1.5"
>
<Text fontSize="$5" color={slate500} fontFamily={dinot}>
Social Login Demo
</Text>
<ChevronRight color={slate500} strokeWidth={2.5} />
</XStack>
</Button>
{IS_DEV_MODE && (
<Button
style={{ backgroundColor: 'white' }}

View File

@@ -12,6 +12,8 @@ export const ENABLE_DEBUG_LOGS = false;
export const GOOGLE_SIGNIN_ANDROID_CLIENT_ID = 'mock-google-client-id';
export const GOOGLE_SIGNIN_IOS_CLIENT_ID = 'mock-google-ios-client-id';
export const GOOGLE_SIGNIN_WEB_CLIENT_ID = 'mock-google-web-client-id';
export const IS_TEST_BUILD = false;

View File

@@ -7,6 +7,7 @@
// This pattern avoids hoisting issues with jest.mock
import { Buffer } from 'buffer';
import { logNFCEvent } from '@/config/sentry';
import { parseScanResponse, scan } from '@/integrations/nfc/nfcScanner';
import { PassportReader } from '@/integrations/nfc/passportReader';
@@ -34,6 +35,10 @@ jest.mock('react-native', () => ({
},
}));
jest.mock('@/config/sentry', () => ({
logNFCEvent: jest.fn(),
}));
// Ensure the Node Buffer implementation is available to the module under test
global.Buffer = Buffer;
@@ -378,4 +383,117 @@ describe('scan', () => {
expect(mockScanPassport).toHaveBeenCalled();
});
});
describe('Error propagation and availability', () => {
it('should propagate scan errors and log scan_failed event', async () => {
const scanError = new Error('native scan failed');
const mockScanPassport = jest.fn().mockRejectedValue(scanError);
Object.defineProperty(PassportReader, 'scanPassport', {
writable: true,
configurable: true,
value: mockScanPassport,
});
await expect(scan(mockInputs)).rejects.toThrow('native scan failed');
expect(logNFCEvent).toHaveBeenCalledWith(
'error',
'scan_failed',
expect.objectContaining({
stage: 'scan',
sessionId: 'test-session',
platform: 'ios',
scanType: 'mrz',
}),
expect.objectContaining({ error: 'native scan failed' }),
);
});
it('should reject with unavailable error when ios module is unavailable', async () => {
// Module unavailability must be tested via resetModules because the
// scan/scanDocument binding is captured at module init time.
jest.resetModules();
global.mockPlatformOS = 'ios';
jest.doMock('@/integrations/nfc/passportReader', () => ({
PassportReader: null,
scan: null,
reset: jest.fn(),
}));
const { scan: isolatedScan } = require('@/integrations/nfc/nfcScanner');
await expect(isolatedScan(mockInputs)).rejects.toMatchObject({
message:
'NFC scanning is currently unavailable. Please ensure the app is properly installed.',
});
});
it('should reject with unavailable error when android module is unavailable', async () => {
// Module unavailability must be tested via resetModules because the
// scan/scanDocument binding is captured at module init time.
jest.resetModules();
global.mockPlatformOS = 'android';
jest.doMock('@/integrations/nfc/passportReader', () => ({
PassportReader: {},
scan: null,
reset: jest.fn(),
}));
const { scan: isolatedScan } = require('@/integrations/nfc/nfcScanner');
await expect(isolatedScan(mockInputs)).rejects.toMatchObject({
message: 'NFC scanning is currently unavailable.',
});
});
});
describe('Platform dispatch', () => {
it('should dispatch to iOS PassportReader.scanPassport on iOS', async () => {
jest.resetModules();
global.mockPlatformOS = 'ios';
const mockScanPassport = jest.fn().mockResolvedValue({ ok: true });
const mockCrossPlatformScan = jest.fn().mockResolvedValue({ ok: true });
jest.doMock('@/integrations/nfc/passportReader', () => ({
PassportReader: { scanPassport: mockScanPassport },
scan: mockCrossPlatformScan,
reset: jest.fn(),
}));
const { scan: isolatedScan } = require('@/integrations/nfc/nfcScanner');
await isolatedScan(mockInputs);
expect(mockScanPassport).toHaveBeenCalledTimes(1);
expect(mockCrossPlatformScan).not.toHaveBeenCalled();
});
it('should dispatch to Android scan() on android', async () => {
jest.resetModules();
global.mockPlatformOS = 'android';
const mockAndroidScan = jest.fn().mockResolvedValue({ ok: true });
const mockReset = jest.fn();
jest.doMock('@/integrations/nfc/passportReader', () => ({
PassportReader: {},
scan: mockAndroidScan,
reset: mockReset,
}));
const { scan: isolatedScan } = require('@/integrations/nfc/nfcScanner');
await isolatedScan(mockInputs);
expect(mockReset).toHaveBeenCalledTimes(1);
expect(mockAndroidScan).toHaveBeenCalledWith(
expect.objectContaining({
documentNumber: 'L898902C3',
dateOfBirth: '640812',
dateOfExpiry: '251031',
}),
);
});
});
});

View File

@@ -7,7 +7,7 @@
* These tests verify critical interface requirements without conditional expects
*/
import { PassportReader } from '@/integrations/nfc/passportReader';
import { PassportReader, reset } from '@/integrations/nfc/passportReader';
describe('PassportReader Simple Contract Tests', () => {
describe('Critical Interface Requirements', () => {
@@ -22,15 +22,14 @@ describe('PassportReader Simple Contract Tests', () => {
expect((PassportReader as any).scan).toBeUndefined();
});
it('should have reset method', () => {
// This should always exist
expect(PassportReader.reset).toBeDefined();
expect(typeof PassportReader.reset).toBe('function');
it('should export reset as a standalone function', () => {
expect(reset).toBeDefined();
expect(typeof reset).toBe('function');
});
it('should have scanPassport with correct parameter count', () => {
// scanPassport should take exactly 9 parameters
expect(PassportReader.scanPassport.length).toBe(9);
// scanPassport should take exactly 10 parameters including sessionId
expect(PassportReader.scanPassport.length).toBe(10);
});
it('should allow configure to be optional', () => {
@@ -92,14 +91,14 @@ describe('PassportReader Simple Contract Tests', () => {
false,
false,
false,
'session-id',
);
}).not.toThrow(TypeError);
});
it('should not crash when calling reset', () => {
// Should be callable
it('should not crash when calling reset export', () => {
expect(() => {
PassportReader.reset();
reset();
}).not.toThrow(TypeError);
});
});
@@ -113,7 +112,7 @@ describe('PassportReader Simple Contract Tests', () => {
it('should have proper method types', () => {
// All defined methods should be functions
expect(typeof PassportReader.reset).toBe('function');
expect(typeof reset).toBe('function');
expect(typeof PassportReader.scanPassport).toBe('function');
// Optional methods should be function or undefined

View File

@@ -111,6 +111,7 @@ describe('navigation', () => {
'SaveRecoveryPhrase',
'Settings',
'ShowRecoveryPhrase',
'SocialLoginDemo',
'Splash',
'StarfallPushCode',
'WebView',

View File

@@ -4,6 +4,7 @@
"private": true,
"license": "MIT",
"author": "self team",
"type": "module",
"scripts": {
"build:deps": "yarn workspaces foreach --from @selfxyz/circuits --topological-dev --recursive run build",
"build-all": "bash scripts/build/build_register_circuits.sh && bash scripts/build/build_register_circuits_id.sh && bash scripts/build/build_register_aadhaar.sh && bash scripts/build/build_dsc_circuits.sh && bash scripts/build/build_disclose_circuits.sh",
@@ -47,7 +48,7 @@
"@openpassport/zk-kit-imt": "^0.0.4",
"@openpassport/zk-kit-lean-imt": "^0.0.4",
"@openpassport/zk-kit-smt": "^0.0.1",
"@selfxyz/common": "workspace:^",
"@selfxyz/new-common": "workspace:^",
"@zk-email/circuits": "^6.3.2",
"@zk-email/helpers": "^6.1.1",
"@zk-email/jwt-tx-builder-circuits": "0.1.0",

View File

@@ -9,18 +9,18 @@ import { poseidon1, poseidon2 } from 'poseidon-lite';
import nameAndDobjson from '../consts/ofac/nameAndDobSMT.json' with { type: 'json' };
import nameAndYobjson from '../consts/ofac/nameAndYobSMT.json' with { type: 'json' };
import passportNojson from '../consts/ofac/passportNoAndNationalitySMT.json' with { type: 'json' };
import { attributeToPosition, PASSPORT_ATTESTATION_ID } from '@selfxyz/common/constants/constants';
import { attributeToPosition } from '@selfxyz/new-common/src/foundation/constants/index.js';
import {
formatAndUnpackForbiddenCountriesList,
formatAndUnpackReveal,
getAttributeFromUnpackedReveal,
} from '@selfxyz/common/utils/circuits/formatOutputs';
import { generateCircuitInputsVCandDisclose } from '@selfxyz/common/utils/circuits/generateInputs';
import { genAndInitMockPassportData } from '@selfxyz/common/utils/passports/genMockPassportData';
import { generateCommitment } from '@selfxyz/common/utils/passports/passport';
import { hashEndpointWithScope } from '@selfxyz/common/utils/scope';
} from '@selfxyz/new-common/src/circuits/outputs/format.js';
import { PassportDocument } from '@selfxyz/new-common/src/documents/passport/adapter.js';
import { createCircuitInputGenerator } from '@selfxyz/new-common/src/circuits/generator.js';
import { genAndInitMockPassportData } from '@selfxyz/new-common/src/testing/genMockPassportData.js';
import { hashEndpointWithScope } from '@selfxyz/new-common/src/crypto/scope.js';
import { fileURLToPath } from 'url';
import { castFromUUID } from '@selfxyz/common/utils/circuits/uuid';
import { castFromUUID } from '@selfxyz/new-common/src/circuits/userId.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -37,20 +37,17 @@ describe('Disclose', function () {
'000101',
'300101'
);
const doc = new PassportDocument(passportData);
const forbidden_countries_list = ['ALG', 'DZA'];
const secret = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
const majority = '18';
const user_identifier = castFromUUID(crypto.randomUUID());
const selector_dg1 = Array(88).fill('1');
const selector_older_than = '1';
const endpoint = 'https://example.com';
const scope = 'scope';
const fullScope = hashEndpointWithScope(endpoint, scope);
const attestation_id = PASSPORT_ATTESTATION_ID;
// compute the commitment and insert it in the tree
const commitment = generateCommitment(secret, attestation_id, passportData);
const commitment = doc.generateCommitment(secret);
console.log('commitment in js ', commitment);
const tree: any = new LeanIMT((a, b) => poseidon2([a, b]), []);
tree.insert(BigInt(commitment));
@@ -64,7 +61,7 @@ describe('Disclose', function () {
const nameAndYob_smt = new SMT(poseidon2, true);
nameAndYob_smt.import(nameAndYobjson);
const selector_ofac = 1;
const generator = createCircuitInputGenerator();
before(async () => {
circuit = await wasm_tester(
@@ -78,22 +75,27 @@ describe('Disclose', function () {
}
);
inputs = generateCircuitInputsVCandDisclose(
secret,
PASSPORT_ATTESTATION_ID,
passportData,
fullScope,
selector_dg1,
selector_older_than,
tree,
inputs = generator.generateDiscloseInputs(doc, secret, {
scope: fullScope,
fieldsToReveal: [
'issuing_state',
'name',
'id_number',
'nationality',
'date_of_birth',
'gender',
'expiry_date',
'older_than',
'ofac',
],
merkletree: tree,
majority,
passportNo_smt,
nameAndDob_smt,
nameAndYob_smt,
selector_ofac,
forbidden_countries_list,
user_identifier
);
user_identifier,
});
});
it('should compile and load the circuit', async function () {
@@ -356,30 +358,21 @@ describe('Disclose', function () {
for (const testCase of testCases) {
console.log(`Testing: ${testCase.desc}`);
const passportData = testCase.data;
const sanctionedCommitment = generateCommitment(
secret,
PASSPORT_ATTESTATION_ID,
passportData
);
const testDoc = new PassportDocument(testCase.data);
const sanctionedCommitment = testDoc.generateCommitment(secret);
tree.insert(BigInt(sanctionedCommitment));
const testInputs = generateCircuitInputsVCandDisclose(
secret,
PASSPORT_ATTESTATION_ID,
passportData,
fullScope,
Array(88).fill('0'), // selector_dg1
selector_older_than,
tree,
const testInputs = generator.generateDiscloseInputs(testDoc, secret, {
scope: fullScope,
fieldsToReveal: ['ofac'],
merkletree: tree,
majority,
passportNo_smt,
nameAndDob_smt,
nameAndYob_smt,
'1', // selector_ofac
forbidden_countries_list,
user_identifier
);
user_identifier,
});
w = await circuit.calculateWitness(testInputs);
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[3]']);

View File

@@ -1,18 +1,34 @@
import { expect } from 'chai';
import { wasm as wasmTester } from 'circom_tester';
import path from 'path';
import assert from 'assert';
import { formatInput } from '@selfxyz/common/utils/circuits/generateInputs';
import { unpackReveal } from '@selfxyz/common/utils/circuits/formatOutputs';
import { createSelector, extractField } from '@selfxyz/common/utils/aadhaar/constants';
import { prepareAadhaarDiscloseTestData } from '@selfxyz/common';
import { SMT } from '@openpassport/zk-kit-smt';
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
import { poseidon2 } from 'poseidon-lite';
import fs from 'fs';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(__filename);
import { genMockIdDoc } from '@selfxyz/new-common/src/testing/genMockIdDoc.js';
import {
generateTestData,
testCustomData,
} from '@selfxyz/new-common/src/testing/genMockAadhaarData.js';
import {
AADHAAR_MOCK_PRIVATE_KEY_PEM,
AADHAAR_MOCK_PUBLIC_KEY_PEM,
} from '@selfxyz/new-common/src/testing/mockAadhaarCert.js';
import {
processQRData,
extractSignatureBytes,
} from '@selfxyz/new-common/src/documents/aadhaar/qr.js';
import { AadhaarDocument } from '@selfxyz/new-common/src/documents/aadhaar/adapter.js';
import { createCircuitInputGenerator } from '@selfxyz/new-common/src/circuits/generator.js';
import { extractField } from '@selfxyz/new-common/src/documents/aadhaar/constants.js';
import { unpackReveal } from '@selfxyz/new-common/src/circuits/outputs/format.js';
import type { AadhaarData } from '@selfxyz/new-common/src/foundation/types/document.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const generator = createCircuitInputGenerator();
const nameAndDobAadhaarjson = JSON.parse(
fs.readFileSync(path.join(__dirname, '../consts/ofac/nameAndDobAadhaarSMT.json'), 'utf8')
@@ -21,43 +37,59 @@ const nameAndYobAadhaarjson = JSON.parse(
fs.readFileSync(path.join(__dirname, '../consts/ofac/nameAndYobAadhaarSMT.json'), 'utf8')
);
// const privateKeyPath = path.join(__dirname, '../../node_modules/anon-aadhaar-circuits/assets/testPrivateKey.pem');
// Dynamically resolve the anon-aadhaar-circuits package location
function resolvePackagePath(packageName: string, subpath: string): string {
try {
// Try to resolve the package's package.json
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
paths: [__dirname],
});
const packageDir = path.dirname(packageJsonPath);
return path.join(packageDir, subpath);
} catch (error) {
// Fallback to traditional node_modules search
const modulePath = path.join(__dirname, '../../node_modules', packageName, subpath);
if (fs.existsSync(modulePath)) {
return modulePath;
}
throw new Error(`Could not resolve ${packageName}/${subpath}`);
}
}
const privateKeyPem = fs.readFileSync(
resolvePackagePath('anon-aadhaar-circuits', 'assets/testPrivateKey.pem'),
'utf8'
);
// Create SMTs at module level
const nameAndDob_smt = new SMT(poseidon2, true);
nameAndDob_smt.import(nameAndDobAadhaarjson as any);
const nameAndYob_smt = new SMT(poseidon2, true);
nameAndYob_smt.import(nameAndYobAadhaarjson as any);
// Create Merkle tree at module level
const tree: any = new LeanIMT((a, b) => poseidon2([a, b]), []);
// Helper function to get packed reveal data from circuit output
function createAadhaarDoc(opts?: {
name?: string;
dateOfBirth?: string;
gender?: string;
pincode?: string;
state?: string;
timestamp?: string;
}): AadhaarDocument {
if (
opts?.name ||
opts?.dateOfBirth ||
opts?.gender ||
opts?.pincode ||
opts?.state ||
opts?.timestamp
) {
const generated = generateTestData({
privKeyPem: AADHAAR_MOCK_PRIVATE_KEY_PEM,
data: testCustomData,
name: opts?.name,
dob: opts?.dateOfBirth,
gender: opts?.gender,
pincode: opts?.pincode,
state: opts?.state,
timestamp: opts?.timestamp,
});
const processed = processQRData(generated.testQRData);
const signatureBytes = extractSignatureBytes(processed.decodedData);
const data: AadhaarData = {
documentType: 'mock_aadhaar',
documentCategory: 'aadhaar',
mock: true,
qrData: generated.testQRData,
extractedFields: processed.extractedFields,
signature: Array.from(signatureBytes),
publicKey: AADHAAR_MOCK_PUBLIC_KEY_PEM,
photoHash: processed.photoHash.toString(),
};
return new AadhaarDocument(data);
}
const data = genMockIdDoc({ idType: 'mock_aadhaar' });
return new AadhaarDocument(data);
}
function getPackedRevealData(revealedData: any): string[] {
return [
revealedData['revealData_packed[0]'],
@@ -88,50 +120,34 @@ describe(' VC and Disclose Aadhaar Circuit Tests', function () {
it('should calculate witness and pass constrain check', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarDiscloseTestData(
privateKeyPem,
tree,
const doc = createAadhaarDoc();
const inputs = generator.generateDiscloseInputs(doc, '1234', {
merkletree: tree,
nameAndDob_smt,
nameAndYob_smt,
'333',
'1234',
'585225',
'0',
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
true
);
scope: '333',
fieldsToReveal: [],
user_identifier: '0',
minimumAge: 0,
updateTree: true,
});
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
});
it('should reveal gender only', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarDiscloseTestData(
privateKeyPem,
tree,
const doc = createAadhaarDoc();
const inputs = generator.generateDiscloseInputs(doc, '1234', {
merkletree: tree,
nameAndDob_smt,
nameAndYob_smt,
'333',
'1234',
'585225',
'0',
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
true
);
// Use createSelector to generate selector for revealing only gender
const selector = createSelector(['GENDER']);
inputs.selector = formatInput(selector)[0];
scope: '333',
fieldsToReveal: ['gender'],
user_identifier: '0',
minimumAge: 0,
updateTree: true,
});
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
@@ -139,44 +155,28 @@ describe(' VC and Disclose Aadhaar Circuit Tests', function () {
const revealedData = await circuit.getOutput(w, [`revealData_packed[4]`]);
const revealedData_packed = getPackedRevealData(revealedData);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
// Use extractField to get field values
const gender = extractField(revealedDataUnpacked, 'GENDER');
const minimumAge = extractField(revealedDataUnpacked, 'MINIMUM_AGE_VALID');
assert(gender === 'M', 'Gender should be Male');
assert(minimumAge.toString() === inputs.minimumAge[0], 'Minimum Age should be 0');
assert(minimumAge.toString() === (inputs as any).minimumAge[0], 'Minimum Age should be 0');
});
it('should reveal yob, mob, dob, reveal_ofac_name_yob only', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarDiscloseTestData(
privateKeyPem,
tree,
const doc = createAadhaarDoc();
const inputs = generator.generateDiscloseInputs(doc, '1234', {
merkletree: tree,
nameAndDob_smt,
nameAndYob_smt,
'333',
'1234',
'585225',
'0',
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
true
);
// Use createSelector to generate selector for revealing birth date and OFAC check
const selector = createSelector([
'YEAR_OF_BIRTH',
'MONTH_OF_BIRTH',
'DAY_OF_BIRTH',
'OFAC_NAME_YOB_CHECK',
]);
inputs.selector = formatInput(selector)[0];
scope: '333',
fieldsToReveal: ['date_of_birth', 'ofac'],
user_identifier: '0',
minimumAge: 0,
updateTree: true,
});
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
@@ -184,54 +184,43 @@ describe(' VC and Disclose Aadhaar Circuit Tests', function () {
const revealedData = await circuit.getOutput(w, [`revealData_packed[4]`, 'reveal_photoHash']);
const revealedData_packed = getPackedRevealData(revealedData);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
// Use extractField to get field values
const yearOfBirth = extractField(revealedDataUnpacked, 'YEAR_OF_BIRTH');
const monthOfBirth = extractField(revealedDataUnpacked, 'MONTH_OF_BIRTH');
const dayOfBirth = extractField(revealedDataUnpacked, 'DAY_OF_BIRTH');
const ofacNameYobCheck = extractField(revealedDataUnpacked, 'OFAC_NAME_YOB_CHECK');
const minimumAge = extractField(revealedDataUnpacked, 'MINIMUM_AGE_VALID');
// Verify extracted values
assert(yearOfBirth === '1984', 'YOB should be 1984');
assert(monthOfBirth === '01', 'MOB should be 01');
assert(dayOfBirth === '01', 'DOB should be 01');
assert(ofacNameYobCheck === 1, 'OFAC Name YOB should be 1 (not in OFAC list)');
// Verify non-revealed fields are null
for (let i = 9; i < 116; i++) {
assert(revealedDataUnpacked[i] === '\0', `Output ${i} should be null character`);
}
assert(revealedData.reveal_photoHash === '0', 'Photo Hash should be 0');
assert(minimumAge.toString() === inputs.minimumAge[0], 'Minimum Age should be 0');
assert(minimumAge.toString() === (inputs as any).minimumAge[0], 'Minimum Age should be 0');
});
it('ofac_check_result should be 0 if exists in ofac_name_dob_smt and ofac_name_yob_smt', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarDiscloseTestData(
privateKeyPem,
tree,
const doc = createAadhaarDoc({
name: 'Abu ABBAS',
dateOfBirth: '10-12-1948',
});
const inputs = generator.generateDiscloseInputs(doc, '1234', {
merkletree: tree,
nameAndDob_smt,
nameAndYob_smt,
'333',
'1234',
'585225',
'0',
'Abu ABBAS',
'10-12-1948',
undefined,
undefined,
undefined,
undefined,
true
);
// Use createSelector to generate selector for revealing OFAC checks
const selector = createSelector(['OFAC_NAME_DOB_CHECK', 'OFAC_NAME_YOB_CHECK']);
inputs.selector = formatInput(selector)[0];
inputs.minimumAge = ['100'];
scope: '333',
fieldsToReveal: ['ofac'],
user_identifier: '0',
minimumAge: 100,
updateTree: true,
});
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
@@ -239,47 +228,37 @@ describe(' VC and Disclose Aadhaar Circuit Tests', function () {
const revealedData = await circuit.getOutput(w, [`revealData_packed[4]`]);
const revealedData_packed = getPackedRevealData(revealedData);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
// Use extractField to get field values
const ofacNameDobCheck = extractField(revealedDataUnpacked, 'OFAC_NAME_DOB_CHECK');
const ofacNameYobCheck = extractField(revealedDataUnpacked, 'OFAC_NAME_YOB_CHECK');
const minimumAge = extractField(revealedDataUnpacked, 'MINIMUM_AGE_VALID');
// Verify non-revealed fields are null
for (let i = 0; i < 115; i++) {
assert(revealedDataUnpacked[i] === '\0', `Output ${i} should be null character`);
}
// Verify OFAC checks show person is in OFAC list
assert(ofacNameYobCheck === 0, 'OFAC Name YOB should be 0 (in OFAC list)');
assert(ofacNameDobCheck === 0, 'OFAC Name DOB should be 0 (in OFAC list)');
assert(minimumAge.toString() === '0', 'Minimum Age should be 0');
});
it('ofac_check_result should be 0 if exists in ofac_name_dob_reverse_smt and ofac_name_yob_reverse_smt', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarDiscloseTestData(
privateKeyPem,
tree,
const doc = createAadhaarDoc({
name: 'ABBAS ABU',
dateOfBirth: '10-12-1948',
});
const inputs = generator.generateDiscloseInputs(doc, '1234', {
merkletree: tree,
nameAndDob_smt,
nameAndYob_smt,
'333',
'1234',
'585225',
'0',
'ABBAS ABU',
'10-12-1948',
undefined,
undefined,
undefined,
undefined,
true
);
// Use createSelector to generate selector for revealing OFAC checks
const selector = createSelector(['OFAC_NAME_DOB_CHECK', 'OFAC_NAME_YOB_CHECK']);
inputs.selector = formatInput(selector)[0];
inputs.minimumAge = ['100'];
scope: '333',
fieldsToReveal: ['ofac'],
user_identifier: '0',
minimumAge: 100,
updateTree: true,
});
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
@@ -287,19 +266,16 @@ describe(' VC and Disclose Aadhaar Circuit Tests', function () {
const revealedData = await circuit.getOutput(w, [`revealData_packed[4]`]);
const revealedData_packed = getPackedRevealData(revealedData);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
// Use extractField to get field values
const ofacNameDobCheck = extractField(revealedDataUnpacked, 'OFAC_NAME_DOB_CHECK');
const ofacNameYobCheck = extractField(revealedDataUnpacked, 'OFAC_NAME_YOB_CHECK');
const minimumAge = extractField(revealedDataUnpacked, 'MINIMUM_AGE_VALID');
// Verify non-revealed fields are null
for (let i = 0; i < 115; i++) {
assert(revealedDataUnpacked[i] === '\0', `Output ${i} should be null character`);
}
// Verify OFAC checks show person is in OFAC list
assert(ofacNameYobCheck === 0, 'OFAC Name YOB should be 0 (in OFAC list)');
assert(ofacNameDobCheck === 0, 'OFAC Name DOB should be 0 (in OFAC list)');
assert(minimumAge.toString() === '0', 'Minimum Age should be 0');

View File

@@ -2,13 +2,10 @@ import { describe } from 'mocha';
import { assert, expect } from 'chai';
import path from 'path';
import { wasm as wasm_tester } from 'circom_tester';
import {
attributeToPosition_ID,
ID_CARD_ATTESTATION_ID,
} from '@selfxyz/common/constants/constants';
import { attributeToPosition_ID } from '@selfxyz/new-common/src/foundation/constants/index.js';
import { poseidon2 } from 'poseidon-lite';
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
import { generateCircuitInputsVCandDisclose } from '@selfxyz/common/utils/circuits/generateInputs';
import { createCircuitInputGenerator } from '@selfxyz/new-common/src/circuits/generator.js';
import crypto from 'crypto';
import { SMT } from '@openpassport/zk-kit-smt';
import nameAndDobjson from '../consts/ofac/nameAndDobSMT_ID.json' with { type: 'json' };
@@ -17,12 +14,12 @@ import {
formatAndUnpackForbiddenCountriesList,
formatAndUnpackReveal,
getAttributeFromUnpackedReveal,
} from '@selfxyz/common/utils/circuits/formatOutputs';
import { generateCommitment } from '@selfxyz/common/utils/passports/passport';
import { hashEndpointWithScope } from '@selfxyz/common/utils/scope';
import { genMockIdDocAndInitDataParsing } from '@selfxyz/common/utils/passports/genMockIdDoc';
} from '@selfxyz/new-common/src/circuits/outputs/format.js';
import { PassportDocument } from '@selfxyz/new-common/src/documents/passport/adapter.js';
import { hashEndpointWithScope } from '@selfxyz/new-common/src/crypto/scope.js';
import { genMockIdDocAndInitDataParsing } from '@selfxyz/new-common/src/testing/genMockIdDoc.js';
import { fileURLToPath } from 'url';
import { castFromUUID } from '@selfxyz/common/utils/circuits/uuid';
import { castFromUUID } from '@selfxyz/new-common/src/circuits/userId.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -34,6 +31,7 @@ describe('Disclose', function () {
const passportData = genMockIdDocAndInitDataParsing({
idType: 'mock_id_card',
});
const doc = new PassportDocument(passportData);
console.log(passportData.mrz);
const forbidden_countries_list = ['ALG', 'DZA'];
@@ -45,10 +43,8 @@ describe('Disclose', function () {
const endpoint = 'https://example.com';
const scope = 'scope';
const fullScope = hashEndpointWithScope(endpoint, scope);
const attestation_id = ID_CARD_ATTESTATION_ID;
// compute the commitment and insert it in the tree
const commitment = generateCommitment(secret, attestation_id, passportData);
const commitment = doc.generateCommitment(secret);
console.log('commitment in js ', commitment);
const tree: any = new LeanIMT((a, b) => poseidon2([a, b]), []);
tree.insert(BigInt(commitment));
@@ -61,6 +57,8 @@ describe('Disclose', function () {
const selector_ofac = 1;
const generator = createCircuitInputGenerator();
before(async () => {
circuit = await wasm_tester(
path.join(__dirname, '../../circuits/disclose/vc_and_disclose_id.circom'),
@@ -73,27 +71,32 @@ describe('Disclose', function () {
}
);
inputs = generateCircuitInputsVCandDisclose(
secret,
ID_CARD_ATTESTATION_ID,
passportData,
fullScope,
selector_dg1,
selector_older_than,
tree,
inputs = generator.generateDiscloseInputs(doc, secret, {
scope: fullScope,
fieldsToReveal: [
'issuing_state',
'name',
'id_number',
'nationality',
'date_of_birth',
'gender',
'expiry_date',
'older_than',
'ofac',
],
merkletree: tree,
majority,
null,
passportNo_smt: null,
nameAndDob_smt,
nameAndYob_smt,
selector_ofac,
forbidden_countries_list,
user_identifier
);
user_identifier,
});
});
// it('should compile and load the circuit', async function () {
// expect(circuit).to.not.be.undefined;
// });
it('should compile and load the circuit', async function () {
expect(circuit).to.not.be.undefined;
});
it('should have nullifier == poseidon(secret, scope)', async function () {
w = await circuit.calculateWitness(inputs);
@@ -249,30 +252,21 @@ describe('Disclose', function () {
for (const testCase of testCases) {
console.log(`Testing: ${testCase.desc}`);
const passportData = testCase.data;
const sanctionedCommitment = generateCommitment(
secret,
ID_CARD_ATTESTATION_ID,
passportData
);
const testDoc = new PassportDocument(testCase.data);
const sanctionedCommitment = testDoc.generateCommitment(secret);
tree.insert(BigInt(sanctionedCommitment));
const testInputs = generateCircuitInputsVCandDisclose(
secret,
ID_CARD_ATTESTATION_ID,
passportData,
fullScope,
Array(90).fill('0'), // selector_dg1
selector_older_than,
tree,
const testInputs = generator.generateDiscloseInputs(testDoc, secret, {
scope: fullScope,
fieldsToReveal: ['ofac'],
merkletree: tree,
majority,
null,
passportNo_smt: null,
nameAndDob_smt,
nameAndYob_smt,
'1', // selector_ofac
forbidden_countries_list,
user_identifier
);
user_identifier,
});
w = await circuit.calculateWitness(testInputs);
const revealedData_packed = await circuit.getOutput(w, ['revealedData_packed[4]']);

View File

@@ -1,22 +1,23 @@
import { wasm as wasmTester } from 'circom_tester';
import * as path from 'path';
import {
NON_OFAC_DUMMY_INPUT,
OFAC_DUMMY_INPUT,
KYC_MAX_LENGTH,
serializeKycData,
} from '@selfxyz/common';
NON_OFAC_DUMMY_KYC_DATA,
OFAC_DUMMY_KYC_DATA,
} from '@selfxyz/new-common/src/testing/genMockKycData.js';
import { serializeKycData } from '@selfxyz/new-common/src/documents/kyc/types.js';
import { KYC_MAX_LENGTH } from '@selfxyz/new-common/src/documents/kyc/constants.js';
import { generateKycDiscloseInputFromDummy } from '@selfxyz/new-common/src/circuits/inputs/disclose-kyc.js';
import type { KycField } from '@selfxyz/new-common/src/documents/kyc/constants.js';
import { SMT } from '@openpassport/zk-kit-smt';
import { poseidon2 } from 'poseidon-lite';
import { unpackReveal } from '@selfxyz/common/utils/circuits/formatOutputs.js';
import { unpackReveal } from '@selfxyz/new-common/src/circuits/outputs/format.js';
import { deepEqual } from 'assert';
import { expect } from 'chai';
import { LeanIMT } from '@openpassport/zk-kit-lean-imt';
import { generateKycDiscloseInput } from '@selfxyz/common/utils/kyc/generateInputs';
import { KycField } from '@selfxyz/common/utils/kyc/constants';
import fs from 'fs';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(__filename);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Load KYC OFAC trees at module level
const nameAndDobKycjson = JSON.parse(
@@ -80,7 +81,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
it('should verify for correct Circuit Input and output', async function () {
this.timeout(0);
const input = generateKycDiscloseInput(
const input = generateKycDiscloseInputFromDummy(
false,
namedob_smt,
nameyob_smt,
@@ -100,7 +101,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
it('should fail for invalid msg ascii', async function () {
this.timeout(0);
const input = generateKycDiscloseInput(
const input = generateKycDiscloseInputFromDummy(
false,
namedob_smt,
nameyob_smt,
@@ -130,7 +131,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
it('should return 0 for an OFAC person', async function () {
this.timeout(0);
const input = generateKycDiscloseInput(
const input = generateKycDiscloseInputFromDummy(
true,
namedob_smt,
nameyob_smt,
@@ -148,7 +149,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
await circuit.checkConstraints(witness);
const revealedData_packed = await getRevealedDataPacked(witness);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
const ofac_results = revealedDataUnpacked.slice(maxLength, maxLength + 2);
deepEqual(ofac_results, ['\x00', '\x00']);
@@ -156,7 +157,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
it('should return 0 for an OFAC person with reverse', async function () {
this.timeout(0);
const input = generateKycDiscloseInput(
const input = generateKycDiscloseInputFromDummy(
true,
namedob_smt,
nameyob_smt,
@@ -175,7 +176,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
await circuit.checkConstraints(witness);
const revealedData_packed = await getRevealedDataPacked(witness);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
const ofac_results = revealedDataUnpacked.slice(maxLength, maxLength + 2);
deepEqual(ofac_results, ['\x00', '\x00']);
@@ -183,7 +184,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
it('should return 1 for a non OFAC person', async function () {
this.timeout(0);
const input = generateKycDiscloseInput(
const input = generateKycDiscloseInputFromDummy(
false,
namedob_smt,
nameyob_smt,
@@ -201,7 +202,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
await circuit.checkConstraints(witness);
const revealedData_packed = await getRevealedDataPacked(witness);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
const ofac_results = revealedDataUnpacked.slice(maxLength, maxLength + 2);
deepEqual(ofac_results, ['\x01', '\x01']);
@@ -223,7 +224,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
'GENDER',
'ADDRESS',
];
const input = generateKycDiscloseInput(
const input = generateKycDiscloseInputFromDummy(
false,
namedob_smt,
nameyob_smt,
@@ -241,9 +242,9 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
await circuit.checkConstraints(witness);
const revealedData_packed = await getRevealedDataPacked(witness);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
const serializedData = Buffer.from(serializeKycData(NON_OFAC_DUMMY_INPUT), 'utf8');
const serializedData = Buffer.from(serializeKycData(NON_OFAC_DUMMY_KYC_DATA), 'utf8');
const serializedArray = Array.from(serializedData);
for (let i = 0; i < Math.min(serializedArray.length, maxLength); i++) {
@@ -280,7 +281,7 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
'GENDER',
'ADDRESS',
];
const input = generateKycDiscloseInput(
const input = generateKycDiscloseInputFromDummy(
true,
namedob_smt,
nameyob_smt,
@@ -299,9 +300,9 @@ describe('VC_AND_DISCLOSE KYC Circuit Tests', () => {
await circuit.checkConstraints(witness);
const revealedData_packed = await getRevealedDataPacked(witness);
const revealedDataUnpacked = unpackReveal(revealedData_packed, 'id');
const revealedDataUnpacked = unpackReveal(revealedData_packed);
const serializedData = Buffer.from(serializeKycData(OFAC_DUMMY_INPUT), 'utf8');
const serializedData = Buffer.from(serializeKycData(OFAC_DUMMY_KYC_DATA), 'utf8');
const serializedArray = Array.from(serializedData);
for (let i = 0; i < Math.min(serializedArray.length, maxLength); i++) {

View File

@@ -2,14 +2,14 @@ import { expect } from 'chai';
import { wasm as wasm_tester } from 'circom_tester';
import dotenv from 'dotenv';
import path from 'path';
import serialized_csca_tree from '../../../common/pubkeys/serialized_csca_tree.json' with { type: 'json' };
import { parseCertificateSimple } from '@selfxyz/common/utils/certificate_parsing/parseCertificateSimple';
import { getCircuitNameFromPassportData } from '@selfxyz/common/utils/circuits/circuitsName';
import { generateCircuitInputsDSC } from '@selfxyz/common/utils/circuits/generateInputs';
import { genAndInitMockPassportData } from '@selfxyz/common/utils/passports/genMockPassportData';
import { parseDscCertificateData } from '@selfxyz/common/utils/passports/passport_parsing/parseDscCertificateData';
import { getLeafDscTreeFromParsedDsc } from '@selfxyz/common/utils/trees';
import { SignatureAlgorithm } from '@selfxyz/common/utils/types';
import serialized_csca_tree from '@selfxyz/new-common/src/data/serialized_csca_tree.json' with { type: 'json' };
import { parseCertificateSimple } from '@selfxyz/new-common/src/certificates/parsing/parseCertificateSimple.js';
import { parseDscCertificateData } from '@selfxyz/new-common/src/certificates/parsing/parseDscCertificateData.js';
import { createCircuitInputGenerator } from '@selfxyz/new-common/src/circuits/generator.js';
import { PassportDocument } from '@selfxyz/new-common/src/documents/passport/adapter.js';
import { genAndInitMockPassportData } from '@selfxyz/new-common/src/testing/genMockPassportData.js';
import { getLeafDscTree } from '@selfxyz/new-common/src/trees/index.js';
import type { SignatureAlgorithm } from '@selfxyz/new-common/src/foundation/types/document.js';
import { fullSigAlgs, sigAlgs } from './test_cases.js';
import { fileURLToPath } from 'url';
dotenv.config();
@@ -27,22 +27,22 @@ testSuite.forEach(({ sigAlg, hashFunction, domainParameter, keyLength }) => {
'000101',
'300101'
);
const doc = new PassportDocument(passportData);
const passportMetadata = passportData.passportMetadata;
const generator = createCircuitInputGenerator();
describe(`DSC chain certificate - ${passportMetadata.cscaHashFunction?.toUpperCase()} ${passportMetadata.cscaSignatureAlgorithm?.toUpperCase()} ${passportMetadata.cscaCurveOrExponent?.toUpperCase()} ${
passportData.csca_parsed.publicKeyDetails.bits
}`, function () {
this.timeout(0); // Disable timeout
let circuit;
const inputs = generateCircuitInputsDSC(passportData, serialized_csca_tree);
const inputs = generator.generateDscInputs(doc, serialized_csca_tree);
before(async () => {
circuit = await wasm_tester(
path.join(
__dirname,
`../../circuits/dsc/instances/${getCircuitNameFromPassportData(passportData, 'dsc')}.circom`
),
path.join(__dirname, `../../circuits/dsc/instances/${doc.getDscCircuitName()}.circom`),
{
include: [
'node_modules',
@@ -66,7 +66,7 @@ testSuite.forEach(({ sigAlg, hashFunction, domainParameter, keyLength }) => {
console.log('\x1b[34m%s\x1b[0m', 'circom: dsc_tree_leaf: ', dsc_tree_leaf);
expect(dsc_tree_leaf).to.be.a('string');
const dsc_tree_leaf_js = getLeafDscTreeFromParsedDsc(passportData.dsc_parsed);
const dsc_tree_leaf_js = getLeafDscTree(passportData.dsc_parsed!, passportData.csca_parsed!);
console.log('\x1b[34m%s\x1b[0m', 'js: dsc_tree_leaf: ', dsc_tree_leaf_js);
expect(dsc_tree_leaf).to.be.equal(dsc_tree_leaf_js);
});

View File

@@ -8,10 +8,10 @@ import nameAndDobjson from '../consts/ofac/nameAndDobSMT.json' with { type: 'jso
import nameAndYobjson from '../consts/ofac/nameAndYobSMT.json' with { type: 'json' };
import nameAndDobIdCardJson from '../consts/ofac/nameAndDobSMT_ID.json' with { type: 'json' };
import nameAndYobIdCardJson from '../consts/ofac/nameAndYobSMT_ID.json' with { type: 'json' };
import { genMockIdDoc } from '@selfxyz/common/utils/passports/genMockIdDoc';
import { genMockIdDoc } from '@selfxyz/new-common/src/testing/genMockIdDoc.js';
import passportNoAndNationalityjson from '../consts/ofac/passportNoAndNationalitySMT.json' with { type: 'json' };
import { generateCircuitInputsOfac } from '@selfxyz/common/utils/circuits/generateInputs';
import { genAndInitMockPassportData } from '@selfxyz/common/utils/passports/genMockPassportData';
import { generateCircuitInputsOfac } from '@selfxyz/new-common/src/circuits/inputs/ofac.js';
import { genAndInitMockPassportData } from '@selfxyz/new-common/src/testing/genMockPassportData.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));

View File

@@ -1,8 +1,11 @@
import { expect } from 'chai';
import path from 'path';
import { wasm as wasm_tester } from 'circom_tester';
import { formatInput } from '@selfxyz/common/utils/circuits/generateInputs';
import { customHasher, packBytesAndPoseidon } from '@selfxyz/common/utils/hash';
import { formatInput } from '@selfxyz/new-common/src/circuits/inputs/format.js';
import {
customHasher,
packBytesAndPoseidon,
} from '@selfxyz/new-common/src/crypto/hash/poseidon.js';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));

View File

@@ -1,11 +1,13 @@
import { expect } from 'chai';
import { wasm as wasm_tester } from 'circom_tester';
import path from 'path';
import { formatCountriesList } from '@selfxyz/common/utils/circuits/formatInputs';
import { formatAndUnpackForbiddenCountriesList } from '@selfxyz/common/utils/circuits/formatOutputs';
import { formatInput } from '@selfxyz/common/utils/circuits/generateInputs';
import { formatMrz } from '@selfxyz/common/utils/passports/format';
import { genAndInitMockPassportData } from '@selfxyz/common/utils/passports/genMockPassportData';
import {
formatCountriesList,
formatInput,
} from '@selfxyz/new-common/src/circuits/inputs/format.js';
import { formatAndUnpackForbiddenCountriesList } from '@selfxyz/new-common/src/circuits/outputs/format.js';
import { formatMrz } from '@selfxyz/new-common/src/documents/passport/format.js';
import { genAndInitMockPassportData } from '@selfxyz/new-common/src/testing/genMockPassportData.js';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));

View File

@@ -1,13 +1,13 @@
import { expect } from 'chai';
import path from 'path';
import { wasm as wasm_tester } from 'circom_tester';
import { testQRData } from '../../../common/src/utils/aadhaar/assets/dataInput.js';
import { testDefaultQRData } from '@selfxyz/new-common/src/testing/genMockAadhaarData.js';
import { sha256Pad } from '@zk-email/helpers/dist/sha-utils.js';
import { Uint8ArrayToCharArray } from '@zk-email/helpers/dist/binary-format.js';
import { convertBigIntToByteArray, decompressByteArray } from '@anon-aadhaar/core';
import { assert } from 'chai';
import { testCustomData } from '../utils/aadhaar/generateTestData.js';
import { generateTestData } from '@selfxyz/common';
import { generateTestData } from '@selfxyz/new-common/src/testing/genMockAadhaarData.js';
import { fileURLToPath } from 'url';
import fs from 'fs';
@@ -43,7 +43,7 @@ describe('Aadhaar QR Data Extractor1', function () {
it('should extract qr data', async function () {
this.timeout(0);
const QRDataBytes = convertBigIntToByteArray(BigInt(testQRData.testQRData));
const QRDataBytes = convertBigIntToByteArray(BigInt(testDefaultQRData));
const QRDataDecode = decompressByteArray(QRDataBytes);
const signedData = QRDataDecode.slice(0, QRDataDecode.length - 256);

View File

@@ -187,5 +187,49 @@ dykOfrmi+DMQ3JjEHTebCcYsj8bLgSxbSQAnm2GOGiF3IzyyBr5Lx+ktsuP39900QNuQ36eMqCNy
liItPMXBJM+dSpolry/OjIsD4x75XDS7GLZi2ZpSBwQRaslxVdB403FD709eimEQ9GfBr8kDx+ff
a8RdwIZ7eEzPpsu+vwmwQTYVBU1AvMWNlyxw+5bA2YMx17rtpCx3n3wZOgCjXScTYJgDkQTLGMss
lhr6Td1Tj4+b49a1KQrtOgmjrwq0/fZOflrjFg4dWtAPfcP69sjdWgYn6XmquMeUcRpxXA==
-----END CERTIFICATE-----`,
`-----BEGIN CERTIFICATE-----
MIIHzTCCBrWgAwIBAgIEYkxPUDANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UEBhMC
SU4xQTA/BgNVBAoTOEd1amFyYXQgTmFybWFkYSBWYWxsZXkgRmVydGlsaXplcnMg
YW5kIENoZW1pY2FscyBMaW1pdGVkMQ8wDQYDVQQLEwZTdWItQ0ExLjAsBgNVBAMT
JShuKUNvZGUgU29sdXRpb25zIFN1Yi1DQSBmb3IgRFNDIDIwMjIwHhcNMjYwMjAz
MDk1NjEyWhcNMjkwMjAzMTcyODM2WjCCAQYxCzAJBgNVBAYTAklOMTEwLwYDVQQK
EyhVTklRVUUgSURFTlRJRklDQVRJT04gQVVUSE9SSVRZIE9GIElORElBMQ8wDQYD
VQQREwYxMTAwMDExDjAMBgNVBAgTBURlbGhpMUMwQQYDVQQJEzpCQU5HTEEgU0FI
SUIgUk9BRCBCRUhJTkQgS0FMSSBNQU5ESVIgR09MRSBNQVJLRVQgTkVXIERFTEhJ
MSUwIwYDVQQzExw0VEggRkxPT1IgVUlEQUkgSEVBRFFVQVJURVJTMTcwNQYDVQQD
Ey5EUyBVbmlxdWUgSWRlbnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIEluZGlhIDA2
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh1+zYnvbcEm0Yz73s5u4
2odpUJMr9wv5bVw7sOE5nFNbrB+U++5I0f8cL2HoHnJOkwvLZzrD0jG/vxAKi6vi
i/gjEzUEgrkdIHxMP3D6GJs0MSQHiEXvIGOwPIH3BLtBOc3m28NVNT6Q9iq0gUwu
xnlhV38UdNhCllqNYhWmAMPJkImgaKrRZvY2pWNs6gd+PlAF/9SO69x3+1meA8kP
k2ZvQanZlx9tfaExeOe9or3NQiKy2+UbtXrpcoAfYbbWi1OUzXi5bJdhbGp239c1
fX6UKyUM5IUMY+m3I7wu2WQ7lmeO2n/vwzQz/PKHXPWYu3bydWMLdCi07vOQBqzC
KwIDAQABo4IDszCCA68wDgYDVR0PAQH/BAQDAgbAMCoGA1UdJQQjMCEGCCsGAQUF
BwMEBgorBgEEAYI3CgMMBgkqhkiG9y8BAQUwggECBgNVHSAEgfowgfcwgYYGBmCC
ZGQCAjB8MHoGCCsGAQUFBwICMG4MbENsYXNzIDIgY2VydGlmaWNhdGVzIGFyZSB1
c2VkIGZvciBmb3JtIHNpZ25pbmcsIGZvcm0gYXV0aGVudGljYXRpb24gYW5kIHNp
Z25pbmcgb3RoZXIgbG93IHJpc2sgdHJhbnNhY3Rpb25zLjBsBgZggmRkCgEwYjBg
BggrBgEFBQcCAjBUDFJUaGlzIGNlcnRpZmljYXRlIHByb3ZpZGVzIGhpZ2hlciBs
ZXZlbCBvZiBhc3N1cmFuY2UgZm9yIGRvY3VtZW50IHNpZ25pbmcgZnVuY3Rpb24u
MIGsBggrBgEFBQcBAQSBnzCBnDA+BggrBgEFBQcwAYYyaHR0cDovL29jc3AubmNv
ZGUuaW4vbkNvZGVTb2x1dGlvbnNTdWJDQWZvckRTQzIwMjIwWgYIKwYBBQUHMAKG
Tmh0dHBzOi8vd3d3Lm5jb2Rlc29sdXRpb25zLmNvbS9yZXBvc2l0b3J5L0NBLVNl
cnZpY2VzLTIwMjIvbmNvZGVjYTIyU3ViY2ExLmRlcjAMBgNVHRMBAf8EAjAAMCIG
A1UdEQQbMBmBF2Rpci5jcm0taHFAdWlkYWkubmV0LmluMIIBAAYDVR0fBIH4MIH1
MEKgQKA+hjxodHRwOi8vd3d3Lm5jb2Rlc29sdXRpb25zLmNvbS9yZXBvc2l0b3J5
L25jb2RlY2EyMnN1YmNhMS5jcmwwga6ggauggaikgaUwgaIxCzAJBgNVBAYTAklO
MUEwPwYDVQQKEzhHdWphcmF0IE5hcm1hZGEgVmFsbGV5IEZlcnRpbGl6ZXJzIGFu
ZCBDaGVtaWNhbHMgTGltaXRlZDEPMA0GA1UECxMGU3ViLUNBMS4wLAYDVQQDEyUo
bilDb2RlIFNvbHV0aW9ucyBTdWItQ0EgZm9yIERTQyAyMDIyMQ8wDQYDVQQDEwZD
Ukw0MTIwKwYDVR0QBCQwIoAPMjAyNjAyMDMwOTU2MTJagQ8yMDI5MDIwMzE3Mjgz
NlowHwYDVR0jBBgwFoAURsE2obZEOWzewDFm8UhoheJjvzswHQYDVR0OBBYEFGZ+
DVgYpnloELJVhydpAEQE3qV7MBkGCSqGSIb2fQdBAAQMMAobBFY4LjMDAgMoMA0G
CSqGSIb3DQEBCwUAA4IBAQACmoprnogLK9HqtpDeMPGuTN98tA7ZSOKqs9S+PGIi
z8h8MnxnlNuUr6HVfe4wWImEeDcfIHWUCPjx/QWy0XeqC09+cwnhdRoQA20CnUMY
vWoOE9zkOzeks0dEzUD9ENeyG6ToxwHrzSogVO6AZ25dDrgpMtqKLlL0DYkNtZke
zT3OnRSVeYCKhvaLlDS9SlwaVKjMzIxDQu190lRwqSXyKC7U8LWVkniB7+rEeiaD
A+9M0jnYvUAj5Jzdp6DuEU94m2RglS3zoHq88OKm/QnayZNBVWYRSrprEMKW3urG
Xm5JdIO8PkWeSc0JbZJUNg2MBNqzhFDzCjqnf4DIOJdE
-----END CERTIFICATE-----`,
];

View File

@@ -4,14 +4,12 @@ import dotenv from 'dotenv';
import { describe } from 'mocha';
import path from 'path';
import { poseidon6 } from 'poseidon-lite';
import serialized_dsc_tree from '@selfxyz/common/pubkeys/serialized_dsc_tree.json' with { type: 'json' };
import { PASSPORT_ATTESTATION_ID } from '@selfxyz/common/constants/constants';
import { parseCertificateSimple } from '@selfxyz/common/utils/certificate_parsing/parseCertificateSimple';
import { getCircuitNameFromPassportData } from '@selfxyz/common/utils/circuits/circuitsName';
import { generateCircuitInputsRegisterForTests } from '@selfxyz/common/utils/circuits/generateInputs';
import { genAndInitMockPassportData } from '@selfxyz/common/utils/passports/genMockPassportData';
import { generateCommitment, generateNullifier } from '@selfxyz/common/utils/passports/passport';
import { SignatureAlgorithm } from '@selfxyz/common/utils/types';
import serialized_dsc_tree from '@selfxyz/new-common/src/data/serialized_dsc_tree.json' with { type: 'json' };
import { PassportDocument } from '@selfxyz/new-common/src/documents/passport/adapter.js';
import { parseCertificateSimple } from '@selfxyz/new-common/src/certificates/parsing/parseCertificateSimple.js';
import { createCircuitInputGenerator } from '@selfxyz/new-common/src/circuits/generator.js';
import { genAndInitMockPassportData } from '@selfxyz/new-common/src/testing/genMockPassportData.js';
import type { SignatureAlgorithm } from '@selfxyz/new-common/src/foundation/types/document.js';
import { fullSigAlgs, sigAlgs } from './test_cases.js';
import { fileURLToPath } from 'url';
@@ -46,19 +44,20 @@ testSuite.forEach(
'300101'
);
const doc = new PassportDocument(passportData);
const secret = poseidon6('SECRET'.split('').map((x) => BigInt(x.charCodeAt(0)))).toString();
const inputs = generateCircuitInputsRegisterForTests(
secret,
passportData,
serialized_dsc_tree as string
);
const generator = createCircuitInputGenerator();
const inputs = generator.generateRegisterInputs(doc, secret, serialized_dsc_tree as string, {
useTestPadding: true,
});
before(async () => {
circuit = await wasm_tester(
path.join(
__dirname,
`../../circuits/register/instances/${getCircuitNameFromPassportData(passportData, 'register')}.circom`
`../../circuits/register/instances/${doc.getRegisterCircuitName()}.circom`
),
{
include: [
@@ -78,17 +77,13 @@ testSuite.forEach(
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
const nullifier_js = generateNullifier(passportData);
const nullifier_js = doc.generateNullifier();
console.log('\x1b[35m%s\x1b[0m', 'js: nullifier:', nullifier_js);
const nullifier = (await circuit.getOutput(w, ['nullifier'])).nullifier;
console.log('\x1b[34m%s\x1b[0m', 'circom: nullifier', nullifier);
expect(nullifier).to.be.equal(nullifier_js);
const commitment_js = generateCommitment(
secret.toString(),
PASSPORT_ATTESTATION_ID,
passportData
);
const commitment_js = doc.generateCommitment(secret.toString());
console.log('\x1b[35m%s\x1b[0m', 'js: commitment:', commitment_js);
const commitment = (await circuit.getOutput(w, ['commitment'])).commitment;
console.log('\x1b[34m%s\x1b[0m', 'circom commitment', commitment);

View File

@@ -2,30 +2,80 @@ import { expect } from 'chai';
import { wasm as wasmTester } from 'circom_tester';
import path from 'path';
import { sha256Pad } from '@zk-email/helpers/dist/sha-utils.js';
import { bufferToHex, Uint8ArrayToCharArray } from '@zk-email/helpers/dist/binary-format.js';
import { Uint8ArrayToCharArray } from '@zk-email/helpers/dist/binary-format.js';
import { convertBigIntToByteArray, decompressByteArray, splitToWords } from '@anon-aadhaar/core';
import assert from 'assert';
import { customHasher } from '@selfxyz/common/utils/hash';
import forge from 'node-forge';
import { fileURLToPath } from 'url';
import { customHasher } from '@selfxyz/new-common/src/crypto/hash/poseidon.js';
import { AadhaarDocument } from '@selfxyz/new-common/src/documents/aadhaar/adapter.js';
import { genMockIdDoc } from '@selfxyz/new-common/src/testing/genMockIdDoc.js';
import {
prepareAadhaarRegisterTestData,
generateTestData,
testCustomData,
prepareAadhaarRegisterData,
} from '@selfxyz/common';
import fs from 'fs';
} from '@selfxyz/new-common/src/testing/genMockAadhaarData.js';
import {
AADHAAR_MOCK_PRIVATE_KEY_PEM,
AADHAAR_MOCK_PUBLIC_KEY_PEM,
} from '@selfxyz/new-common/src/testing/mockAadhaarCert.js';
import {
extractSignatureBytes,
processQRData,
} from '@selfxyz/new-common/src/documents/aadhaar/qr.js';
import { createCircuitInputGenerator } from '@selfxyz/new-common/src/circuits/generator.js';
import type { AadhaarData } from '@selfxyz/new-common/src/foundation/types/document.js';
import { pubkeys } from './pubkeys.js';
const __dirname = path.dirname(new URL(import.meta.url).pathname);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const generator = createCircuitInputGenerator();
const privateKeyPem = fs.readFileSync(
path.join(__dirname, '../../node_modules/anon-aadhaar-circuits/assets/testPrivateKey.pem'),
'utf8'
);
const publicKeyPem = fs.readFileSync(
path.join(__dirname, '../../node_modules/anon-aadhaar-circuits/assets/testPublicKey.pem'),
'utf8'
);
function createAadhaarDoc(opts?: {
name?: string;
dateOfBirth?: string;
gender?: string;
pincode?: string;
state?: string;
timestamp?: string;
}): AadhaarDocument {
const hasCustom =
opts?.name ||
opts?.dateOfBirth ||
opts?.gender ||
opts?.pincode ||
opts?.state ||
opts?.timestamp;
if (hasCustom) {
// For custom fields or timestamp, we generate test data and build AadhaarData manually
const generated = generateTestData({
privKeyPem: AADHAAR_MOCK_PRIVATE_KEY_PEM,
data: testCustomData,
name: opts?.name,
dob: opts?.dateOfBirth,
gender: opts?.gender,
pincode: opts?.pincode,
state: opts?.state,
timestamp: opts?.timestamp,
});
const processed = processQRData(generated.testQRData);
const signatureBytes = extractSignatureBytes(processed.decodedData);
const data: AadhaarData = {
documentType: 'mock_aadhaar',
documentCategory: 'aadhaar',
mock: true,
qrData: generated.testQRData,
extractedFields: processed.extractedFields,
signature: Array.from(signatureBytes),
publicKey: AADHAAR_MOCK_PUBLIC_KEY_PEM,
photoHash: processed.photoHash.toString(),
};
return new AadhaarDocument(data);
}
// Default case: use unified genMockIdDoc
const data = genMockIdDoc({ idType: 'mock_aadhaar' });
return new AadhaarDocument(data);
}
describe('REGISTER AADHAAR Circuit Tests', function () {
let circuit: any;
@@ -45,36 +95,40 @@ describe('REGISTER AADHAAR Circuit Tests', function () {
this.timeout(0);
expect(circuit).to.not.be.undefined;
});
it('should pass constrain check for circuit with Sha256RSA signature', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarRegisterTestData(privateKeyPem, publicKeyPem, '1234');
const doc = createAadhaarDoc();
const inputs = generator.generateRegisterInputs(doc, '1234', '');
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
});
it('should pass constrain and output correct nullifier and commitment', async function () {
this.timeout(0);
const { inputs, nullifier, commitment } = prepareAadhaarRegisterTestData(
privateKeyPem,
publicKeyPem,
'1234'
);
const doc = createAadhaarDoc();
const inputs = generator.generateRegisterInputs(doc, '1234', '');
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
const out = await circuit.getOutput(w, ['nullifier', 'commitment']);
assert(BigInt(out.nullifier) === BigInt(nullifier));
assert(BigInt(out.commitment) === BigInt(commitment));
assert(BigInt(out.nullifier) === BigInt(doc.generateNullifier()));
assert(BigInt(out.commitment) === BigInt(doc.generateCommitment('1234')));
});
it('should not verify the signature of created from different key', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarRegisterTestData(privateKeyPem, publicKeyPem, '1234');
const newTestData = generateTestData({ privKeyPem: privateKeyPem, data: testCustomData });
const doc = createAadhaarDoc();
const inputs = generator.generateRegisterInputs(doc, '1234', '') as Record<string, any>;
const newTestData = generateTestData({
privKeyPem: AADHAAR_MOCK_PRIVATE_KEY_PEM,
data: testCustomData,
});
const QRDataBytes = convertBigIntToByteArray(BigInt(newTestData.testQRData));
const decodedData = decompressByteArray(QRDataBytes);
const signatureBytes = decodedData.slice(decodedData.length - 256, decodedData.length);
const newSignature = BigInt('0x' + bufferToHex(Buffer.from(signatureBytes)).toString());
const newSignature = BigInt('0x' + Buffer.from(signatureBytes).toString('hex'));
inputs.signature = splitToWords(newSignature, BigInt(121), BigInt(17));
try {
@@ -87,10 +141,11 @@ describe('REGISTER AADHAAR Circuit Tests', function () {
it('should fail when qrdata is tampered', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarRegisterTestData(privateKeyPem, publicKeyPem, '1234');
const doc = createAadhaarDoc();
const inputs = generator.generateRegisterInputs(doc, '1234', '') as Record<string, any>;
const newTestData = generateTestData({
privKeyPem: privateKeyPem,
privKeyPem: AADHAAR_MOCK_PRIVATE_KEY_PEM,
data: testCustomData,
gender: 'F',
});
@@ -98,7 +153,6 @@ describe('REGISTER AADHAAR Circuit Tests', function () {
const decodedData = decompressByteArray(QRDataBytes);
const signedData = decodedData.slice(0, decodedData.length - 256);
const [qrDataPadded, qrDataPaddedLen] = sha256Pad(signedData, 512 * 3);
inputs.qrDataPadded = Uint8ArrayToCharArray(qrDataPadded);
@@ -114,38 +168,35 @@ describe('REGISTER AADHAAR Circuit Tests', function () {
it('should return different commitment when secret is tampered', async function () {
this.timeout(0);
const { inputs, commitment } = prepareAadhaarRegisterTestData(
privateKeyPem,
publicKeyPem,
'1234'
);
const doc = createAadhaarDoc();
const originalCommitment = doc.generateCommitment('1234');
const inputs = generator.generateRegisterInputs(doc, '1234', '') as Record<string, any>;
inputs.secret = '1235';
const w = await circuit.calculateWitness(inputs);
const out = await circuit.getOutput(w, ['commitment']);
assert(BigInt(out.commitment) !== BigInt(commitment));
assert(BigInt(out.commitment) !== BigInt(originalCommitment));
});
it.skip('should pass for different qr data', async function () {
this.timeout(0);
const { inputs, nullifier, commitment } = prepareAadhaarRegisterTestData(
privateKeyPem,
publicKeyPem,
'1234',
'KL RAHUL',
'18-04-1992'
);
const doc = createAadhaarDoc({
name: 'KL RAHUL',
dateOfBirth: '18-04-1992',
});
const inputs = generator.generateRegisterInputs(doc, '1234', '');
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
const out = await circuit.getOutput(w, ['nullifier', 'commitment']);
assert(BigInt(out.nullifier) === BigInt(nullifier));
assert(BigInt(out.commitment) === BigInt(commitment));
assert(BigInt(out.nullifier) === BigInt(doc.generateNullifier()));
assert(BigInt(out.commitment) === BigInt(doc.generateCommitment('1234')));
});
it('should create the pubkey commitment correctly', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarRegisterTestData(privateKeyPem, publicKeyPem, '1234');
const doc = createAadhaarDoc();
const inputs = generator.generateRegisterInputs(doc, '1234', '') as Record<string, any>;
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
@@ -157,17 +208,11 @@ describe('REGISTER AADHAAR Circuit Tests', function () {
it('should create the timestamp correctly', async function () {
this.timeout(0);
const { inputs } = prepareAadhaarRegisterTestData(
privateKeyPem,
publicKeyPem,
'1234',
'Some Guy',
undefined,
undefined,
undefined,
undefined,
new Date(Date.now() - 30 * 60 * 1000).getTime().toString()
);
const doc = createAadhaarDoc({
name: 'Some Guy',
timestamp: new Date(Date.now() - 30 * 60 * 1000).getTime().toString(),
});
const inputs = generator.generateRegisterInputs(doc, '1234', '');
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
@@ -176,21 +221,10 @@ describe('REGISTER AADHAAR Circuit Tests', function () {
it.skip('should work for a real id', async function () {
this.timeout(0);
const actualQrData = '';
const { inputs, nullifier, commitment } = await prepareAadhaarRegisterData(
actualQrData,
'1234',
pubkeys
);
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
const out = await circuit.getOutput(w, ['nullifier', 'commitment', 'pubKeyHash']);
assert(BigInt(out.nullifier) === BigInt(nullifier));
assert(BigInt(out.commitment) === BigInt(commitment));
// Production path — not testable without real QR data + matching certs
});
it.skip('should log all pubkey commitments', async function () {
it('should log all pubkey commitments', async function () {
this.timeout(0);
for (const cert of pubkeys) {
const certObj = forge.pki.certificateFromPem(cert);

View File

@@ -1,19 +1,19 @@
import { expect } from 'chai';
import { wasm as wasmTester } from 'circom_tester';
import path from 'path';
import { packBytesAndPoseidon } from '@selfxyz/common/utils/hash';
import { packBytesAndPoseidon } from '@selfxyz/new-common/src/crypto/hash/poseidon.js';
import { poseidon2 } from 'poseidon-lite';
import {
generateKycRegisterInput,
generateMockKycRegisterInput,
} from '@selfxyz/common/utils/kyc/generateInputs.js';
import { KycRegisterInput } from '@selfxyz/common/utils/kyc/types';
import { generateMockKycRegisterInputs } from '@selfxyz/new-common/src/circuits/inputs/register-kyc.js';
import type { KycRegisterInput } from '@selfxyz/new-common/src/documents/kyc/types.js';
import {
KYC_ID_NUMBER_INDEX,
KYC_ID_NUMBER_LENGTH,
KYC_ID_TYPE_INDEX,
KYC_ID_TYPE_LENGTH,
} from '@selfxyz/common/utils/kyc/constants';
} from '@selfxyz/new-common/src/documents/kyc/constants.js';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
describe('REGISTER KYC Circuit Tests', () => {
let circuit: any;
@@ -21,7 +21,7 @@ describe('REGISTER KYC Circuit Tests', () => {
before(async function () {
this.timeout(0);
input = await generateMockKycRegisterInput(null, true, undefined);
input = generateMockKycRegisterInputs(null, true, undefined);
circuit = await wasmTester(
path.join(__dirname, '../../circuits/register/instances/register_kyc.circom'),
{
@@ -83,7 +83,7 @@ describe('REGISTER KYC Circuit Tests', () => {
it('should fail if data is tampered', async function () {
this.timeout(0);
input = await generateMockKycRegisterInput(null, true, undefined);
input = generateMockKycRegisterInputs(null, true, undefined);
input.data_padded[5] = Number(input.data_padded[5]) + 1;
try {
const w = await circuit.calculateWitness(input);
@@ -96,7 +96,7 @@ describe('REGISTER KYC Circuit Tests', () => {
it('should fail if data is not bytes', async function () {
this.timeout(0);
input = await generateMockKycRegisterInput(null, true, undefined);
input = generateMockKycRegisterInputs(null, true, undefined);
input.data_padded[5] = 8000;
try {
const w = await circuit.calculateWitness(input);
@@ -123,7 +123,7 @@ describe('REGISTER KYC Circuit Tests', () => {
it('should fail if s is 0', async function () {
this.timeout(0);
input = await generateMockKycRegisterInput(null, true, undefined);
input = generateMockKycRegisterInputs(null, true, undefined);
input.s = BigInt(0);
try {
const w = await circuit.calculateWitness(input);
@@ -136,7 +136,7 @@ describe('REGISTER KYC Circuit Tests', () => {
it('should fail if R is not on the curve', async function () {
this.timeout(0);
input = await generateMockKycRegisterInput(null, true, undefined);
input = generateMockKycRegisterInputs(null, true, undefined);
//go beyond the suborder
input.R[0] = BigInt(
BigInt('9736030358979909402780800718157159386076813972158567259200215660948447373049') + 1n
@@ -153,7 +153,7 @@ describe('REGISTER KYC Circuit Tests', () => {
it('should fail if pubKey is not on the curve', async function () {
this.timeout(0);
input = await generateMockKycRegisterInput(null, true, undefined);
input = generateMockKycRegisterInputs(null, true, undefined);
input.pubKey[0] = BigInt(
'2736030358979909402780800718157159386076813972158567259200215660948447373049'
);

View File

@@ -3,16 +3,15 @@ import { describe } from 'mocha';
import { expect } from 'chai';
import path from 'path';
import { wasm as wasm_tester } from 'circom_tester';
import { generateCircuitInputsRegisterForTests } from '@selfxyz/common/utils/circuits/generateInputs';
import { SignatureAlgorithm } from '@selfxyz/common/utils/types';
import { getCircuitNameFromPassportData } from '@selfxyz/common/utils/circuits/circuitsName';
import { createCircuitInputGenerator } from '@selfxyz/new-common/src/circuits/generator.js';
import type { SignatureAlgorithm } from '@selfxyz/new-common/src/foundation/types/document.js';
import type { hashAlgosTypes } from '@selfxyz/new-common/src/foundation/constants/crypto.js';
import { PassportDocument } from '@selfxyz/new-common/src/documents/passport/adapter.js';
import { parseCertificateSimple } from '@selfxyz/new-common/src/certificates/parsing/parseCertificateSimple.js';
import { sigAlgs, fullSigAlgs } from './test_cases.js';
import { generateCommitment, generateNullifier } from '@selfxyz/common/utils/passports/passport';
import { poseidon6 } from 'poseidon-lite';
import { hashAlgosTypes, ID_CARD_ATTESTATION_ID } from '@selfxyz/common/constants/constants';
import { parseCertificateSimple } from '@selfxyz/common/utils/certificate_parsing/parseCertificateSimple';
import serialized_dsc_tree from '../../../common/pubkeys/serialized_dsc_tree.json' with { type: 'json' };
import { genMockIdDocAndInitDataParsing } from '@selfxyz/common/utils/passports/genMockIdDoc';
import serialized_dsc_tree from '@selfxyz/new-common/src/data/serialized_dsc_tree.json' with { type: 'json' };
import { genMockIdDocAndInitDataParsing } from '@selfxyz/new-common/src/testing/genMockIdDoc.js';
import { fileURLToPath } from 'url';
dotenv.config();
@@ -43,19 +42,20 @@ testSuite.forEach(
signatureType:
`${sigAlg}_${hashFunction}_${domainParameter}_${keyLength}${saltLength ? `_${saltLength}` : ''}` as SignatureAlgorithm,
});
const doc = new PassportDocument(passportData);
const secret = poseidon6('SECRET'.split('').map((x) => BigInt(x.charCodeAt(0)))).toString();
const inputs = generateCircuitInputsRegisterForTests(
secret,
passportData,
serialized_dsc_tree as string
);
const generator = createCircuitInputGenerator();
const inputs = generator.generateRegisterInputs(doc, secret, serialized_dsc_tree as string, {
useTestPadding: true,
});
before(async () => {
circuit = await wasm_tester(
path.join(
__dirname,
`../../circuits/register_id/instances/${getCircuitNameFromPassportData(passportData, 'register')}.circom`
`../../circuits/register_id/instances/${doc.getRegisterCircuitName()}.circom`
),
{
include: [
@@ -75,17 +75,13 @@ testSuite.forEach(
const w = await circuit.calculateWitness(inputs);
await circuit.checkConstraints(w);
const nullifier_js = generateNullifier(passportData);
const nullifier_js = doc.generateNullifier();
console.log('\x1b[35m%s\x1b[0m', 'js: nullifier:', nullifier_js);
const nullifier = (await circuit.getOutput(w, ['nullifier'])).nullifier;
console.log('\x1b[34m%s\x1b[0m', 'circom: nullifier', nullifier);
expect(nullifier).to.be.equal(nullifier_js);
const commitment_js = generateCommitment(
secret.toString(),
ID_CARD_ATTESTATION_ID,
passportData
);
const commitment_js = doc.generateCommitment(secret.toString());
console.log('\x1b[35m%s\x1b[0m', 'js: commitment:', commitment_js);
const commitment = (await circuit.getOutput(w, ['commitment'])).commitment;
console.log('\x1b[34m%s\x1b[0m', 'circom commitment', commitment);

View File

@@ -1,8 +1,8 @@
import { wasm as wasmTester } from 'circom_tester';
import * as crypto from 'crypto';
import { initElliptic } from '@selfxyz/common/utils/certificate_parsing/elliptic';
import { initElliptic } from '@selfxyz/new-common/src/certificates/parsing/elliptic.js';
import * as path from 'path';
import { splitToWords } from '@selfxyz/common/utils/bytes';
import { splitToWords } from '@selfxyz/new-common/src/foundation/bytes.js';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));

View File

@@ -1,9 +1,12 @@
import crypto from 'crypto';
import { SignatureAlgorithm } from '@selfxyz/common/utils/types';
import { hexToDecimal, splitToWords } from '@selfxyz/common/utils/bytes';
import { bytesToBigDecimal } from '@selfxyz/common/utils/bytes';
import { getNAndK } from '@selfxyz/common/utils/passports/passport';
import type { SignatureAlgorithm } from '@selfxyz/new-common/src/foundation/types/document.js';
import {
hexToDecimal,
splitToWords,
bytesToBigDecimal,
} from '@selfxyz/new-common/src/foundation/bytes.js';
import { getNAndK } from '@selfxyz/new-common/src/certificates/signature.js';
export const generateMockRsaPkcs1v1_5Inputs = (signatureAlgorithm: SignatureAlgorithm) => {
let privateKey: string;

View File

@@ -1,10 +1,12 @@
import * as forge from 'node-forge';
import forge from 'node-forge';
import { SignatureAlgorithm } from '@selfxyz/common/utils/types';
import { hexToDecimal } from '@selfxyz/common/utils/bytes';
import { bytesToBigDecimal } from '@selfxyz/common/utils/bytes';
import { getNAndK } from '@selfxyz/common/utils/passports/passport';
import { splitToWords } from '@selfxyz/common/utils/bytes';
import type { SignatureAlgorithm } from '@selfxyz/new-common/src/foundation/types/document.js';
import {
hexToDecimal,
bytesToBigDecimal,
splitToWords,
} from '@selfxyz/new-common/src/foundation/bytes.js';
import { getNAndK } from '@selfxyz/new-common/src/certificates/signature.js';
export const generateMockRsaPssInputs = (
signatureAlgorithm: SignatureAlgorithm,

View File

@@ -1,6 +1,9 @@
import { expect } from 'chai';
import { wasm as wasmTester } from 'circom_tester';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
describe('date', async () => {
let circuit;

View File

@@ -1,6 +1,9 @@
import { expect } from 'chai';
import { wasm as wasmTester } from 'circom_tester';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
describe('isOlderThan', async () => {
let circuit;

View File

@@ -1,16 +1,45 @@
import { expect } from 'chai';
import { wasm as wasmTester } from 'circom_tester';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
import {
generateCircuitInputsOfac,
NON_OFAC_DUMMY_INPUT,
OFAC_DUMMY_INPUT,
} from '../../../../../common/src/utils/kyc/generateInputs';
import { serializeKycData } from '../../../../../common/src/utils/kyc/types';
NON_OFAC_DUMMY_KYC_DATA,
OFAC_DUMMY_KYC_DATA,
} from '@selfxyz/new-common/src/testing/genMockKycData.js';
import { serializeKycData } from '@selfxyz/new-common/src/documents/kyc/types.js';
import {
getNameDobLeafKyc,
getNameYobLeafKyc,
generateSMTProof,
} from '@selfxyz/new-common/src/trees/index.js';
import { SMT } from '@openpassport/zk-kit-smt';
import { formatInput } from '@selfxyz/new-common/src/circuits/inputs/format.js';
import type { KycData } from '@selfxyz/new-common/src/documents/kyc/types.js';
import { poseidon2 } from 'poseidon-lite';
import nameAndDobjson from '../../../consts/ofac/nameAndDobKycSMT.json';
import nameAndYobjson from '../../../consts/ofac/nameAndYobKycSMT.json';
import nameAndDobjson from '../../../consts/ofac/nameAndDobKycSMT.json' with { type: 'json' };
import nameAndYobjson from '../../../consts/ofac/nameAndYobKycSMT.json' with { type: 'json' };
const generateCircuitInputsOfac = (
data: Omit<
KycData,
'user_identifier' | 'current_date' | 'majority_age_ASCII' | 'selector_older_than'
>,
smt: SMT,
proofLevel: number
) => {
const leaf =
proofLevel === 2
? getNameDobLeafKyc(data.fullName, data.dob)
: getNameYobLeafKyc(data.fullName, data.dob.slice(0, 4));
const { root, closestleaf, siblings } = generateSMTProof(smt, leaf);
return {
smt_root: formatInput(root),
smt_leaf_key: formatInput(closestleaf),
smt_siblings: formatInput(siblings),
};
};
describe('OFAC - Name and DOB match', async function () {
this.timeout(10000);
@@ -27,7 +56,7 @@ describe('OFAC - Name and DOB match', async function () {
],
});
namedob_smt.import(nameAndDobjson);
namedob_smt.import(nameAndDobjson as any);
});
it('should compile and load the circuit', async () => {
@@ -35,8 +64,8 @@ describe('OFAC - Name and DOB match', async function () {
});
it('should return 0 if the person is in the ofac list', async () => {
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_KYC_DATA, namedob_smt, proofLevel);
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
...ofacInputs,
@@ -48,8 +77,8 @@ describe('OFAC - Name and DOB match', async function () {
});
it('should return 1 if the person is not in the ofac list', async () => {
const dummy_kyc_input = serializeKycData(NON_OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(NON_OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(NON_OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(NON_OFAC_DUMMY_KYC_DATA, namedob_smt, proofLevel);
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
...ofacInputs,
@@ -61,8 +90,8 @@ describe('OFAC - Name and DOB match', async function () {
});
it('should return 0 if the internal computed merkle root is wrong (wrong leaf key)', async () => {
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_KYC_DATA, namedob_smt, proofLevel);
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
...ofacInputs,
@@ -75,8 +104,8 @@ describe('OFAC - Name and DOB match', async function () {
});
it('should return 0 if the internal computed merkle root is wrong (wrong siblings)', async () => {
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_KYC_DATA, namedob_smt, proofLevel);
ofacInputs.smt_siblings[0] = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
@@ -89,8 +118,8 @@ describe('OFAC - Name and DOB match', async function () {
});
it('should return 0 if the merkle root is wrong', async () => {
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, namedob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_KYC_DATA, namedob_smt, proofLevel);
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
...ofacInputs,
@@ -118,7 +147,7 @@ describe('OFAC - Name and YOB match', async function () {
],
});
nameyob_smt.import(nameAndYobjson);
nameyob_smt.import(nameAndYobjson as any);
});
it('should compile and load the circuit', async () => {
@@ -126,8 +155,8 @@ describe('OFAC - Name and YOB match', async function () {
});
it('should return 0 if the person is in the ofac list', async () => {
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_KYC_DATA, nameyob_smt, proofLevel);
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
...ofacInputs,
@@ -139,8 +168,8 @@ describe('OFAC - Name and YOB match', async function () {
});
it('should return 1 if the person is not in the ofac list', async () => {
const dummy_kyc_input = serializeKycData(NON_OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(NON_OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(NON_OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(NON_OFAC_DUMMY_KYC_DATA, nameyob_smt, proofLevel);
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
...ofacInputs,
@@ -152,8 +181,8 @@ describe('OFAC - Name and YOB match', async function () {
});
it('should return 0 if the internal computed merkle root is wrong (wrong leaf key)', async () => {
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_KYC_DATA, nameyob_smt, proofLevel);
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
...ofacInputs,
@@ -166,8 +195,8 @@ describe('OFAC - Name and YOB match', async function () {
});
it('should return 0 if the internal computed merkle root is wrong (wrong siblings)', async () => {
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_KYC_DATA, nameyob_smt, proofLevel);
ofacInputs.smt_siblings[0] = BigInt(Math.floor(Math.random() * Math.pow(2, 254))).toString();
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
@@ -180,8 +209,8 @@ describe('OFAC - Name and YOB match', async function () {
});
it('should return 0 if the merkle root is wrong', async () => {
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_INPUT);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_INPUT, nameyob_smt, proofLevel);
const dummy_kyc_input = serializeKycData(OFAC_DUMMY_KYC_DATA);
const ofacInputs = generateCircuitInputsOfac(OFAC_DUMMY_KYC_DATA, nameyob_smt, proofLevel);
const inputs = {
data_padded: dummy_kyc_input.split('').map((x) => x.charCodeAt(0)),
...ofacInputs,

View File

@@ -2,7 +2,7 @@ import { wasm as wasmTester } from 'circom_tester';
import { describe, it } from 'mocha';
import path from 'path';
import { generateMockRsaPkcs1v1_5Inputs } from './generateMockInputsInCircuits.js';
import { SignatureAlgorithm } from '@selfxyz/common/utils/types';
import type { SignatureAlgorithm } from '@selfxyz/new-common/src/foundation/types/document.js';
import { expect } from 'chai';
import { fileURLToPath } from 'url';

View File

@@ -1,4 +1,4 @@
import { SignatureAlgorithm } from '@selfxyz/common/utils/types';
import type { SignatureAlgorithm } from '@selfxyz/new-common/src/foundation/types/document.js';
export const fullAlgorithms: { algo: SignatureAlgorithm; saltLength: number }[] = [
{ algo: 'rsapss_sha256_65537_4096', saltLength: 32 },

View File

@@ -13,16 +13,15 @@
"baseUrl": ".",
"composite": false,
"paths": {
"@selfxyz/common/utils/*": ["../common/src/utils/*"],
"@selfxyz/common/constants/*": ["../common/src/constants/*"],
"@selfxyz/common": ["../common/index.ts"]
"@selfxyz/new-common/src/*": ["../new-common/src/*"],
"@selfxyz/new-common": ["../new-common/src/index.ts"]
}
},
"include": ["tests/**/*", "tests/**/*.json", "src/**/*", "../common/src/**/*"],
"include": ["tests/**/*", "tests/**/*.json", "src/**/*"],
"exclude": ["node_modules"],
"references": [
{
"path": "../common"
"path": "../new-common"
}
]
}

View File

@@ -1,25 +1,3 @@
export function getAdjustedTimestampBytes(y: number = 0, m: number = 0, d: number = 0): number[] {
// Get the current date/time
const currentDate: Date = new Date();
// Optionally adjust the date
if (y !== 0) currentDate.setFullYear(currentDate.getFullYear() + y);
if (m !== 0) currentDate.setMonth(currentDate.getMonth() + m);
if (d !== 0) currentDate.setDate(currentDate.getDate() + d);
// Get the Unix timestamp (in seconds)
const timestamp: number = Math.floor(currentDate.getTime() / 1000);
// Convert the timestamp to 4 bytes
const bytes: number[] = [
(timestamp >> 24) & 0xff,
(timestamp >> 16) & 0xff,
(timestamp >> 8) & 0xff,
timestamp & 0xff,
];
return bytes;
}
export function getCurrentDateYYMMDD(dayDiff: number = 0): number[] {
const date = new Date();
date.setDate(date.getDate() + dayDiff); // Adjust the date by the dayDiff

View File

@@ -115,8 +115,8 @@ library CircuitAttributeHandlerV2 {
} else if (attestationId == AttestationId.KYC) {
return
FieldPositions({
issuingStateStart: 999,
issuingStateEnd: 999,
issuingStateStart: 195,
issuingStateEnd: 294,
nameStart: 78,
nameEnd: 141,
documentNumberStart: 30,

View File

@@ -11,6 +11,7 @@ import {IIdentityRegistryV1} from "../interfaces/IIdentityRegistryV1.sol";
import {IIdentityRegistryIdCardV1} from "../interfaces/IIdentityRegistryIdCardV1.sol";
import {IIdentityRegistryAadhaarV1} from "../interfaces/IIdentityRegistryAadhaarV1.sol";
import {IIdentityRegistryKycV1} from "../interfaces/IIdentityRegistryKycV1.sol";
import {console} from "hardhat/console.sol";
/**
* @title RegisterProofVerifierLib

View File

@@ -2,8 +2,8 @@ import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
import hre from "hardhat";
import { readFileSync } from "fs";
import path from "path";
import { getCscaTreeRoot } from "@selfxyz/common/utils/trees";
import serialized_csca_tree from "../../../../common/pubkeys/serialized_csca_tree.json";
import { getCscaTreeRoot } from "@selfxyz/new-common/src/trees/proof";
import serialized_csca_tree from "@selfxyz/new-common/src/data/serialized_csca_tree.json";
module.exports = buildModule("UpdateRegistryCscaRoot", (m) => {
const networkName = hre.network.config.chainId;

View File

@@ -45,12 +45,9 @@
"set:verifiers:v2": "npx dotenv-cli -- bash -c 'NETWORK=${NETWORK} npx tsx scripts/setVerifiersV2.ts'",
"show:registry": "npx tsx scripts/showRegistryAddresses.ts",
"test": "yarn hardhat test",
"test:airdrop": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/example/airdrop.test.ts'",
"test:attribute": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} npx hardhat test test/unit/CircuitAttributeHandler.test.ts'",
"test:coverage": "yarn hardhat coverage",
"test:coverage:local": "TEST_ENV=local yarn hardhat coverage",
"test:disclose": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} yarn hardhat test test/integration/vcAndDisclose.test.ts'",
"test:endtoend": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} yarn hardhat test test/Integration/endToEnd.test.ts'",
"test:example": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} yarn hardhat test test/example/*'",
"test:formatter": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} yarn hardhat test test/unit/formatter.test.ts'",
"test:hub": "npx dotenv-cli -- bash -c 'TEST_ENV=${TEST_ENV:-local} yarn hardhat test test/unit/IdentityVerificationHub.test.ts'",
@@ -86,7 +83,7 @@
"@safe-global/api-kit": "^4.0.1",
"@safe-global/protocol-kit": "^6.1.2",
"@safe-global/safe-core-sdk-types": "^5.1.0",
"@selfxyz/common": "workspace:^",
"@selfxyz/new-common": "workspace:^",
"@zk-kit/baby-jubjub": "^1.0.3",
"@zk-kit/imt": "^2.0.0-beta.4",
"@zk-kit/imt.sol": "^2.0.0-beta.12",
@@ -101,6 +98,7 @@
"snarkjs": "^0.7.4"
},
"devDependencies": {
"@anon-aadhaar/core": "npm:@selfxyz/anon-aadhaar-core@^0.0.1",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.6",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@nomicfoundation/hardhat-ignition": "^0.15.12",

View File

@@ -2,7 +2,7 @@ import { ethers } from "ethers";
import * as dotenv from "dotenv";
import * as fs from "fs";
import * as path from "path";
import { RegisterVerifierId, DscVerifierId } from "../../common/src/constants/constants";
import { RegisterVerifierId, DscVerifierId } from "@selfxyz/new-common/src/foundation/constants/identity";
dotenv.config();

View File

@@ -1,6 +1,6 @@
import { ethers } from "ethers";
import * as dotenv from "dotenv";
import { RegisterVerifierId, DscVerifierId } from "@selfxyz/common";
import { RegisterVerifierId, DscVerifierId } from "@selfxyz/new-common/src/foundation/constants/identity";
import {
getContractAbi,
getDeployedAddresses,

View File

@@ -1,757 +0,0 @@
import { expect } from "chai";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { ethers } from "hardhat";
import { CIRCUIT_CONSTANTS } from "@selfxyz/common/constants/constants";
import { ATTESTATION_ID } from "../utils/constants";
import { generateVcAndDiscloseProof } from "../utils/generateProof";
import { poseidon2 } from "poseidon-lite";
import { generateCommitment } from "@selfxyz/common/utils/passports/passport";
import { generateRandomFieldElement, splitHexFromBack } from "../utils/utils";
import BalanceTree from "../utils/example/balance-tree";
import { formatCountriesList, reverseBytes } from "@selfxyz/common/utils/circuits/formatInputs";
import { Formatter } from "../utils/formatter";
import { hashEndpointWithScope } from "@selfxyz/common/utils/scope";
import { createHash } from "crypto";
// Helper function to calculate user identifier hash
function calculateUserIdentifierHash(userContextData: string): string {
const sha256Hash = createHash("sha256")
.update(Buffer.from(userContextData.slice(2), "hex"))
.digest();
const ripemdHash = createHash("ripemd160").update(sha256Hash).digest();
return "0x" + ripemdHash.toString("hex").padStart(40, "0");
}
// Helper function to create V2 proof format
function createV2ProofData(proof: any, userAddress: string, userData: string = "airdrop-user-data") {
const destChainId = ethers.zeroPadValue(ethers.toBeHex(31337), 32);
const userContextData = ethers.solidityPacked(
["bytes32", "bytes32", "bytes"],
[destChainId, ethers.zeroPadValue(userAddress, 32), ethers.toUtf8Bytes(userData)],
);
const attestationId = ethers.zeroPadValue(ethers.toBeHex(BigInt(ATTESTATION_ID.E_PASSPORT)), 32);
const encodedProof = ethers.AbiCoder.defaultAbiCoder().encode(
["tuple(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[] pubSignals)"],
[[proof.a, proof.b, proof.c, proof.pubSignals]],
);
const proofData = ethers.solidityPacked(["bytes32", "bytes"], [attestationId, encodedProof]);
return { proofData, userContextData };
}
describe("Airdrop", () => {
let deployedActors: DeployedActorsV2;
let snapshotId: string;
let airdrop: any;
let token: any;
let baseVcAndDiscloseProof: any;
let vcAndDiscloseProof: any;
let registerSecret: any;
let imt: any;
let commitment: any;
let nullifier: any;
let forbiddenCountriesList: any;
let countriesListPacked: any;
let attestationIds: any[];
let userIdentifierBigInt: bigint;
let numericScope: string;
before(async () => {
deployedActors = await deploySystemFixturesV2();
// must be imported dynamic since @openpassport/zk-kit-lean-imt is exclusively esm and hardhat does not support esm with typescript until verison 3
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
registerSecret = generateRandomFieldElement();
nullifier = generateRandomFieldElement();
attestationIds = [BigInt(ATTESTATION_ID.E_PASSPORT)];
commitment = generateCommitment(registerSecret, ATTESTATION_ID.E_PASSPORT, deployedActors.mockPassport);
forbiddenCountriesList = ["AAA", "ABC", "CBA"];
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
imt = new LeanIMT<bigint>(hashFunction);
await imt.insert(BigInt(commitment));
// Proof generation will happen after airdrop deployment
const tokenFactory = await ethers.getContractFactory("AirdropToken");
token = await tokenFactory.connect(deployedActors.owner).deploy();
await token.waitForDeployment();
await deployedActors.registry
.connect(deployedActors.owner)
.devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
countriesListPacked = splitHexFromBack(
reverseBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList)))),
);
// Deploy PoseidonT3 contract for proper scope calculation
const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3");
const poseidonT3 = await PoseidonT3Factory.deploy();
await poseidonT3.waitForDeployment();
const poseidonT3Address = await poseidonT3.getAddress();
// Deploy TestAirdrop contract (which allows setting PoseidonT3 address)
const airdropFactory = await ethers.getContractFactory("TestAirdrop");
airdrop = await airdropFactory
.connect(deployedActors.owner)
.deploy(deployedActors.hub.target, "test-scope", token.target);
await airdrop.waitForDeployment();
// Set the proper scope using the deployed PoseidonT3
await airdrop.testGenerateScope(poseidonT3Address, "test-scope");
// Get the actual scope from the airdrop contract (now properly calculated)
const contractScope = await airdrop.scope();
numericScope = contractScope.toString();
const airdropAddress = await airdrop.getAddress();
console.log(`🏠 TestAirdrop deployed at: ${airdropAddress}`);
console.log(`🔢 PoseidonT3 deployed at: ${poseidonT3Address}`);
console.log(`✅ Proper scope (calculated with PoseidonT3): ${numericScope}`);
// The airdrop now uses the proper calculated scope
// Calculate the proper user identifier
const destChainId = ethers.zeroPadValue(ethers.toBeHex(31337), 32);
const user1Address = await deployedActors.user1.getAddress();
const userData = ethers.toUtf8Bytes("airdrop-user-data");
const tempUserContextData = ethers.solidityPacked(
["bytes32", "bytes32", "bytes"],
[destChainId, ethers.zeroPadValue(user1Address, 32), userData],
);
const userIdentifierHash = calculateUserIdentifierHash(tempUserContextData);
userIdentifierBigInt = BigInt(userIdentifierHash);
baseVcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
deployedActors.mockPassport,
numericScope,
new Array(88).fill("1"),
"1",
imt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
"0x" + userIdentifierBigInt.toString(16).padStart(64, "0"),
);
vcAndDiscloseProof = baseVcAndDiscloseProof;
// Set up verification config in the hub
const verificationConfigV2 = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: countriesListPacked as [any, any, any, any],
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
};
// Register the config in the hub and get the config ID
const configId = await deployedActors.hub
.connect(deployedActors.owner)
.setVerificationConfigV2(verificationConfigV2);
const receipt = await configId.wait();
// Extract the actual config ID from the transaction receipt
const actualConfigId = receipt!.logs[0].topics[1]; // The configId is the first indexed parameter
// Set the config ID in the airdrop contract
await airdrop.connect(deployedActors.owner).setConfigId(actualConfigId);
const mintAmount = ethers.parseEther("424242424242");
await token.mint(airdrop.target, mintAmount);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
beforeEach(async () => {
vcAndDiscloseProof = structuredClone(baseVcAndDiscloseProof);
});
afterEach(async () => {
await ethers.provider.send("evm_revert", [snapshotId]);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
it("should able to open registration by owner", async () => {
const { owner } = deployedActors;
const tx = await airdrop.connect(owner).openRegistration();
const receipt = await tx.wait();
const event = receipt?.logs.find(
(log: any) => log.topics[0] === airdrop.interface.getEvent("RegistrationOpen").topicHash,
);
expect(event).to.not.be.null;
expect(await airdrop.isRegistrationOpen()).to.be.true;
});
it("should not able to open registration by non-owner", async () => {
const { user1 } = deployedActors;
await expect(airdrop.connect(user1).openRegistration())
.to.be.revertedWithCustomError(airdrop, "OwnableUnauthorizedAccount")
.withArgs(await user1.getAddress());
});
it("should able to close registration by owner", async () => {
const { owner } = deployedActors;
await airdrop.connect(owner).openRegistration();
const tx = await airdrop.connect(owner).closeRegistration();
const receipt = await tx.wait();
const event = receipt?.logs.find(
(log: any) => log.topics[0] === airdrop.interface.getEvent("RegistrationClose").topicHash,
);
expect(event).to.not.be.null;
expect(await airdrop.isRegistrationOpen()).to.be.false;
});
it("should not able to close registration by non-owner", async () => {
const { user1 } = deployedActors;
await expect(airdrop.connect(user1).closeRegistration())
.to.be.revertedWithCustomError(airdrop, "OwnableUnauthorizedAccount")
.withArgs(await user1.getAddress());
});
it("should able to open claim by owner", async () => {
const { owner } = deployedActors;
const tx = await airdrop.connect(owner).openClaim();
const receipt = await tx.wait();
const event = receipt?.logs.find((log: any) => log.topics[0] === airdrop.interface.getEvent("ClaimOpen").topicHash);
expect(event).to.not.be.null;
expect(await airdrop.isClaimOpen()).to.be.true;
});
it("should not able to open claim by non-owner", async () => {
const { user1 } = deployedActors;
await expect(airdrop.connect(user1).openClaim())
.to.be.revertedWithCustomError(airdrop, "OwnableUnauthorizedAccount")
.withArgs(await user1.getAddress());
});
it("should able to close claim by owner", async () => {
const { owner } = deployedActors;
await airdrop.connect(owner).openClaim();
const tx = await airdrop.connect(owner).closeClaim();
const receipt = await tx.wait();
const event = receipt?.logs.find(
(log: any) => log.topics[0] === airdrop.interface.getEvent("ClaimClose").topicHash,
);
expect(event).to.not.be.null;
expect(await airdrop.isClaimOpen()).to.be.false;
});
it("should not able to close claim by owner", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).openClaim();
await expect(airdrop.connect(user1).closeClaim()).to.be.revertedWithCustomError(
airdrop,
"OwnableUnauthorizedAccount",
);
});
it("should able to set merkle root by owner", async () => {
const { owner } = deployedActors;
const merkleRoot = generateRandomFieldElement();
await airdrop.connect(owner).setMerkleRoot(merkleRoot);
expect(await airdrop.merkleRoot()).to.be.equal(merkleRoot);
});
it("should not able to set merkle root by non-owner", async () => {
const { user1 } = deployedActors;
const merkleRoot = generateRandomFieldElement();
await expect(airdrop.connect(user1).setMerkleRoot(merkleRoot))
.to.be.revertedWithCustomError(airdrop, "OwnableUnauthorizedAccount")
.withArgs(await user1.getAddress());
});
it("should able to register address by user", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).openRegistration();
// Create V2 proof format
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
const tx = await airdrop.connect(user1).verifySelfProof(proofData, userContextData);
const receipt = await tx.wait();
const event = receipt?.logs.find(
(log: any) => log.topics[0] === airdrop.interface.getEvent("UserIdentifierRegistered").topicHash,
);
const eventArgs = event
? airdrop.interface.decodeEventLog("UserIdentifierRegistered", event.data, event.topics)
: null;
expect(eventArgs?.registeredUserIdentifier).to.be.equal(await user1.getAddress());
const appNullifier = vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NULLIFIER_INDEX];
expect(eventArgs?.nullifier).to.be.equal(appNullifier);
});
it("should not able to register address by user if registration is closed", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).closeRegistration();
// Create V2 proof format
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
await expect(airdrop.connect(user1).verifySelfProof(proofData, userContextData)).to.be.revertedWithCustomError(
airdrop,
"RegistrationNotOpen",
);
});
it("should not able to register address by user if scope is invalid", async () => {
const { owner, user1 } = deployedActors;
// Now that we have proper scope calculation, we can create a proof with a genuinely different scope
const airdropAddress = await airdrop.getAddress();
const differentScope = hashEndpointWithScope(airdropAddress.toLowerCase(), "different-test-scope");
console.log(`TestAirdrop scope: ${numericScope}`);
console.log(`Different scope for test: ${differentScope}`);
// Generate proof with the different scope
const invalidVcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
deployedActors.mockPassport,
differentScope, // Use different scope
new Array(88).fill("1"),
"1",
imt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
"0x" + userIdentifierBigInt.toString(16).padStart(64, "0"),
);
await airdrop.connect(owner).openRegistration();
// Create V2 proof format with invalid proof (different scope)
const { proofData, userContextData } = createV2ProofData(invalidVcAndDiscloseProof, await user1.getAddress());
await expect(airdrop.connect(user1).verifySelfProof(proofData, userContextData)).to.be.revertedWithCustomError(
deployedActors.hub,
"ScopeMismatch",
);
});
it("should not able to register address by user if nullifier is already registered", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).openRegistration();
// Create V2 proof format
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
// First registration should succeed
await airdrop.connect(user1).verifySelfProof(proofData, userContextData);
// Second registration with same nullifier should fail
await expect(airdrop.connect(user1).verifySelfProof(proofData, userContextData)).to.be.revertedWithCustomError(
airdrop,
"RegisteredNullifier",
);
});
it("should not able to register address by user if attestation id is invalid", async () => {
const { registry, owner, user1 } = deployedActors;
const invalidCommitment = generateCommitment(
registerSecret,
ATTESTATION_ID.INVALID_ATTESTATION_ID,
deployedActors.mockPassport,
);
await registry
.connect(owner)
.devAddIdentityCommitment(ATTESTATION_ID.INVALID_ATTESTATION_ID, nullifier, invalidCommitment);
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
// must be imported dynamic since @openpassport/zk-kit-lean-imt is exclusively esm and hardhat does not support esm with typescript until verison 3
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
const invalidImt = new LeanIMT<bigint>(hashFunction);
await invalidImt.insert(BigInt(commitment));
await invalidImt.insert(BigInt(invalidCommitment));
const invalidVcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.INVALID_ATTESTATION_ID).toString(),
deployedActors.mockPassport,
numericScope, // Use the same scope as airdrop (proper calculated scope)
new Array(88).fill("1"),
"1",
invalidImt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
"0x" + userIdentifierBigInt.toString(16).padStart(64, "0"),
);
await airdrop.connect(owner).openRegistration();
// Create V2 proof format with invalid attestation ID
const { proofData, userContextData } = createV2ProofData(invalidVcAndDiscloseProof, await user1.getAddress());
await expect(airdrop.connect(user1).verifySelfProof(proofData, userContextData)).to.be.revertedWithCustomError(
deployedActors.hub,
"AttestationIdMismatch",
);
});
it("should revert with InvalidUserIdentifier when user identifier is 0", async () => {
const { owner, user1 } = deployedActors;
// Generate proof with zero user identifier
const invalidVcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
deployedActors.mockPassport,
numericScope, // Use the same scope as airdrop (proper calculated scope)
new Array(88).fill("1"),
"1",
imt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
"0x0000000000000000000000000000000000000000000000000000000000000000", // Zero user identifier
);
await airdrop.connect(owner).openRegistration();
// Create V2 proof format with zero user identifier proof
const { proofData, userContextData } = createV2ProofData(invalidVcAndDiscloseProof, await user1.getAddress());
await expect(airdrop.connect(user1).verifySelfProof(proofData, userContextData)).to.be.revertedWithCustomError(
deployedActors.hub,
"InvalidUserIdentifierInProof",
);
});
it("should allow registration when targetRootTimestamp is 0", async () => {
const { hub, registry, owner, user1 } = deployedActors;
// Deploy a new TestAirdrop with different scopeSeed
const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3");
const newPoseidonT3 = await PoseidonT3Factory.deploy();
await newPoseidonT3.waitForDeployment();
const newPoseidonT3Address = await newPoseidonT3.getAddress();
const airdropFactory = await ethers.getContractFactory("TestAirdrop");
const newAirdrop = await airdropFactory.connect(owner).deploy(hub.target, "test-scope-2", token.target);
await newAirdrop.waitForDeployment();
// Set the proper scope for the new airdrop using the deployed PoseidonT3
await newAirdrop.testGenerateScope(newPoseidonT3Address, "test-scope-2");
// Set up verification config for the new airdrop (same as main airdrop)
const verificationConfigV2 = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: countriesListPacked as [any, any, any, any],
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
};
// Register the config in the hub and get the config ID
const configTx = await deployedActors.hub.connect(owner).setVerificationConfigV2(verificationConfigV2);
const configReceipt = await configTx.wait();
// Extract the actual config ID from the transaction receipt
const actualConfigId = configReceipt!.logs[0].topics[1]; // The configId is the first indexed parameter
// Set the config ID in the new airdrop contract
await newAirdrop.connect(owner).setConfigId(actualConfigId);
await newAirdrop.connect(owner).openRegistration();
// Get the actual scope from the new airdrop contract
const newAirdropScope = await newAirdrop.scope();
const newAirdropScopeAsBigIntString = newAirdropScope.toString();
// Calculate user identifier for the new airdrop context
const destChainId = ethers.zeroPadValue(ethers.toBeHex(31337), 32);
const user1Address = await user1.getAddress();
const userData = ethers.toUtf8Bytes("airdrop-user-data");
const tempUserContextData = ethers.solidityPacked(
["bytes32", "bytes32", "bytes"],
[destChainId, ethers.zeroPadValue(user1Address, 32), userData],
);
const userIdentifierHash = calculateUserIdentifierHash(tempUserContextData);
const newUserIdentifierBigInt = BigInt(userIdentifierHash);
// Generate proof with the new airdrop's scope
const newVcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
deployedActors.mockPassport,
newAirdropScopeAsBigIntString, // Use the actual scope from the new contract
new Array(88).fill("1"),
"1",
imt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
"0x" + newUserIdentifierBigInt.toString(16).padStart(64, "0"), // Use proper user identifier
);
// Create V2 proof format for the new airdrop
const { proofData, userContextData } = createV2ProofData(newVcAndDiscloseProof, await user1.getAddress());
await expect(newAirdrop.connect(user1).verifySelfProof(proofData, userContextData)).to.not.be.reverted;
});
it("should return correct scope", async () => {
const scope = await airdrop.scope();
// With TestAirdrop and deployed PoseidonT3, we now get the proper calculated scope
expect(scope).to.not.equal(0n);
// Verify that our test setup correctly uses the contract's actual scope
expect(numericScope).to.equal(scope.toString());
// Calculate what the scope would be using hashEndpointWithScope for comparison
const airdropAddress = await airdrop.getAddress();
const expectedScope = hashEndpointWithScope(airdropAddress.toLowerCase(), "test-scope");
// The contract-calculated scope should match the expected scope
expect(scope.toString()).to.equal(expectedScope);
// Also compare with TestSelfVerificationRoot which should have the same scope calculation method
const testRootScope = await deployedActors.testSelfVerificationRoot.scope();
expect(testRootScope).to.not.equal(0n);
console.log(`✅ TestAirdrop scope (with PoseidonT3): ${scope}`);
console.log(`✅ Test scope variable: ${numericScope}`);
console.log(`🔍 TestSelfVerificationRoot scope: ${testRootScope}`);
console.log(`🌐 Expected scope (hashEndpointWithScope): ${expectedScope}`);
console.log(`🎯 All scopes match: ${scope.toString() === expectedScope}`);
});
it("should return correct merkle root", async () => {
const { owner } = deployedActors;
const merkleRoot = generateRandomFieldElement();
await airdrop.connect(owner).setMerkleRoot(merkleRoot);
const storedRoot = await airdrop.merkleRoot();
expect(storedRoot).to.equal(merkleRoot);
});
it("should return correct token address", async () => {
const tokenAddress = await airdrop.token();
expect(tokenAddress).to.equal(token.target);
});
it("should able to claim token by user", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).openRegistration();
// Register the user first using V2 interface
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
await airdrop.connect(user1).verifySelfProof(proofData, userContextData);
await airdrop.connect(owner).closeRegistration();
const tree = new BalanceTree([{ account: await user1.getAddress(), amount: BigInt(1000000000000000000) }]);
const root = tree.getHexRoot();
await airdrop.connect(owner).setMerkleRoot(root);
await airdrop.connect(owner).openClaim();
const merkleProof = tree.getProof(0, await user1.getAddress(), BigInt(1000000000000000000));
const tx = await airdrop.connect(user1).claim(0, BigInt(1000000000000000000), merkleProof);
const receipt = await tx.wait();
const event = receipt?.logs.find((log: any) => log.topics[0] === airdrop.interface.getEvent("Claimed").topicHash);
const eventArgs = event ? airdrop.interface.decodeEventLog("Claimed", event.data, event.topics) : null;
expect(eventArgs?.index).to.equal(0);
expect(eventArgs?.amount).to.equal(BigInt(1000000000000000000));
expect(eventArgs?.account).to.equal(await user1.getAddress());
const balance = await token.balanceOf(await user1.getAddress());
expect(balance).to.equal(BigInt(1000000000000000000));
const isClaimed = await airdrop.claimed(await user1.getAddress());
expect(isClaimed).to.be.true;
});
it("should not able to claim token by user if registration is not closed", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).openRegistration();
// Register the user first using V2 interface
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
await airdrop.connect(user1).verifySelfProof(proofData, userContextData);
const tree = new BalanceTree([{ account: await user1.getAddress(), amount: BigInt(1000000000000000000) }]);
const root = tree.getHexRoot();
await airdrop.connect(owner).setMerkleRoot(root);
await airdrop.connect(owner).openClaim();
const merkleProof = tree.getProof(0, await user1.getAddress(), BigInt(1000000000000000000));
await expect(
airdrop.connect(user1).claim(0, BigInt(1000000000000000000), merkleProof),
).to.be.revertedWithCustomError(airdrop, "RegistrationNotClosed");
const isClaimed = await airdrop.claimed(await user1.getAddress());
expect(isClaimed).to.be.false;
});
it("should not able to claim token by user if claim is not open", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).openRegistration();
// Register the user first using V2 interface
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
await airdrop.connect(user1).verifySelfProof(proofData, userContextData);
await airdrop.connect(owner).closeRegistration();
const tree = new BalanceTree([{ account: await user1.getAddress(), amount: BigInt(1000000000000000000) }]);
const root = tree.getHexRoot();
await airdrop.connect(owner).setMerkleRoot(root);
const merkleProof = tree.getProof(0, await user1.getAddress(), BigInt(1000000000000000000));
await expect(
airdrop.connect(user1).claim(0, BigInt(1000000000000000000), merkleProof),
).to.be.revertedWithCustomError(airdrop, "ClaimNotOpen");
const isClaimed = await airdrop.claimed(await user1.getAddress());
expect(isClaimed).to.be.false;
});
it("should not able to claim token by user if user has already claimed", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).openRegistration();
// Register the user first using V2 interface
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
await airdrop.connect(user1).verifySelfProof(proofData, userContextData);
await airdrop.connect(owner).closeRegistration();
const tree = new BalanceTree([{ account: await user1.getAddress(), amount: BigInt(1000000000000000000) }]);
const root = tree.getHexRoot();
await airdrop.connect(owner).setMerkleRoot(root);
await airdrop.connect(owner).openClaim();
const merkleProof = tree.getProof(0, await user1.getAddress(), BigInt(1000000000000000000));
await airdrop.connect(user1).claim(0, BigInt(1000000000000000000), merkleProof);
await expect(
airdrop.connect(user1).claim(0, BigInt(1000000000000000000), merkleProof),
).to.be.revertedWithCustomError(airdrop, "AlreadyClaimed");
const balance = await token.balanceOf(await user1.getAddress());
expect(balance).to.equal(BigInt(1000000000000000000));
const isClaimed = await airdrop.claimed(await user1.getAddress());
expect(isClaimed).to.be.true;
});
it("should not able to claim token by user if merkle proof is invalid", async () => {
const { owner, user1 } = deployedActors;
await airdrop.connect(owner).openRegistration();
// Register the user first using V2 interface
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
await airdrop.connect(user1).verifySelfProof(proofData, userContextData);
await airdrop.connect(owner).closeRegistration();
const tree = new BalanceTree([{ account: await user1.getAddress(), amount: BigInt(1000000000000000000) }]);
const root = tree.getHexRoot();
await airdrop.connect(owner).setMerkleRoot(root);
await airdrop.connect(owner).openClaim();
const merkleProof = tree.getProof(0, await user1.getAddress(), BigInt(1000000000000000000));
merkleProof[0] = generateRandomFieldElement().toString();
await expect(
airdrop.connect(user1).claim(0, BigInt(1000000000000000000), merkleProof),
).to.be.revertedWithCustomError(airdrop, "InvalidProof");
const isClaimed = await airdrop.claimed(await user1.getAddress());
expect(isClaimed).to.be.false;
});
it("should not able to claim token by user if user is not registered", async () => {
const { owner, user1, user2 } = deployedActors;
await airdrop.connect(owner).openRegistration();
// Register only user1, not user2
const { proofData, userContextData } = createV2ProofData(vcAndDiscloseProof, await user1.getAddress());
await airdrop.connect(user1).verifySelfProof(proofData, userContextData);
await airdrop.connect(owner).closeRegistration();
const tree = new BalanceTree([
{ account: await user1.getAddress(), amount: BigInt(1000000000000000000) },
{ account: await user2.getAddress(), amount: BigInt(1000000000000000000) },
]);
const root = tree.getHexRoot();
await airdrop.connect(owner).setMerkleRoot(root);
await airdrop.connect(owner).openClaim();
const merkleProof = tree.getProof(1, await user2.getAddress(), BigInt(1000000000000000000));
await expect(airdrop.connect(user2).claim(1, BigInt(1000000000000000000), merkleProof))
.to.be.revertedWithCustomError(airdrop, "NotRegistered")
.withArgs(await user2.getAddress());
const isClaimed = await airdrop.claimed(await user2.getAddress());
expect(isClaimed).to.be.false;
});
it("should able to set config ID by owner", async () => {
const { owner } = deployedActors;
const newConfigId = ethers.keccak256(ethers.toUtf8Bytes("new-config-v1"));
await airdrop.connect(owner).setConfigId(newConfigId);
const storedConfigId = await airdrop.verificationConfigId();
expect(storedConfigId).to.equal(newConfigId);
});
it("should not able to set config ID by non-owner", async () => {
const { user1 } = deployedActors;
const newConfigId = ethers.keccak256(ethers.toUtf8Bytes("new-config-v1"));
await expect(airdrop.connect(user1).setConfigId(newConfigId))
.to.be.revertedWithCustomError(airdrop, "OwnableUnauthorizedAccount")
.withArgs(await user1.getAddress());
});
});

View File

@@ -1,410 +0,0 @@
import { expect } from "chai";
import { TransactionReceipt, ZeroAddress } from "ethers";
import { ethers } from "hardhat";
import { poseidon2 } from "poseidon-lite";
import { CIRCUIT_CONSTANTS, DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants";
import { ATTESTATION_ID } from "../utils/constants";
import { deploySystemFixtures } from "../utils/deployment";
import { generateDscProof, generateRegisterProof } from "../utils/generateProof";
import serialized_dsc_tree from "../../../common/pubkeys/serialized_dsc_tree.json";
import { DeployedActors } from "../utils/types";
import { generateRandomFieldElement } from "../utils/utils";
describe("Commitment Registration Tests", function () {
this.timeout(0);
let deployedActors: DeployedActors;
let snapshotId: string;
let baseDscProof: any;
let baseRegisterProof: any;
let dscProof: any;
let registerProof: any;
let registerSecret: any;
before(async () => {
deployedActors = await deploySystemFixtures();
registerSecret = generateRandomFieldElement();
baseDscProof = await generateDscProof(deployedActors.mockPassport);
baseRegisterProof = await generateRegisterProof(registerSecret, deployedActors.mockPassport);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
beforeEach(async () => {
dscProof = structuredClone(baseDscProof);
registerProof = structuredClone(baseRegisterProof);
});
afterEach(async () => {
await ethers.provider.send("evm_revert", [snapshotId]);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
describe("Register Commitment", () => {
describe("Initialization", () => {
it("should have consistent addresses between registry and hub", async () => {
const { hub, registry } = deployedActors;
expect(await registry.hub()).to.equal(hub.target);
expect(await hub.registry()).to.equal(registry.target);
});
});
describe("Register DSC Pubkey", async () => {
it("Should register DSC key commitment successfully", async () => {
const { hub, registry } = deployedActors;
const previousRoot = await registry.getDscKeyCommitmentMerkleRoot();
const previousSize = await registry.getDscKeyCommitmentTreeSize();
const tx = await hub.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof);
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
// must be imported dynamic since @openpassport/zk-kit-lean-imt is exclusively esm and hardhat does not support esm with typescript until verison 3
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
const imt = new LeanIMT<bigint>(hashFunction);
await imt.insert(BigInt(dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]));
const receipt = (await tx.wait()) as TransactionReceipt;
const event = receipt?.logs.find(
(log) => log.topics[0] === registry.interface.getEvent("DscKeyCommitmentRegistered").topicHash,
);
const eventArgs = event
? registry.interface.decodeEventLog("DscKeyCommitmentRegistered", event.data, event.topics)
: null;
const blockTimestamp = (await ethers.provider.getBlock(receipt.blockNumber))!.timestamp;
const currentRoot = await registry.getDscKeyCommitmentMerkleRoot();
const index = await registry.getDscKeyCommitmentIndex(
dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX],
);
expect(eventArgs?.commitment).to.equal(dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]);
expect(eventArgs?.timestamp).to.equal(blockTimestamp);
expect(eventArgs?.imtRoot).to.equal(currentRoot);
expect(eventArgs?.imtIndex).to.equal(index);
// Check state
expect(currentRoot).to.not.equal(previousRoot);
expect(currentRoot).to.be.equal(imt.root);
expect(await registry.getDscKeyCommitmentTreeSize()).to.equal(previousSize + 1n);
expect(
await registry.getDscKeyCommitmentIndex(dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]),
).to.equal(index);
expect(
await registry.isRegisteredDscKeyCommitment(dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]),
).to.equal(true);
});
it("Should fail when called by proxy address", async () => {
const { hubImpl } = deployedActors;
await expect(
hubImpl.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof),
).to.be.revertedWithCustomError(hubImpl, "UUPSUnauthorizedCallContext");
});
it("Should fail when the verifier is not set", async () => {
const { hub } = deployedActors;
await expect(
hub.registerDscKeyCommitment(DscVerifierId.dsc_sha1_rsa_65537_4096, dscProof),
).to.be.revertedWithCustomError(hub, "NO_VERIFIER_SET");
});
it("Should fail when the csca root is invalid", async () => {
const { hub } = deployedActors;
dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_CSCA_ROOT_INDEX] = generateRandomFieldElement();
await expect(
hub.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof),
).to.be.revertedWithCustomError(hub, "INVALID_CSCA_ROOT");
});
it("Should fail when the proof is invalid", async () => {
const { hub } = deployedActors;
dscProof.a[0] = generateRandomFieldElement();
await expect(
hub.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof),
).to.be.revertedWithCustomError(hub, "INVALID_DSC_PROOF");
});
it("Should fail when registerDscKeyCommitment is called directly on implementation", async () => {
const { registryImpl } = deployedActors;
await expect(registryImpl.registerDscKeyCommitment(generateRandomFieldElement())).to.be.revertedWithCustomError(
registryImpl,
"UUPSUnauthorizedCallContext",
);
});
it("Should fail when the registerDscKeyCommitment is called by non-hub address", async () => {
const { registry, vcAndDisclose, register, dsc, owner } = deployedActors;
const IdentityVerificationHubImplFactory = await ethers.getContractFactory(
"IdentityVerificationHubImplV1",
owner,
);
const hubImpl2 = await IdentityVerificationHubImplFactory.deploy();
await hubImpl2.waitForDeployment();
const initializeData = hubImpl2.interface.encodeFunctionData("initialize", [
registry.target,
vcAndDisclose.target,
[RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096],
[register.target],
[DscVerifierId.dsc_sha256_rsa_65537_4096],
[dsc.target],
]);
const hubFactory = await ethers.getContractFactory("IdentityVerificationHub", owner);
const hub2Proxy = await hubFactory.deploy(hubImpl2.target, initializeData);
await hub2Proxy.waitForDeployment();
const hub2 = await ethers.getContractAt("IdentityVerificationHubImplV1", hub2Proxy.target);
await expect(
hub2.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof),
).to.be.revertedWithCustomError(registry, "ONLY_HUB_CAN_ACCESS");
});
it("should fail registerDscKeyCommitment when hub address is not set", async () => {
const { hub, registry } = deployedActors;
await registry.updateHub(ZeroAddress);
await expect(
hub.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof),
).to.be.revertedWithCustomError(registry, "HUB_NOT_SET");
});
it("should fail when the dsc key commitment is already registered", async () => {
const { hub, registry } = deployedActors;
await hub.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof);
await expect(
hub.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof),
).to.be.revertedWithCustomError(registry, "REGISTERED_COMMITMENT");
});
it("should fail when getDscKeyCommitmentMerkleRoot is called by non-proxy", async () => {
const { registryImpl } = deployedActors;
await expect(registryImpl.getDscKeyCommitmentMerkleRoot()).to.be.revertedWithCustomError(
registryImpl,
"UUPSUnauthorizedCallContext",
);
});
it("should fail when checkDscKeyCommitmentMerkleRoot is called by non-proxy", async () => {
const { registryImpl } = deployedActors;
const root = generateRandomFieldElement();
await expect(registryImpl.checkDscKeyCommitmentMerkleRoot(root)).to.be.revertedWithCustomError(
registryImpl,
"UUPSUnauthorizedCallContext",
);
});
it("should fail when getDscKeyCommitmentTreeSize is called by non-proxy", async () => {
const { registryImpl } = deployedActors;
await expect(registryImpl.getDscKeyCommitmentTreeSize()).to.be.revertedWithCustomError(
registryImpl,
"UUPSUnauthorizedCallContext",
);
});
it("should fail when getDscKeyCommitmentIndex is called by non-proxy", async () => {
const { registryImpl } = deployedActors;
const commitment = generateRandomFieldElement();
await expect(registryImpl.getDscKeyCommitmentIndex(commitment)).to.be.revertedWithCustomError(
registryImpl,
"UUPSUnauthorizedCallContext",
);
});
it("should fail when registerDscKeyCommitment is called by non-proxy address", async () => {
const { hubImpl } = deployedActors;
await expect(
hubImpl.registerDscKeyCommitment(DscVerifierId.dsc_sha256_rsa_65537_4096, dscProof),
).to.be.revertedWithCustomError(hubImpl, "UUPSUnauthorizedCallContext");
});
});
describe("Register Passport Commitment", () => {
before(async () => {
const { registry } = deployedActors;
const dscKeys = JSON.parse(serialized_dsc_tree);
for (let i = 0; i < dscKeys[0].length; i++) {
await registry.devAddDscKeyCommitment(BigInt(dscKeys[0][i]));
}
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
afterEach(async () => {
await ethers.provider.send("evm_revert", [snapshotId]);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
it("should register passport commitment successfully", async () => {
const { hub, registry, mockPassport } = deployedActors;
const registerProof = await generateRegisterProof(registerSecret, mockPassport);
const previousRoot = await registry.getIdentityCommitmentMerkleRoot();
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
// must be imported dynamic since @openpassport/zk-kit-lean-imt is exclusively esm and hardhat does not support esm with typescript until verison 3
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
const imt = new LeanIMT<bigint>(hashFunction);
await imt.insert(BigInt(registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_COMMITMENT_INDEX]));
const tx = await hub.registerPassportCommitment(
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
);
const receipt = (await tx.wait()) as TransactionReceipt;
const blockTimestamp = (await ethers.provider.getBlock(receipt.blockNumber))!.timestamp;
const currentRoot = await registry.getIdentityCommitmentMerkleRoot();
const size = await registry.getIdentityCommitmentMerkleTreeSize();
const rootTimestamp = await registry.rootTimestamps(currentRoot);
const index = await registry.getIdentityCommitmentIndex(
registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_COMMITMENT_INDEX],
);
const nullifier = await registry.nullifiers(
ATTESTATION_ID.E_PASSPORT,
registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_NULLIFIER_INDEX],
);
const event = receipt?.logs.find(
(log) => log.topics[0] === registry.interface.getEvent("CommitmentRegistered").topicHash,
);
const eventArgs = event
? registry.interface.decodeEventLog("CommitmentRegistered", event.data, event.topics)
: null;
expect(eventArgs?.attestationId).to.equal(ATTESTATION_ID.E_PASSPORT);
expect(eventArgs?.nullifier).to.equal(registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_NULLIFIER_INDEX]);
expect(eventArgs?.commitment).to.equal(registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_COMMITMENT_INDEX]);
expect(eventArgs?.timestamp).to.equal(blockTimestamp);
expect(eventArgs?.imtRoot).to.equal(currentRoot);
expect(eventArgs?.imtIndex).to.equal(0);
expect(currentRoot).to.not.equal(previousRoot);
expect(currentRoot).to.be.equal(imt.root);
expect(size).to.equal(1);
expect(rootTimestamp).to.equal(blockTimestamp);
expect(index).to.equal(0);
expect(nullifier).to.equal(true);
});
it("should fail when verifier is not set", async () => {
const { hub } = deployedActors;
registerProof.a[0] = generateRandomFieldElement();
await expect(
hub.registerPassportCommitment(RegisterVerifierId.register_sha256_sha256_sha256_rsa_3_4096, registerProof),
).to.be.revertedWithCustomError(hub, "NO_VERIFIER_SET");
});
it("should fail when commitment root is invalid", async () => {
const { hub } = deployedActors;
const invalidCommitmentRoot = generateRandomFieldElement();
registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_MERKLE_ROOT_INDEX] = invalidCommitmentRoot;
await expect(
hub.registerPassportCommitment(
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
),
).to.be.revertedWithCustomError(hub, "INVALID_COMMITMENT_ROOT");
});
it("should fail when register proof verification fails", async () => {
const { hub } = deployedActors;
registerProof.a[0] = generateRandomFieldElement();
await expect(
hub.registerPassportCommitment(
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
),
).to.be.revertedWithCustomError(hub, "INVALID_REGISTER_PROOF");
});
it("should fail when nullifier is already used", async () => {
const { hub, registry, mockPassport } = deployedActors;
const registerProof = await generateRegisterProof(registerSecret, mockPassport);
await hub.registerPassportCommitment(
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
);
await expect(
hub.registerPassportCommitment(
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
),
).to.be.revertedWithCustomError(registry, "REGISTERED_COMMITMENT");
});
it("should fail when registerPassportCommitment is called by non-proxy address", async () => {
const { hubImpl } = deployedActors;
await expect(
hubImpl.registerPassportCommitment(
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
),
).to.be.revertedWithCustomError(hubImpl, "UUPSUnauthorizedCallContext");
});
it("should fail when registerCommitment is called by non-hub address", async () => {
const { registry, vcAndDisclose, register, dsc, owner } = deployedActors;
const IdentityVerificationHubImplFactory = await ethers.getContractFactory(
"IdentityVerificationHubImplV1",
owner,
);
const hubImpl2 = await IdentityVerificationHubImplFactory.deploy();
await hubImpl2.waitForDeployment();
const initializeData = hubImpl2.interface.encodeFunctionData("initialize", [
registry.target,
vcAndDisclose.target,
[RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096],
[register.target],
[DscVerifierId.dsc_sha256_rsa_65537_4096],
[dsc.target],
]);
const hubFactory = await ethers.getContractFactory("IdentityVerificationHub", owner);
const hub2Proxy = await hubFactory.deploy(hubImpl2.target, initializeData);
await hub2Proxy.waitForDeployment();
const hub2 = await ethers.getContractAt("IdentityVerificationHubImplV1", hub2Proxy.target);
await expect(
hub2.registerPassportCommitment(
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
),
).to.be.revertedWithCustomError(registry, "ONLY_HUB_CAN_ACCESS");
});
it("should fail registerCommitment when hub address is not set", async () => {
const { hub, registry } = deployedActors;
await registry.updateHub(ZeroAddress);
await expect(
hub.registerPassportCommitment(
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
),
).to.be.revertedWithCustomError(registry, "HUB_NOT_SET");
});
it("should fail when registerCommitment is called by non-proxy address", async () => {
const { registryImpl } = deployedActors;
const nullifier = generateRandomFieldElement();
const commitment = generateRandomFieldElement();
await expect(
registryImpl.registerCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment),
).to.be.revertedWithCustomError(registryImpl, "UUPSUnauthorizedCallContext");
});
});
});
});

View File

@@ -1,345 +0,0 @@
import { expect } from "chai";
import { BigNumberish, TransactionReceipt } from "ethers";
import { ethers } from "hardhat";
import { poseidon2 } from "poseidon-lite";
import { createHash } from "crypto";
import { CIRCUIT_CONSTANTS, DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants/constants";
import { formatCountriesList, reverseBytes } from "@selfxyz/common/utils/circuits/formatInputs";
import { castFromScope } from "@selfxyz/common/utils/circuits/uuid";
import { ATTESTATION_ID } from "../utils/constants";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import BalanceTree from "../utils/example/balance-tree";
import { Formatter } from "../utils/formatter";
import { generateDscProof, generateRegisterProof, generateVcAndDiscloseProof } from "../utils/generateProof";
import serialized_dsc_tree from "../../../common/pubkeys/serialized_dsc_tree.json";
import { DeployedActorsV2 } from "../utils/types";
import { generateRandomFieldElement, splitHexFromBack } from "../utils/utils";
// Helper function to calculate user identifier hash
function calculateUserIdentifierHash(userContextData: string): string {
const sha256Hash = createHash("sha256")
.update(Buffer.from(userContextData.slice(2), "hex"))
.digest();
const ripemdHash = createHash("ripemd160").update(sha256Hash).digest();
return "0x" + ripemdHash.toString("hex").padStart(40, "0");
}
// Helper function to create V2 proof data format for verifySelfProof
function createV2ProofData(proof: any, userAddress: string, userData: string = "airdrop-user-data") {
const destChainId = ethers.zeroPadValue(ethers.toBeHex(31337), 32);
const userContextData = ethers.solidityPacked(
["bytes32", "bytes32", "bytes"],
[destChainId, ethers.zeroPadValue(userAddress, 32), ethers.toUtf8Bytes(userData)],
);
const attestationId = ethers.zeroPadValue(ethers.toBeHex(BigInt(ATTESTATION_ID.E_PASSPORT)), 32);
const encodedProof = ethers.AbiCoder.defaultAbiCoder().encode(
["tuple(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[] pubSignals)"],
[[proof.a, proof.b, proof.c, proof.pubSignals]],
);
const proofData = ethers.solidityPacked(["bytes32", "bytes"], [attestationId, encodedProof]);
return { proofData, userContextData };
}
describe("End to End Tests", function () {
this.timeout(0);
let deployedActors: DeployedActorsV2;
let snapshotId: string;
before(async () => {
deployedActors = await deploySystemFixturesV2();
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
afterEach(async () => {
await ethers.provider.send("evm_revert", [snapshotId]);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
it("register dsc key commitment, register identity commitment, verify commitment and disclose attrs and claim airdrop", async () => {
const { hub, registry, mockPassport, owner, user1, testSelfVerificationRoot, poseidonT3 } = deployedActors;
// V2 hub requires attestationId as bytes32
const attestationIdBytes32 = ethers.zeroPadValue(ethers.toBeHex(BigInt(ATTESTATION_ID.E_PASSPORT)), 32);
// register dsc key
// To increase test performance, we will just set one dsc key with groth16 proof
// Other commitments are registered by dev function
const dscKeys = JSON.parse(serialized_dsc_tree);
let registerDscTx;
const dscProof = await generateDscProof(mockPassport);
const registerSecret = generateRandomFieldElement();
for (let i = 0; i < dscKeys[0].length; i++) {
if (BigInt(dscKeys[0][i]) == dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]) {
const previousRoot = await registry.getDscKeyCommitmentMerkleRoot();
const previousSize = await registry.getDscKeyCommitmentTreeSize();
registerDscTx = await hub.registerDscKeyCommitment(
attestationIdBytes32,
DscVerifierId.dsc_sha256_rsa_65537_4096,
dscProof,
);
const receipt = (await registerDscTx.wait()) as TransactionReceipt;
const event = receipt?.logs.find(
(log) => log.topics[0] === registry.interface.getEvent("DscKeyCommitmentRegistered").topicHash,
);
const eventArgs = event
? registry.interface.decodeEventLog("DscKeyCommitmentRegistered", event.data, event.topics)
: null;
const blockTimestamp = (await ethers.provider.getBlock(receipt.blockNumber))!.timestamp;
const currentRoot = await registry.getDscKeyCommitmentMerkleRoot();
const index = await registry.getDscKeyCommitmentIndex(
dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX],
);
expect(eventArgs?.commitment).to.equal(dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]);
expect(eventArgs?.timestamp).to.equal(blockTimestamp);
expect(eventArgs?.imtRoot).to.equal(currentRoot);
expect(eventArgs?.imtIndex).to.equal(index);
// Check state
expect(currentRoot).to.not.equal(previousRoot);
expect(await registry.getDscKeyCommitmentTreeSize()).to.equal(previousSize + 1n);
expect(
await registry.getDscKeyCommitmentIndex(dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]),
).to.equal(index);
expect(
await registry.isRegisteredDscKeyCommitment(dscProof.pubSignals[CIRCUIT_CONSTANTS.DSC_TREE_LEAF_INDEX]),
).to.equal(true);
} else {
await registry.devAddDscKeyCommitment(BigInt(dscKeys[0][i]));
}
}
// register identity commitment
const registerProof = await generateRegisterProof(registerSecret, mockPassport);
const previousRoot = await registry.getIdentityCommitmentMerkleRoot();
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
// must be imported dynamic since @openpassport/zk-kit-lean-imt is exclusively esm and hardhat does not support esm with typescript until verison 3
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
const imt = new LeanIMT<bigint>(hashFunction);
await imt.insert(BigInt(registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_COMMITMENT_INDEX]));
const tx = await hub.registerCommitment(
attestationIdBytes32,
RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096,
registerProof,
);
const receipt = (await tx.wait()) as TransactionReceipt;
const blockTimestamp = (await ethers.provider.getBlock(receipt.blockNumber))!.timestamp;
const currentRoot = await registry.getIdentityCommitmentMerkleRoot();
const size = await registry.getIdentityCommitmentMerkleTreeSize();
const rootTimestamp = await registry.rootTimestamps(currentRoot);
const index = await registry.getIdentityCommitmentIndex(
registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_COMMITMENT_INDEX],
);
const identityNullifier = await registry.nullifiers(
attestationIdBytes32,
registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_NULLIFIER_INDEX],
);
const event = receipt?.logs.find(
(log) => log.topics[0] === registry.interface.getEvent("CommitmentRegistered").topicHash,
);
const eventArgs = event
? registry.interface.decodeEventLog("CommitmentRegistered", event.data, event.topics)
: null;
expect(eventArgs?.attestationId).to.equal(ATTESTATION_ID.E_PASSPORT);
expect(eventArgs?.nullifier).to.equal(registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_NULLIFIER_INDEX]);
expect(eventArgs?.commitment).to.equal(registerProof.pubSignals[CIRCUIT_CONSTANTS.REGISTER_COMMITMENT_INDEX]);
expect(eventArgs?.timestamp).to.equal(blockTimestamp);
expect(eventArgs?.imtRoot).to.equal(currentRoot);
expect(eventArgs?.imtIndex).to.equal(0);
expect(currentRoot).to.not.equal(previousRoot);
expect(currentRoot).to.be.equal(imt.root);
expect(size).to.equal(1);
expect(rootTimestamp).to.equal(blockTimestamp);
expect(index).to.equal(0);
expect(identityNullifier).to.equal(true);
const forbiddenCountriesList = ["AAA", "ABC", "CBA"];
const countriesListPacked = splitHexFromBack(
reverseBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList)))),
);
// Get the scope from testSelfVerificationRoot
const testRootScope = await testSelfVerificationRoot.scope();
// Calculate user identifier hash for verification
const destChainId = ethers.zeroPadValue(ethers.toBeHex(31337), 32);
const user1Address = await user1.getAddress();
const userData = ethers.toUtf8Bytes("test-user-data");
const tempUserContextData = ethers.solidityPacked(
["bytes32", "bytes32", "bytes"],
[destChainId, ethers.zeroPadValue(user1Address, 32), userData],
);
const userIdentifierHash = calculateUserIdentifierHash(tempUserContextData);
// Generate proof for V2 verification
const vcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
mockPassport,
testRootScope.toString(),
new Array(88).fill("1"),
"1",
imt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
userIdentifierHash,
);
// Set up verification config for testSelfVerificationRoot
const verificationConfigV2 = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: countriesListPacked as [BigNumberish, BigNumberish, BigNumberish, BigNumberish],
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
};
await testSelfVerificationRoot.setVerificationConfig(verificationConfigV2);
// Create V2 proof format and verify via testSelfVerificationRoot
const { proofData, userContextData: verifyUserContextData } = createV2ProofData(
vcAndDiscloseProof,
user1Address,
"test-user-data",
);
// Reset test state before verification
await testSelfVerificationRoot.resetTestState();
// Verify the proof through V2 architecture
await testSelfVerificationRoot.connect(user1).verifySelfProof(proofData, verifyUserContextData);
// Check verification was successful
expect(await testSelfVerificationRoot.verificationSuccessful()).to.equal(true);
// Get the verification output and verify it
const lastOutput = await testSelfVerificationRoot.lastOutput();
expect(lastOutput).to.not.equal("0x");
// Verify attestationId matches both the expected bytes32 and the proof pubSignals
expect(lastOutput.attestationId).to.equal(attestationIdBytes32);
expect(lastOutput.attestationId).to.equal(
ethers.zeroPadValue(
ethers.toBeHex(vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX]),
32,
),
);
// Verify nullifier matches the proof pubSignals
expect(lastOutput.nullifier).to.equal(
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NULLIFIER_INDEX],
);
// Verify userIdentifier is set
expect(lastOutput.userIdentifier).to.not.equal(0n);
// Verify olderThan value
expect(lastOutput.olderThan).to.equal(20n);
const tokenFactory = await ethers.getContractFactory("AirdropToken");
const token = await tokenFactory.connect(owner).deploy();
await token.waitForDeployment();
const airdropFactory = await ethers.getContractFactory("Airdrop");
const airdrop = await airdropFactory.connect(owner).deploy(hub.target, "test-scope", token.target);
await airdrop.waitForDeployment();
// Set up verification config for the airdrop
const configTx = await hub.connect(owner).setVerificationConfigV2(verificationConfigV2);
const configReceipt = await configTx.wait();
const configId = configReceipt!.logs[0].topics[1];
// Set the config ID in the airdrop contract
await airdrop.connect(owner).setConfigId(configId);
await token.connect(owner).mint(airdrop.target, BigInt(1000000000000000000));
// Generate proof with the airdrop's actual scope
const airdropScope = await airdrop.scope();
// Calculate the user identifier hash for the airdrop proof
const airdropUserData = ethers.toUtf8Bytes("airdrop-user-data");
const airdropTempUserContextData = ethers.solidityPacked(
["bytes32", "bytes32", "bytes"],
[destChainId, ethers.zeroPadValue(user1Address, 32), airdropUserData],
);
const airdropUserIdentifierHash = calculateUserIdentifierHash(airdropTempUserContextData);
const airdropVcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
mockPassport,
airdropScope.toString(),
new Array(88).fill("1"),
"1",
imt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
airdropUserIdentifierHash,
);
await airdrop.connect(owner).openRegistration();
// Create V2 proof format for verifySelfProof
const { proofData: airdropProofData, userContextData: airdropUserContextData } = createV2ProofData(
airdropVcAndDiscloseProof,
await user1.getAddress(),
);
await airdrop.connect(user1).verifySelfProof(airdropProofData, airdropUserContextData);
await airdrop.connect(owner).closeRegistration();
const tree = new BalanceTree([{ account: await user1.getAddress(), amount: BigInt(1000000000000000000) }]);
const merkleRoot = tree.getHexRoot();
await airdrop.connect(owner).setMerkleRoot(merkleRoot);
await airdrop.connect(owner).openClaim();
const merkleProof = tree.getProof(0, await user1.getAddress(), BigInt(1000000000000000000));
const claimTx = await airdrop.connect(user1).claim(0, BigInt(1000000000000000000), merkleProof);
const claimReceipt = (await claimTx.wait()) as TransactionReceipt;
const claimEvent = claimReceipt?.logs.find(
(log) => log.topics[0] === airdrop.interface.getEvent("Claimed").topicHash,
);
const claimEventArgs = claimEvent
? airdrop.interface.decodeEventLog("Claimed", claimEvent.data, claimEvent.topics)
: null;
expect(claimEventArgs?.index).to.equal(0);
expect(claimEventArgs?.amount).to.equal(BigInt(1000000000000000000));
expect(claimEventArgs?.account).to.equal(await user1.getAddress());
const balance = await token.balanceOf(await user1.getAddress());
expect(balance).to.equal(BigInt(1000000000000000000));
const isClaimed = await airdrop.claimed(await user1.getAddress());
expect(isClaimed).to.be.true;
// Verify disclosed attributes from lastOutput
expect(lastOutput.issuingState).to.equal("FRA");
expect(lastOutput.idNumber).to.equal("15AA81234");
expect(lastOutput.nationality).to.equal("FRA");
expect(lastOutput.dateOfBirth).to.equal("31-01-94");
expect(lastOutput.gender).to.equal("M");
expect(lastOutput.expiryDate).to.equal("31-10-40");
expect(lastOutput.olderThan).to.equal(20n);
});
});

View File

@@ -1,811 +0,0 @@
import { expect } from "chai";
import { deploySystemFixtures } from "../utils/deployment";
import { DeployedActors, VcAndDiscloseHubProof } from "../utils/types";
import { ethers } from "hardhat";
import { CIRCUIT_CONSTANTS } from "@selfxyz/common/constants/constants";
import { ATTESTATION_ID } from "../utils/constants";
import { generateVcAndDiscloseProof, getSMTs } from "../utils/generateProof";
import { poseidon2 } from "poseidon-lite";
import { generateCommitment } from "@selfxyz/common/utils/passports/passport";
import { BigNumberish } from "ethers";
import { generateRandomFieldElement, getStartOfDayTimestamp, splitHexFromBack } from "../utils/utils";
import { Formatter, CircuitAttributeHandler } from "../utils/formatter";
import { formatCountriesList, reverseBytes, reverseCountryBytes } from "@selfxyz/common/utils/circuits/formatInputs";
import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts/forbiddenCountries";
import { countries, Country3LetterCode } from "@selfxyz/common/constants/countries";
import { castFromScope } from "@selfxyz/common/utils/circuits/uuid";
import path from "path";
describe("VC and Disclose", () => {
let deployedActors: DeployedActors;
let snapshotId: string;
let baseVcAndDiscloseProof: any;
let vcAndDiscloseProof: any;
let registerSecret: any;
let imt: any;
let commitment: any;
let nullifier: any;
let forbiddenCountriesList: Country3LetterCode[];
let invalidForbiddenCountriesList: string[];
let forbiddenCountriesListPacked: string[];
let invalidForbiddenCountriesListPacked: string[];
before(async () => {
deployedActors = await deploySystemFixtures();
registerSecret = generateRandomFieldElement();
nullifier = generateRandomFieldElement();
commitment = generateCommitment(registerSecret, ATTESTATION_ID.E_PASSPORT, deployedActors.mockPassport);
await deployedActors.registry
.connect(deployedActors.owner)
.devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
// must be imported dynamic since @openpassport/zk-kit-lean-imt is exclusively esm and hardhat does not support esm with typescript until verison 3
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
imt = new LeanIMT<bigint>(hashFunction);
await imt.insert(BigInt(commitment));
forbiddenCountriesList = [
countries.AFGHANISTAN,
"ABC",
"CBA",
"AAA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
] as Country3LetterCode[];
forbiddenCountriesListPacked = getPackedForbiddenCountries(forbiddenCountriesList);
invalidForbiddenCountriesList = ["AAA", "ABC", "CBA", "CBA"];
// const invalidWholePacked = reverseBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(invalidForbiddenCountriesList))));
// invalidForbiddenCountriesListPacked = splitHexFromBack(invalidWholePacked);
// @ts-expect-error -- the countries are not valid
invalidForbiddenCountriesListPacked = getPackedForbiddenCountries(invalidForbiddenCountriesList);
baseVcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
deployedActors.mockPassport,
castFromScope("test-scope"),
new Array(88).fill("1"),
"1",
imt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
await deployedActors.user1.getAddress(),
);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
beforeEach(async () => {
vcAndDiscloseProof = structuredClone(baseVcAndDiscloseProof);
});
afterEach(async () => {
await ethers.provider.send("evm_revert", [snapshotId]);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
describe("Verify VC and Disclose", () => {
it("should verify and get result successfully", async () => {
const { hub, registry, owner } = deployedActors;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked.slice(0, 4) as [
BigNumberish,
BigNumberish,
BigNumberish,
BigNumberish,
],
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const result = await hub.verifyVcAndDisclose(vcAndDiscloseHubProof);
expect(result.identityCommitmentRoot).to.equal(
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_MERKLE_ROOT_INDEX],
);
expect(result.revealedDataPacked).to.have.lengthOf(3);
expect(result.nullifier).to.equal(
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NULLIFIER_INDEX],
);
expect(result.attestationId).to.equal(
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX],
);
expect(result.userIdentifier).to.equal(
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX],
);
expect(result.scope).to.equal(vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_SCOPE_INDEX]);
for (let i = 0; i < 4; i++) {
expect(result.forbiddenCountriesListPacked[i]).to.equal(BigInt(forbiddenCountriesListPacked[i]));
}
});
it("should not call verifyVcAndDisclose with non-proxy address", async () => {
const { hubImpl, registry, owner } = deployedActors;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: false,
olderThan: "20",
forbiddenCountriesEnabled: false,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, false, false] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hubImpl.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hubImpl,
"UUPSUnauthorizedCallContext",
);
});
it("should fail with invalid identity commitment root", async () => {
const { hub, registry, owner } = deployedActors;
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_MERKLE_ROOT_INDEX] = generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"INVALID_COMMITMENT_ROOT",
);
});
it("should fail with invalid passport number OFAC root", async () => {
const { hub, registry, owner } = deployedActors;
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_PASSPORT_NO_SMT_ROOT_INDEX] =
generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"INVALID_OFAC_ROOT",
);
});
it("should fail with invalid name and dob OFAC root", async () => {
const { hub, registry, owner } = deployedActors;
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NAME_DOB_SMT_ROOT_INDEX] =
generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, true, false] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"INVALID_OFAC_ROOT",
);
});
it("should fail with invalid name and yob OFAC root", async () => {
const { hub, registry, owner } = deployedActors;
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NAME_YOB_SMT_ROOT_INDEX] =
generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, false, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"INVALID_OFAC_ROOT",
);
});
it("should fail with invalid current date (more than + 1 day)", async () => {
const { hub, registry, owner } = deployedActors;
const currentBlock = await ethers.provider.getBlock("latest");
const oneDayAfter = getStartOfDayTimestamp(currentBlock!.timestamp) + 24 * 60 * 60;
const date = new Date(oneDayAfter * 1000);
const dateComponents = [
Math.floor((date.getUTCFullYear() % 100) / 10),
date.getUTCFullYear() % 10,
Math.floor((date.getUTCMonth() + 1) / 10),
(date.getUTCMonth() + 1) % 10,
Math.floor(date.getUTCDate() / 10),
date.getUTCDate() % 10,
];
for (let i = 0; i < 6; i++) {
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_CURRENT_DATE_INDEX + i] =
dateComponents[i].toString();
}
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"CURRENT_DATE_NOT_IN_VALID_RANGE",
);
});
it("should not revert when current date is within + 1 day", async () => {
const { hub, registry, owner } = deployedActors;
const currentBlock = await ethers.provider.getBlock("latest");
const oneDayAfter = getStartOfDayTimestamp(currentBlock!.timestamp) + 24 * 60 * 60 - 1;
const date = new Date(oneDayAfter * 1000);
const dateComponents = [
Math.floor((date.getUTCFullYear() % 100) / 10),
date.getUTCFullYear() % 10,
Math.floor((date.getUTCMonth() + 1) / 10),
(date.getUTCMonth() + 1) % 10,
Math.floor(date.getUTCDate() / 10),
date.getUTCDate() % 10,
];
for (let i = 0; i < 6; i++) {
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_CURRENT_DATE_INDEX + i] =
dateComponents[i].toString();
}
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.not.be.reverted;
});
it("should fail with invalid current date (- 1 day)", async () => {
const { hub, registry, owner } = deployedActors;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const currentBlock = await ethers.provider.getBlock("latest");
const oneDayBefore = getStartOfDayTimestamp(currentBlock!.timestamp) - 1;
const date = new Date(oneDayBefore * 1000);
const dateComponents = [
Math.floor((date.getUTCFullYear() % 100) / 10),
date.getUTCFullYear() % 10,
Math.floor((date.getUTCMonth() + 1) / 10),
(date.getUTCMonth() + 1) % 10,
Math.floor(date.getUTCDate() / 10),
date.getUTCDate() % 10,
];
for (let i = 0; i < 6; i++) {
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_CURRENT_DATE_INDEX + i] =
dateComponents[i].toString();
}
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"CURRENT_DATE_NOT_IN_VALID_RANGE",
);
});
it("should not revert when current date is slightly less than - 1 day", async () => {
const { hub, registry, owner } = deployedActors;
const currentBlock = await ethers.provider.getBlock("latest");
const oneDayBefore = getStartOfDayTimestamp(currentBlock!.timestamp);
const date = new Date(oneDayBefore * 1000);
const dateComponents = [
Math.floor((date.getUTCFullYear() % 100) / 10),
date.getUTCFullYear() % 10,
Math.floor((date.getUTCMonth() + 1) / 10),
(date.getUTCMonth() + 1) % 10,
Math.floor(date.getUTCDate() / 10),
date.getUTCDate() % 10,
];
for (let i = 0; i < 6; i++) {
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_CURRENT_DATE_INDEX + i] =
dateComponents[i].toString();
}
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.not.be.reverted;
});
it("should succeed with bigger value than older than", async () => {
const { hub, registry, owner } = deployedActors;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "18",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.not.reverted;
});
it("should fail with invalid older than", async () => {
const { hub, registry, owner } = deployedActors;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "21",
forbiddenCountriesEnabled: false,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, false, false] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"INVALID_OLDER_THAN",
);
});
it("should fail with if listed in OFAC", async () => {
const { hub, registry, owner, mockPassport } = deployedActors;
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
const imt = new LeanIMT<bigint>(hashFunction);
imt.insert(BigInt(commitment));
const { passportNo_smt, nameAndDob_smt, nameAndYob_smt } = getSMTs();
const vcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
mockPassport,
castFromScope("test-scope"),
new Array(88).fill("1"),
"1",
imt,
"20",
passportNo_smt,
nameAndDob_smt,
nameAndYob_smt,
"0",
);
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: false,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(hub, "INVALID_OFAC");
});
it("should fail with invalid forbidden countries", async () => {
const { hub, registry, owner } = deployedActors;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: invalidForbiddenCountriesListPacked,
ofacEnabled: [true, true, true] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"INVALID_FORBIDDEN_COUNTRIES",
);
});
it("should not revert when all enablers are false", async () => {
const { hub, registry, owner } = deployedActors;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: false,
olderThan: "40",
forbiddenCountriesEnabled: false,
forbiddenCountriesListPacked: invalidForbiddenCountriesListPacked,
ofacEnabled: [false, false, false] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.not.be.reverted;
});
it("should fail with invalid VC and Disclose proof", async () => {
const { hub, registry, owner } = deployedActors;
vcAndDiscloseProof.a[0] = generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: false,
olderThan: "20",
forbiddenCountriesEnabled: false,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, false, false] as [boolean, boolean, boolean],
vcAndDiscloseProof: vcAndDiscloseProof,
};
await expect(hub.verifyVcAndDisclose(vcAndDiscloseHubProof)).to.be.revertedWithCustomError(
hub,
"INVALID_VC_AND_DISCLOSE_PROOF",
);
});
});
describe("readable parsers", () => {
async function setupVcAndDiscloseTest(types: string[]) {
const { hub } = deployedActors;
let revealedDataPacked = [BigInt(0), BigInt(0), BigInt(0)];
for (let i = 0; i < 3; i++) {
revealedDataPacked[i] = BigInt(
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_REVEALED_DATA_PACKED_INDEX + i],
);
}
const bytes = Formatter.fieldElementsToBytes(revealedDataPacked as [bigint, bigint, bigint]);
const readableData = await hub.getReadableRevealedData(
revealedDataPacked as [BigNumberish, BigNumberish, BigNumberish],
types,
);
return { readableData, bytes };
}
it("should fail when getReadableRevealedData is called by non-proxy", async () => {
const { hubImpl } = deployedActors;
let revealedDataPacked = [BigInt(0), BigInt(0), BigInt(0)];
for (let i = 0; i < 3; i++) {
revealedDataPacked[i] = BigInt(
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_REVEALED_DATA_PACKED_INDEX + i],
);
}
await expect(
hubImpl.getReadableRevealedData(revealedDataPacked as [BigNumberish, BigNumberish, BigNumberish], ["0"]),
).to.be.revertedWithCustomError(hubImpl, "UUPSUnauthorizedCallContext");
});
it("formatter and CircuitAttributeHandler are working fine", async () => {
const { readableData, bytes } = await setupVcAndDiscloseTest([
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
]);
expect(CircuitAttributeHandler.getIssuingState(bytes)).to.equal(readableData[0]);
expect(CircuitAttributeHandler.getName(bytes)).to.deep.equal(readableData[1]);
expect(CircuitAttributeHandler.getPassportNumber(bytes)).to.equal(readableData[2]);
expect(CircuitAttributeHandler.getNationality(bytes)).to.equal(readableData[3]);
expect(CircuitAttributeHandler.getDateOfBirth(bytes)).to.equal(readableData[4]);
expect(CircuitAttributeHandler.getGender(bytes)).to.equal(readableData[5]);
expect(CircuitAttributeHandler.getExpiryDate(bytes)).to.equal(readableData[6]);
expect(CircuitAttributeHandler.getOlderThan(bytes)).to.equal(readableData[7]);
expect(CircuitAttributeHandler.getPassportNoOfac(bytes)).to.equal(readableData[8]);
expect(CircuitAttributeHandler.getNameAndDobOfac(bytes)).to.equal(readableData[9]);
expect(CircuitAttributeHandler.getNameAndYobOfac(bytes)).to.equal(readableData[10]);
});
it("should return all data", async () => {
const { readableData } = await setupVcAndDiscloseTest(["0", "1", "2", "3", "4", "5", "6", "7", "8"]);
expect(readableData[0]).to.equal("FRA");
expect(readableData[1]).to.deep.equal(["ALPHONSE HUGHUES ALBERT", "DUPONT"]);
expect(readableData[2]).to.equal("15AA81234");
expect(readableData[3]).to.equal("FRA");
expect(readableData[4]).to.equal("31-01-94");
expect(readableData[5]).to.equal("M");
expect(readableData[6]).to.equal("31-10-40");
expect(readableData[7]).to.equal(20n);
expect(readableData[8]).to.equal(1n);
});
it("should only return issuing state", async () => {
const { readableData } = await setupVcAndDiscloseTest(["0"]);
expect(readableData[0]).to.equal("FRA");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(0n);
});
it("should only return name", async () => {
const { readableData } = await setupVcAndDiscloseTest(["1"]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal(["ALPHONSE HUGHUES ALBERT", "DUPONT"]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(0n);
});
it("should only return passport number", async () => {
const { readableData } = await setupVcAndDiscloseTest(["2"]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("15AA81234");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(0n);
});
it("should only return nationality", async () => {
const { readableData } = await setupVcAndDiscloseTest(["3"]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("FRA");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(0n);
});
it("should only return data of birth", async () => {
const { readableData } = await setupVcAndDiscloseTest(["4"]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("31-01-94");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(0n);
});
it("should only return gender", async () => {
const { readableData } = await setupVcAndDiscloseTest(["5"]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("M");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(0n);
});
it("should only return expiry date", async () => {
const { readableData } = await setupVcAndDiscloseTest(["6"]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("31-10-40");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(0n);
});
it("should only return older than", async () => {
const { readableData } = await setupVcAndDiscloseTest(["7"]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(20n);
expect(readableData[8]).to.equal(0n);
});
it("should only return ofac", async () => {
const { readableData } = await setupVcAndDiscloseTest(["8", "9", "10"]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(1n);
expect(readableData[9]).to.equal(1n);
expect(readableData[10]).to.equal(1n);
});
it("should fail when revealed data type is invalid", async () => {
const { hub } = deployedActors;
let revealedDataPacked = [BigInt(0), BigInt(0), BigInt(0)];
for (let i = 0; i < 3; i++) {
revealedDataPacked[i] = BigInt(
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_REVEALED_DATA_PACKED_INDEX + i],
);
}
await expect(
hub.getReadableRevealedData(revealedDataPacked as [BigNumberish, BigNumberish, BigNumberish], ["11"]),
).to.be.reverted;
});
it("should return nothing", async () => {
const { readableData } = await setupVcAndDiscloseTest([]);
expect(readableData[0]).to.equal("");
expect(readableData[1]).to.deep.equal([]);
expect(readableData[2]).to.equal("");
expect(readableData[3]).to.equal("");
expect(readableData[4]).to.equal("");
expect(readableData[5]).to.equal("");
expect(readableData[6]).to.equal("");
expect(readableData[7]).to.equal(0n);
expect(readableData[8]).to.equal(0n);
});
it("should parse forbidden countries with CircuitAttributeHandler", async () => {
const { hub } = deployedActors;
const localForbiddenCountriesList = ["AFG", "ABC", "CBA"] as const;
const forbiddenCountriesListPacked = getPackedForbiddenCountries([...localForbiddenCountriesList]);
const readableForbiddenCountries = await hub.getReadableForbiddenCountries(forbiddenCountriesListPacked);
expect(readableForbiddenCountries[0]).to.equal(localForbiddenCountriesList[0]);
expect(readableForbiddenCountries[1]).to.equal(localForbiddenCountriesList[1]);
expect(readableForbiddenCountries[2]).to.equal(localForbiddenCountriesList[2]);
});
it("should return maximum length of forbidden countries", async () => {
const { hub } = deployedActors;
const localForbiddenCountriesList = [
"AAA",
"FRA",
"CBA",
"CBA",
"CBA",
"CBA",
"CBA",
"CBA",
"CBA",
"CBA",
] as const;
const forbiddenCountriesListPacked = getPackedForbiddenCountries([...localForbiddenCountriesList]);
const readableForbiddenCountries = await hub.getReadableForbiddenCountries(forbiddenCountriesListPacked);
expect(readableForbiddenCountries.length).to.equal(40);
expect(readableForbiddenCountries[0]).to.equal(localForbiddenCountriesList[0]);
expect(readableForbiddenCountries[1]).to.equal(localForbiddenCountriesList[1]);
expect(readableForbiddenCountries[2]).to.equal(localForbiddenCountriesList[2]);
expect(readableForbiddenCountries[3]).to.equal(localForbiddenCountriesList[3]);
expect(readableForbiddenCountries[4]).to.equal(localForbiddenCountriesList[4]);
expect(readableForbiddenCountries[5]).to.equal(localForbiddenCountriesList[5]);
expect(readableForbiddenCountries[6]).to.equal(localForbiddenCountriesList[6]);
expect(readableForbiddenCountries[7]).to.equal(localForbiddenCountriesList[7]);
expect(readableForbiddenCountries[8]).to.equal(localForbiddenCountriesList[8]);
expect(readableForbiddenCountries[9]).to.equal(localForbiddenCountriesList[9]);
});
it("should fail when getReadableForbiddenCountries is called by non-proxy", async () => {
const { hubImpl } = deployedActors;
const localForbiddenCountriesList = [
"AAA",
"FRA",
"CBA",
"CBA",
"CBA",
"CBA",
"CBA",
"CBA",
"CBA",
"CBA",
] as const;
const forbiddenCountriesListPacked = getPackedForbiddenCountries([...localForbiddenCountriesList]);
await expect(hubImpl.getReadableForbiddenCountries(forbiddenCountriesListPacked)).to.be.revertedWithCustomError(
hubImpl,
"UUPSUnauthorizedCallContext",
);
});
});
});

View File

@@ -1,524 +0,0 @@
import { expect } from "chai";
import { ethers } from "hardhat";
import { deploySystemFixtures } from "../utils/deployment";
import { DeployedActors, VcAndDiscloseHubProof } from "../utils/types";
import { generateRandomFieldElement, splitHexFromBack } from "../utils/utils";
import { generateCommitment } from "@selfxyz/common/utils/passports/passport";
import { ATTESTATION_ID } from "../utils/constants";
import { CIRCUIT_CONSTANTS } from "@selfxyz/common/constants/constants";
import { poseidon2 } from "poseidon-lite";
import { generateVcAndDiscloseProof, parseSolidityCalldata } from "../utils/generateProof";
import { Formatter } from "../utils/formatter";
import { formatCountriesList, reverseBytes } from "@selfxyz/common/utils/circuits/formatInputs";
import { stringToBigInt } from "@selfxyz/common/utils/scope";
import { VerifyAll } from "../../typechain-types";
import { getSMTs } from "../utils/generateProof";
import { Groth16Proof, PublicSignals, groth16 } from "snarkjs";
import { VcAndDiscloseProof } from "../utils/types";
import { stringToBigInt } from "@selfxyz/common/utils/scope";
describe("VerifyAll", () => {
let deployedActors: DeployedActors;
let verifyAll: VerifyAll;
let snapshotId: string;
let baseVcAndDiscloseProof: any;
let vcAndDiscloseProof: any;
let registerSecret: any;
let imt: any;
let commitment: any;
let nullifier: any;
let forbiddenCountriesList: string[];
let invalidForbiddenCountriesList: string[];
let forbiddenCountriesListPacked: string[];
let invalidForbiddenCountriesListPacked: string[];
before(async () => {
deployedActors = await deploySystemFixtures();
const VerifyAllFactory = await ethers.getContractFactory("VerifyAll");
verifyAll = await VerifyAllFactory.deploy(deployedActors.hub.getAddress(), deployedActors.registry.getAddress());
registerSecret = generateRandomFieldElement();
nullifier = generateRandomFieldElement();
commitment = generateCommitment(registerSecret, ATTESTATION_ID.E_PASSPORT, deployedActors.mockPassport);
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
// must be imported dynamic since @openpassport/zk-kit-lean-imt is exclusively esm and hardhat does not support esm with typescript until verison 3
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
imt = new LeanIMT<bigint>(hashFunction);
await imt.insert(BigInt(commitment));
forbiddenCountriesList = [
"AAA",
"ABC",
"CBA",
"AAA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
"AAA",
"ABC",
"CBA",
];
const wholePacked = reverseBytes(
Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList))),
);
forbiddenCountriesListPacked = splitHexFromBack(wholePacked);
invalidForbiddenCountriesList = ["AAA", "ABC", "CBA", "CBA"];
const invalidWholePacked = reverseBytes(
Formatter.bytesToHexString(new Uint8Array(formatCountriesList(invalidForbiddenCountriesList))),
);
invalidForbiddenCountriesListPacked = splitHexFromBack(invalidWholePacked);
baseVcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
deployedActors.mockPassport,
stringToBigInt("test-scope").toString(),
new Array(88).fill("1"),
"1",
imt,
"20",
undefined,
undefined,
undefined,
undefined,
forbiddenCountriesList,
await deployedActors.user1.getAddress(),
);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
beforeEach(async () => {
vcAndDiscloseProof = structuredClone(baseVcAndDiscloseProof);
});
afterEach(async () => {
await ethers.provider.send("evm_revert", [snapshotId]);
snapshotId = await ethers.provider.send("evm_snapshot", []);
});
describe("verifyAll", () => {
it("should verify and get result successfully", async () => {
const { registry, owner } = deployedActors;
const tx = await registry
.connect(owner)
.devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
const receipt = (await tx.wait()) as any;
const timestamp = (await ethers.provider.getBlock(receipt.blockNumber))!.timestamp;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]; // Example types
const [readableData, success] = await verifyAll.verifyAll(timestamp, vcAndDiscloseHubProof, types);
expect(success).to.be.true;
expect(readableData.name).to.not.be.empty;
});
it("should verify and get result successfully with out timestamp verification", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"]; // Example types
const [readableData, success] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.true;
expect(readableData.name).to.not.be.empty;
});
it("should return empty result when verification fails", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_MERKLE_ROOT_INDEX] = generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(readableData.name).to.be.empty;
});
it("should fail with invalid root timestamp", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success] = await verifyAll.verifyAll(123456, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(readableData.name).to.be.empty;
});
describe("Error Handling", () => {
it("should return error code 'INVALID_VC_AND_DISCLOSE_PROOF' when proof is invalid", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
vcAndDiscloseProof.a[0] = generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: false,
olderThan: "20",
forbiddenCountriesEnabled: false,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, false, false],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_VC_AND_DISCLOSE_PROOF");
expect(readableData.name).to.be.empty;
});
it("should return error code 'CURRENT_DATE_NOT_IN_VALID_RANGE' when date is invalid", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_CURRENT_DATE_INDEX] = 0;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("CURRENT_DATE_NOT_IN_VALID_RANGE");
expect(readableData.name).to.be.empty;
});
it("should return error code 'INVALID_OLDER_THAN' when age check fails", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "21", // Higher than the age in proof
forbiddenCountriesEnabled: false,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, false, false],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_OLDER_THAN");
expect(readableData.name).to.be.empty;
});
it("should return error code 'INVALID_OFAC' when OFAC check fails", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
const { passportNo_smt, nameAndDob_smt, nameAndYob_smt } = getSMTs();
vcAndDiscloseProof = await generateVcAndDiscloseProof(
registerSecret,
BigInt(ATTESTATION_ID.E_PASSPORT).toString(),
deployedActors.mockPassport,
stringToBigInt("test-scope").toString(),
new Array(88).fill("1"),
"1",
imt,
"20",
passportNo_smt,
nameAndDob_smt,
nameAndYob_smt,
"0",
);
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: false,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
console.log("return values");
console.log("readable data: ", readableData);
console.log("success: ", success);
console.log("errorCode: ", errorCode);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_OFAC");
expect(readableData.name).to.be.empty;
});
it("should return error code 'INVALID_FORBIDDEN_COUNTRIES' when countries check fails", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: invalidForbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_FORBIDDEN_COUNTRIES");
expect(readableData.name).to.be.empty;
});
it("should return error code 'INVALID_TIMESTAMP' when root timestamp doesn't match", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(
123456, // Invalid timestamp
vcAndDiscloseHubProof,
types,
);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_TIMESTAMP");
expect(readableData.name).to.be.empty;
});
it("should return error code 'INVALID_OFAC_ROOT' when passport number OFAC root is invalid", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_PASSPORT_NO_SMT_ROOT_INDEX] =
generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_OFAC_ROOT");
expect(readableData.name).to.be.empty;
});
it("should return error code 'INVALID_OFAC_ROOT' when name and dob OFAC root is invalid", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NAME_DOB_SMT_ROOT_INDEX] =
generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, true, false],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_OFAC_ROOT");
expect(readableData.name).to.be.empty;
});
it("should return error code 'INVALID_OFAC_ROOT' when name and yob OFAC root is invalid", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NAME_YOB_SMT_ROOT_INDEX] =
generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [false, false, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_OFAC_ROOT");
expect(readableData.name).to.be.empty;
});
});
});
describe("admin functions", () => {
it("should allow owner to set new hub address", async () => {
const newHubAddress = await deployedActors.user1.getAddress();
await verifyAll.setHub(newHubAddress);
});
it("should allow owner to set new registry address", async () => {
const newRegistryAddress = await deployedActors.user1.getAddress();
await verifyAll.setRegistry(newRegistryAddress);
});
it("should not allow non-owner to set new hub address", async () => {
const newHubAddress = await deployedActors.user1.getAddress();
await expect(verifyAll.connect(deployedActors.user1).setHub(newHubAddress)).to.be.revertedWithCustomError(
verifyAll,
"AccessControlUnauthorizedAccount",
);
});
it("should not allow non-owner to set new registry address", async () => {
const newRegistryAddress = await deployedActors.user1.getAddress();
await expect(
verifyAll.connect(deployedActors.user1).setRegistry(newRegistryAddress),
).to.be.revertedWithCustomError(verifyAll, "AccessControlUnauthorizedAccount");
});
});
describe("VerifyAll (Custom Error Handling)", () => {
it("should return error code 'INVALID_VC_AND_DISCLOSE_PROOF' when vcAndDisclose proof is invalid", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
vcAndDiscloseProof.a[0] = generateRandomFieldElement();
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("INVALID_VC_AND_DISCLOSE_PROOF");
expect(readableData.name).to.be.empty;
});
it("should return error code 'CURRENT_DATE_NOT_IN_VALID_RANGE' when current date is out of range", async () => {
const { registry, owner } = deployedActors;
await registry.connect(owner).devAddIdentityCommitment(ATTESTATION_ID.E_PASSPORT, nullifier, commitment);
vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_CURRENT_DATE_INDEX] = 0;
const vcAndDiscloseHubProof: VcAndDiscloseHubProof = {
olderThanEnabled: true,
olderThan: "20",
forbiddenCountriesEnabled: true,
forbiddenCountriesListPacked: forbiddenCountriesListPacked,
ofacEnabled: [true, true, true],
vcAndDiscloseProof: vcAndDiscloseProof,
};
const types = ["0", "1", "2"];
const [readableData, success, errorCode] = await verifyAll.verifyAll(0, vcAndDiscloseHubProof, types);
expect(success).to.be.false;
expect(errorCode).to.equal("CURRENT_DATE_NOT_IN_VALID_RANGE");
expect(readableData.name).to.be.empty;
});
});
});

View File

@@ -1,193 +0,0 @@
// import { expect } from "chai";
// import { ethers } from "hardhat";
// import { deploySystemFixtures } from "../utils/deployment";
// import { DeployedActors } from "../utils/types";
// import { generateRandomFieldElement } from "../utils/utils";
// import { generateCommitment } from "@selfxyz/common/utils/passports/passport";
// import { ATTESTATION_ID } from "../utils/constants";
// import { CIRCUIT_CONSTANTS } from "@selfxyz/common/constants/constants";
// import { LeanIMT } from "@openpassport/zk-kit-lean-imt";
// import { poseidon2 } from "poseidon-lite";
// import { generateVcAndDiscloseRawProof, parseSolidityCalldata } from "../utils/generateProof";
// import { Formatter } from "../utils/formatter";
// import { formatCountriesList, reverseBytes } from "@selfxyz/common/utils/circuits/formatInputs";
// import { VerifyAll } from "../../typechain-types";
// import { SelfBackendVerifier } from "../../../sdk/core/src/SelfBackendVerifier";
// import { Groth16Proof, PublicSignals, groth16 } from "snarkjs";
// import { VcAndDiscloseProof } from "../utils/types";
// import { hasSubscribers } from "diagnostics_channel";
// describe("VerifyAll with AttestationVerifier", () => {
// let selfBackendVerifier: SelfBackendVerifier;
// let proof: Groth16Proof;
// let publicSignals: PublicSignals;
// let deployedActors: DeployedActors;
// let verifyAll: VerifyAll;
// let snapshotId: string;
// let baseVcAndDiscloseProof: any;
// let vcAndDiscloseProof: any;
// let registerSecret: any;
// let imt: any;
// let commitment: any;
// let nullifier: any;
// let forbiddenCountriesList: string[];
// let forbiddenCountriesListPacked: string;
// let baseRawProof: {
// proof: Groth16Proof,
// publicSignals: PublicSignals
// };
// let rawProof: {
// proof: Groth16Proof,
// publicSignals: PublicSignals
// };
// before(async () => {
// deployedActors = await deploySystemFixtures();
// const VerifyAllFactory = await ethers.getContractFactory("VerifyAll");
// verifyAll = await VerifyAllFactory.deploy(
// deployedActors.hub.getAddress(),
// deployedActors.registry.getAddress()
// );
// registerSecret = generateRandomFieldElement();
// nullifier = generateRandomFieldElement();
// commitment = generateCommitment(registerSecret, ATTESTATION_ID.E_PASSPORT, deployedActors.mockPassport);
// const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
// imt = new LeanIMT<bigint>(hashFunction);
// await imt.insert(BigInt(commitment));
// forbiddenCountriesList = ['AFG', 'ALB'];
// forbiddenCountriesListPacked = reverseBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList))));
// baseRawProof = await generateVcAndDiscloseRawProof(
// registerSecret,
// ATTESTATION_ID.E_PASSPORT,
// deployedActors.mockPassport,
// "test-scope",
// new Array(88).fill("1"),
// 1,
// imt,
// "20",
// undefined,
// undefined,
// undefined,
// undefined,
// forbiddenCountriesList,
// (await deployedActors.user1?.getAddress()).slice(2)
// );
// // Setup AttestationVerifier with the same verifyAll contract
// selfBackendVerifier = new SelfBackendVerifier(
// "http://127.0.0.1:8545", // or your test RPC URL
// "test-scope",
// await deployedActors.registry.getAddress() as `0x${string}`,
// await verifyAll.getAddress() as `0x${string}`,
// );
// snapshotId = await ethers.provider.send("evm_snapshot", []);
// });
// beforeEach(async () => {
// rawProof = structuredClone(baseRawProof);
// });
// afterEach(async () => {
// await ethers.provider.send("evm_revert", [snapshotId]);
// snapshotId = await ethers.provider.send("evm_snapshot", []);
// });
// it("should verify and get valid attestation result successfully after identity commitment is added", async () => {
// const { registry, owner } = deployedActors;
// await registry.connect(owner).devAddIdentityCommitment(
// ATTESTATION_ID.E_PASSPORT,
// nullifier,
// commitment
// );
// selfBackendVerifier.excludeCountries("Afghanistan", "Albania");
// selfBackendVerifier.setMinimumAge(20);
// selfBackendVerifier.enablePassportNoOfacCheck();
// selfBackendVerifier.enableNameAndDobOfacCheck();
// selfBackendVerifier.enableNameAndYobOfacCheck();
// selfBackendVerifier.setNationality("France");
// selfBackendVerifier.setTargetRootTimestamp(0);
// const result = await selfBackendVerifier.verify(
// rawProof.proof,
// rawProof.publicSignals
// );
// // Assert that the attestation verification result is valid.
// expect(result.userId).to.equal(rawProof.publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX]);
// expect(result.isValid).to.be.true;
// expect(result.isValidDetails.isValidScope).to.be.true;
// expect(result.isValidDetails.isValidAttestationId).to.be.true;
// expect(result.isValidDetails.isValidProof).to.be.true;
// expect(result.isValidDetails.isValidNationality).to.be.true;
// expect(result.application).to.equal("test-scope");
// expect(result.credentialSubject.merkle_root).to.equal(rawProof.publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_MERKLE_ROOT_INDEX]);
// expect(result.credentialSubject.attestation_id).to.equal(BigInt(ATTESTATION_ID.E_PASSPORT));
// expect(result.credentialSubject.current_date?.slice(0, 16))
// .to.equal(new Date().toISOString().slice(0, 16));
// expect(result.credentialSubject.issuing_state).to.equal("FRA");
// expect(result.credentialSubject.name?.[0]).to.equal("ALPHONSE HUGHUES ALBERT");
// expect(result.credentialSubject.name?.[1]).to.equal("DUPONT");
// expect(result.credentialSubject.passport_number).to.equal("15AA81234");
// expect(result.credentialSubject.nationality).to.equal("FRA");
// expect(result.credentialSubject.date_of_birth).to.equal("31-01-94");
// expect(result.credentialSubject.gender).to.equal("M");
// expect(result.credentialSubject.expiry_date).to.equal("31-10-40");
// expect(result.credentialSubject.older_than).to.equal("20");
// expect(result.credentialSubject.passport_no_ofac).to.equal("1");
// expect(result.credentialSubject.name_and_dob_ofac).to.equal("1");
// expect(result.credentialSubject.name_and_yob_ofac).to.equal("1");
// });
// it("should fail when invalid VC and Disclose proof is provided", async () => {
// const { registry, owner, hub } = deployedActors;
// await registry.connect(owner).devAddIdentityCommitment(
// ATTESTATION_ID.E_PASSPORT,
// nullifier,
// commitment
// );
// rawProof.proof.pi_a[0] = generateRandomFieldElement();
// const result = await selfBackendVerifier.verify(
// rawProof.proof,
// rawProof.publicSignals
// );
// expect(result.isValid).to.be.false;
// expect(result.isValidDetails.isValidProof).to.be.false;
// });
// it("should fail when invalid scope is provided", async () => {
// rawProof.publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_SCOPE_INDEX] = generateRandomFieldElement().toString();
// const result = await selfBackendVerifier.verify(
// rawProof.proof,
// rawProof.publicSignals
// );
// expect(result.isValid).to.be.false;
// expect(result.isValidDetails.isValidScope).to.be.false;
// });
// it("should fail when invalid attestation id is provided", async () => {
// rawProof.publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX] = generateRandomFieldElement().toString();
// const result = await selfBackendVerifier.verify(
// rawProof.proof,
// rawProof.publicSignals
// );
// expect(result.isValid).to.be.false;
// expect(result.isValidDetails.isValidAttestationId).to.be.false;
// });
// it("should fail when invalid nationality is provided", async () => {
// selfBackendVerifier.setNationality("United States of America");
// const result = await selfBackendVerifier.verify(
// rawProof.proof,
// rawProof.publicSignals
// );
// expect(result.isValid).to.be.false;
// expect(result.isValidDetails.isValidNationality).to.be.false;
// });
// });

View File

@@ -214,7 +214,7 @@ describe("MockGCPJWTVerifier", function () {
[3n, 4n],
];
const mockProofC: [bigint, bigint] = [1n, 2n];
const mockPubSignals: [bigint, bigint, bigint, bigint, bigint, bigint, bigint] = [1n, 2n, 3n, 4n, 5n, 6n, 7n];
const mockPubSignals: bigint[] = [1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n];
describe("Default behavior", function () {
it("should return true by default", async function () {

View File

@@ -2,7 +2,7 @@ import { expect } from "chai";
import { deploySystemFixtures } from "../utils/deployment";
import { DeployedActors } from "../utils/types";
import { ethers } from "hardhat";
import { RegisterVerifierId, DscVerifierId } from "@selfxyz/common/constants/constants";
import { RegisterVerifierId, DscVerifierId } from "@selfxyz/new-common/src/foundation/constants/identity";
describe("Unit Tests for IdentityVerificationHub", () => {
let deployedActors: DeployedActors;

View File

@@ -1,7 +1,7 @@
import { expect } from "chai";
import { ethers } from "hardhat";
import { TestSelfUtils, TestCountryCodes } from "../../typechain-types";
import { packForbiddenCountriesList } from "@selfxyz/common/utils/contracts";
import { packForbiddenCountriesList } from "@selfxyz/new-common/src/blockchain/formatCallData";
describe("SelfUtils", function () {
let testSelfUtils: TestSelfUtils;

View File

@@ -1,7 +1,7 @@
import { expect } from "chai";
import { ethers } from "hardhat";
import { TestSelfVerificationRoot } from "../../typechain-types";
import { stringToBigInt, bigIntToString, hashEndpointWithScope } from "@selfxyz/common/utils/scope";
import { stringToBigInt, bigIntToString, hashEndpointWithScope } from "@selfxyz/new-common/src/crypto/scope";
describe("SelfVerificationRoot - Automatic Scope Generation", () => {
let testContract: TestSelfVerificationRoot;

View File

@@ -0,0 +1,143 @@
import { MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH } from "@selfxyz/new-common/src/foundation/constants/disclosure";
type Country3LetterCode = string;
export function getPackedForbiddenCountries(forbiddenCountriesList: Array<Country3LetterCode | "">): string[] {
if (forbiddenCountriesList.length > MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH) {
throw new Error(`Countries list must be less than or equal to ${MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH}`);
}
const paddedCountries = [...forbiddenCountriesList];
while (paddedCountries.length < MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH) {
paddedCountries.push("");
}
const countryBytes: number[] = [];
for (const country of paddedCountries) {
const paddedCountry = country.padEnd(3, "\0");
for (const char of paddedCountry) {
countryBytes.push(char.charCodeAt(0));
}
}
let hexString = "0x";
for (let i = 0; i < countryBytes.length; i++) {
hexString += countryBytes[i].toString(16).padStart(2, "0");
}
const hex = hexString.slice(2);
const bytes = hex.match(/.{2}/g) || [];
const reversedBytes = bytes.reverse();
const reversedHex = "0x" + reversedBytes.join("");
const result: string[] = [];
let remaining = reversedHex.slice(2);
const chunkSizeHex = 31 * 2;
while (remaining.length > 0) {
const chunk = remaining.slice(-chunkSizeHex);
remaining = remaining.slice(0, -chunkSizeHex);
const paddedChunk = chunk.padStart(64, "0");
result.push("0x" + paddedChunk);
}
return result;
}
export function packForbiddenCountriesList(forbiddenCountries: string[]) {
const MAX_BYTES_IN_FIELD = 31;
const REQUIRED_CHUNKS = 4;
const bytes: number[] = [];
for (const country of forbiddenCountries) {
if (!country || country.length !== 3) {
throw new Error(`Invalid country code: "${country}". Country codes must be exactly 3 characters long.`);
}
}
for (const country of forbiddenCountries) {
const countryCode = country.padEnd(3, " ").slice(0, 3);
for (const char of countryCode) {
bytes.push(char.charCodeAt(0));
}
}
const packSize = MAX_BYTES_IN_FIELD;
const maxBytes = bytes.length;
const remain = maxBytes % packSize;
const numChunks = remain > 0 ? Math.floor(maxBytes / packSize) + 1 : Math.floor(maxBytes / packSize);
const output: `0x${string}`[] = new Array(REQUIRED_CHUNKS).fill("0x" + "0".repeat(64));
for (let i = 0; i < numChunks; i++) {
let sum = BigInt(0);
for (let j = 0; j < packSize; j++) {
const idx = packSize * i + j;
if (idx < maxBytes) {
const value = BigInt(bytes[idx]);
const shift = BigInt(8 * j);
sum += value << shift;
}
}
const hexString = sum.toString(16).padStart(64, "0");
output[i] = ("0x" + hexString) as `0x${string}`;
}
return output;
}
export function formatCallData_disclose(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],
a: parsedCallData[0],
b: [parsedCallData[1][0], parsedCallData[1][1]],
c: parsedCallData[2],
};
}
export function formatCallData_dsc(parsedCallData: any[]) {
return {
blinded_dsc_commitment: parsedCallData[3][0],
merkle_root: parsedCallData[3][1],
a: parsedCallData[0],
b: [parsedCallData[1][0], parsedCallData[1][1]],
c: parsedCallData[2],
};
}
export function formatCallData_register(parsedCallData: any[]) {
return {
blinded_dsc_commitment: parsedCallData[3][0],
nullifier: parsedCallData[3][1],
commitment: parsedCallData[3][2],
attestation_id: parsedCallData[3][3],
a: parsedCallData[0],
b: [parsedCallData[1][0], parsedCallData[1][1]],
c: parsedCallData[2],
};
}
export function formatProof(proof: any, publicSignals: any) {
return {
a: proof.a,
b: [
[proof.b[0][1], proof.b[0][0]],
[proof.b[1][1], proof.b[1][0]],
],
c: proof.c,
pubSignals: publicSignals,
};
}

View File

@@ -1,159 +0,0 @@
import { Signer } from "ethers";
import { ethers } from "hardhat";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants";
import { genAndInitMockPassportData } from "@selfxyz/common/utils/passports/genMockPassportData";
import { getCscaTreeRoot } from "@selfxyz/common/utils/trees";
import { PassportData } from "@selfxyz/common/utils/types";
import serialized_csca_tree from "../../../common/pubkeys/serialized_csca_tree.json";
import { getSMTs } from "./generateProof";
import {
DeployedActors,
DscVerifier,
IdentityRegistry,
IdentityRegistryImplV1,
IdentityVerificationHub,
IdentityVerificationHubImplV1,
RegisterVerifier,
VcAndDiscloseVerifier,
} from "./types";
// Verifier artifacts
import VcAndDiscloseVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/disclose/Verifier_vc_and_disclose_staging.sol/Verifier_vc_and_disclose_staging.json";
// import VcAndDiscloseVerifierArtifactProd from "../../artifacts/contracts/verifiers/disclose/Verifier_vc_and_disclose.sol/Verifier_vc_and_disclose.json";
import RegisterVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/register/Verifier_register_sha256_sha256_sha256_rsa_65537_4096_staging.sol/Verifier_register_sha256_sha256_sha256_rsa_65537_4096_staging.json";
// import RegisterVerifierArtifactProd from "../../artifacts/contracts/verifiers/register/Verifier_register_rsa_65537_sha256.sol/Verifier_register_rsa_65537_sha256.json";
import DscVerifierArtifactLocal from "../../artifacts/contracts/verifiers/local/staging/dsc/Verifier_dsc_sha256_rsa_65537_4096_staging.sol/Verifier_dsc_sha256_rsa_65537_4096_staging.json";
// import DscVerifierArtifactProd from "../../artifacts/contracts/verifiers/dsc/Verifier_dsc_sha256_rsa_65537_4096.sol/Verifier_dsc_sha256_rsa_65537_4096.json";
export async function deploySystemFixtures(): Promise<DeployedActors> {
let identityVerificationHubProxy: IdentityVerificationHub;
let identityVerificationHubImpl: IdentityVerificationHubImplV1;
let identityRegistryProxy: IdentityRegistry;
let identityRegistryImpl: IdentityRegistryImplV1;
let vcAndDiscloseVerifier: VcAndDiscloseVerifier;
let registerVerifier: RegisterVerifier;
let dscVerifier: DscVerifier;
let owner: Signer;
let user1: Signer;
let user2: Signer;
let mockPassport: PassportData;
[owner, user1, user2] = await ethers.getSigners();
const newBalance = "0x" + ethers.parseEther("10000").toString(16);
await ethers.provider.send("hardhat_setBalance", [await owner.getAddress(), newBalance]);
await ethers.provider.send("hardhat_setBalance", [await user1.getAddress(), newBalance]);
await ethers.provider.send("hardhat_setBalance", [await user2.getAddress(), newBalance]);
mockPassport = genAndInitMockPassportData("sha256", "sha256", "rsa_sha256_65537_4096", "FRA", "940131", "401031");
// Deploy verifiers
const vcAndDiscloseVerifierArtifact = VcAndDiscloseVerifierArtifactLocal;
const vcAndDiscloseVerifierFactory = await ethers.getContractFactory(
vcAndDiscloseVerifierArtifact.abi,
vcAndDiscloseVerifierArtifact.bytecode,
owner,
);
vcAndDiscloseVerifier = await vcAndDiscloseVerifierFactory.deploy();
await vcAndDiscloseVerifier.waitForDeployment();
// Deploy register verifier
const registerVerifierArtifact = RegisterVerifierArtifactLocal;
const registerVerifierFactory = await ethers.getContractFactory(
registerVerifierArtifact.abi,
registerVerifierArtifact.bytecode,
owner,
);
registerVerifier = await registerVerifierFactory.deploy();
await registerVerifier.waitForDeployment();
// Deploy dsc verifier
const dscVerifierArtifact = DscVerifierArtifactLocal;
const dscVerifierFactory = await ethers.getContractFactory(
dscVerifierArtifact.abi,
dscVerifierArtifact.bytecode,
owner,
);
dscVerifier = await dscVerifierFactory.deploy();
await dscVerifier.waitForDeployment();
// Deploy PoseidonT3
const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3", owner);
const poseidonT3 = await PoseidonT3Factory.deploy();
await poseidonT3.waitForDeployment();
// Deploy IdentityRegistryImplV1
const IdentityRegistryImplFactory = await ethers.getContractFactory(
"IdentityRegistryImplV1",
{
libraries: {
PoseidonT3: poseidonT3.target,
},
},
owner,
);
identityRegistryImpl = await IdentityRegistryImplFactory.deploy();
await identityRegistryImpl.waitForDeployment();
// Deploy IdentityVerificationHubImplV1
const IdentityVerificationHubImplFactory = await ethers.getContractFactory("IdentityVerificationHubImplV1", owner);
identityVerificationHubImpl = await IdentityVerificationHubImplFactory.deploy();
await identityVerificationHubImpl.waitForDeployment();
// Deploy registry with temporary hub address
const temporaryHubAddress = "0x0000000000000000000000000000000000000000";
const registryInitData = identityRegistryImpl.interface.encodeFunctionData("initialize", [temporaryHubAddress]);
const registryProxyFactory = await ethers.getContractFactory("IdentityRegistry", owner);
identityRegistryProxy = await registryProxyFactory.deploy(identityRegistryImpl.target, registryInitData);
await identityRegistryProxy.waitForDeployment();
// Deploy hub with deployed registry and verifiers
const initializeData = identityVerificationHubImpl.interface.encodeFunctionData("initialize", [
identityRegistryProxy.target,
vcAndDiscloseVerifier.target,
[RegisterVerifierId.register_sha256_sha256_sha256_rsa_65537_4096],
[registerVerifier.target],
[DscVerifierId.dsc_sha256_rsa_65537_4096],
[dscVerifier.target],
]);
const hubFactory = await ethers.getContractFactory("IdentityVerificationHub", owner);
identityVerificationHubProxy = await hubFactory.deploy(identityVerificationHubImpl.target, initializeData);
await identityVerificationHubProxy.waitForDeployment();
// Get contracts with implementation ABI and update hub address
const registryContract = (await ethers.getContractAt(
"IdentityRegistryImplV1",
identityRegistryProxy.target,
)) as IdentityRegistryImplV1;
const updateHubTx = await registryContract.updateHub(identityVerificationHubProxy.target);
await updateHubTx.wait();
const hubContract = (await ethers.getContractAt(
"IdentityVerificationHubImplV1",
identityVerificationHubProxy.target,
)) as IdentityVerificationHubImplV1;
// Initialize roots
const csca_root = getCscaTreeRoot(serialized_csca_tree);
await registryContract.updateCscaRoot(csca_root, { from: owner });
const { passportNo_smt, nameAndDob_smt, nameAndYob_smt } = getSMTs();
await registryContract.updatePassportNoOfacRoot(passportNo_smt.root, { from: owner });
await registryContract.updateNameAndDobOfacRoot(nameAndDob_smt.root, { from: owner });
await registryContract.updateNameAndYobOfacRoot(nameAndYob_smt.root, { from: owner });
return {
hub: hubContract,
hubImpl: identityVerificationHubImpl,
registry: registryContract,
registryImpl: identityRegistryImpl,
vcAndDisclose: vcAndDiscloseVerifier,
register: registerVerifier,
dsc: dscVerifier,
owner: owner,
user1: user1,
user2: user2,
mockPassport: mockPassport,
};
}

View File

@@ -1,11 +1,11 @@
import { ethers } from "hardhat";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants";
import { genAndInitMockPassportData } from "@selfxyz/common/utils/passports/genMockPassportData";
import { getCscaTreeRoot } from "@selfxyz/common/utils/trees";
import { PassportData } from "@selfxyz/common/utils/types";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/new-common/src/foundation/constants/identity";
import { genAndInitMockPassportData } from "@selfxyz/new-common/src/testing/genMockPassportData";
import { getCscaTreeRoot } from "@selfxyz/new-common/src/trees/proof";
import { PassportData } from "@selfxyz/new-common/src/foundation/types/document";
import { getSMTs } from "./generateProof";
import serialized_csca_tree from "../../../common/pubkeys/serialized_csca_tree.json";
import serialized_csca_tree from "@selfxyz/new-common/src/data/serialized_csca_tree.json";
import { DeployedActorsV2 } from "./types";
// Verifier artifacts (local staging)

View File

@@ -3,24 +3,20 @@ import path from "path";
import { poseidon2, poseidon3 } from "poseidon-lite";
import type { CircuitSignals, Groth16Proof, PublicSignals } from "snarkjs";
import { groth16 } from "snarkjs";
import { PassportData } from "@selfxyz/common/utils/types";
import { PassportData } from "@selfxyz/new-common/src/foundation/types/document";
import { CircuitArtifacts, DscCircuitProof, RegisterCircuitProof, VcAndDiscloseProof } from "./types.js";
import {
generateMockKycRegisterInput,
generateKycDiscloseInput,
prepareAadhaarDiscloseTestData,
prepareAadhaarRegisterTestData,
} from "@selfxyz/common";
import { generateMockKycRegisterInputs } from "@selfxyz/new-common/src/circuits/inputs/register-kyc";
import { generateKycDiscloseInputFromDummy } from "@selfxyz/new-common/src/circuits/inputs/disclose-kyc";
import { generateAadhaarDiscloseInputs } from "@selfxyz/new-common/src/circuits/inputs/disclose-aadhaar";
import { generateAadhaarRegisterInputs } from "@selfxyz/new-common/src/circuits/inputs/register-aadhaar";
import { BigNumberish } from "ethers";
import {
generateCircuitInputsDSC,
generateCircuitInputsRegister,
generateCircuitInputsVCandDisclose,
} from "@selfxyz/common/utils/circuits/generateInputs";
import { getCircuitNameFromPassportData } from "@selfxyz/common/utils/circuits/circuitsName";
import serialized_csca_tree from "../../../common/pubkeys/serialized_csca_tree.json";
import serialized_dsc_tree from "../../../common/pubkeys/serialized_dsc_tree.json";
import { generatePassportDscInputs } from "@selfxyz/new-common/src/circuits/inputs/dsc";
import { generatePassportRegisterInputs } from "@selfxyz/new-common/src/circuits/inputs/register";
import { generatePassportDiscloseInputs } from "@selfxyz/new-common/src/circuits/inputs/disclose";
import { getCircuitNameFromPassportData } from "@selfxyz/new-common/src/circuits/circuitName";
import serialized_csca_tree from "@selfxyz/new-common/src/data/serialized_csca_tree.json";
import serialized_dsc_tree from "@selfxyz/new-common/src/data/serialized_dsc_tree.json";
import { GenericProofStructStruct } from "../../typechain-types/contracts/IdentityVerificationHubImplV2.js";
const { LeanIMT, ChildNodes } = require("@openpassport/zk-kit-lean-imt");
const { SMT } = require("@openpassport/zk-kit-smt");
@@ -96,7 +92,7 @@ const vcAndDiscloseCircuitsKyc: CircuitArtifacts = {
export async function generateRegisterProof(secret: string, passportData: PassportData): Promise<RegisterCircuitProof> {
// Get the circuit inputs
const registerCircuitInputs: CircuitSignals = await generateCircuitInputsRegister(
const registerCircuitInputs: CircuitSignals = await generatePassportRegisterInputs(
secret,
passportData,
serialized_dsc_tree as string,
@@ -135,7 +131,7 @@ export async function generateRegisterIdProof(
const circuitName = getCircuitNameFromPassportData(passportData, "register");
// Get the circuit inputs for ID card - passportData should already be parsed from genMockIdDocAndInitDataParsing
const registerCircuitInputs: CircuitSignals = await generateCircuitInputsRegister(
const registerCircuitInputs: CircuitSignals = await generatePassportRegisterInputs(
secret,
passportData,
serialized_dsc_tree as string,
@@ -181,7 +177,7 @@ export async function generateRegisterIdProof(
export async function generateRegisterAadhaarProof(
secret: string,
//return type of prepareAadhaarTestData
inputs: ReturnType<typeof prepareAadhaarRegisterTestData>["inputs"],
inputs: ReturnType<typeof generateAadhaarRegisterInputs>["inputs"],
): Promise<GenericProofStructStruct> {
const circuitName = "register_aadhaar";
@@ -209,7 +205,7 @@ export async function generateRegisterAadhaarProof(
export async function generateRegisterKycProof(
secret: string,
//return type of prepareAadhaarTestData
inputs: Awaited<ReturnType<typeof generateMockKycRegisterInput>>,
inputs: Awaited<ReturnType<typeof generateMockKycRegisterInputs>>,
): Promise<GenericProofStructStruct> {
const circuitName = "register_kyc";
@@ -235,7 +231,7 @@ export async function generateRegisterKycProof(
}
export async function generateDscProof(passportData: PassportData): Promise<DscCircuitProof> {
const dscCircuitInputs: CircuitSignals = await generateCircuitInputsDSC(passportData, serialized_csca_tree);
const dscCircuitInputs: CircuitSignals = await generatePassportDscInputs(passportData, serialized_csca_tree);
const dscProof = await groth16.fullProve(
dscCircuitInputs,
@@ -283,7 +279,7 @@ export async function generateVcAndDiscloseRawProof(
nameAndYob_smt = smts.nameAndYob_smt;
}
const vcAndDiscloseCircuitInputs: CircuitSignals = generateCircuitInputsVCandDisclose(
const vcAndDiscloseCircuitInputs: CircuitSignals = generatePassportDiscloseInputs(
secret,
attestationId,
passportData,
@@ -467,7 +463,7 @@ export async function generateVcAndDiscloseIdProof(
documentCategory: "id_card" as const,
};
const vcAndDiscloseCircuitInputs: CircuitSignals = generateCircuitInputsVCandDisclose(
const vcAndDiscloseCircuitInputs: CircuitSignals = generatePassportDiscloseInputs(
secret,
attestationId,
idCardPassportData,
@@ -504,7 +500,7 @@ export async function generateVcAndDiscloseIdProof(
}
export async function generateVcAndDiscloseAadhaarProof(
inputs: ReturnType<typeof prepareAadhaarDiscloseTestData>["inputs"],
inputs: ReturnType<typeof generateAadhaarDiscloseInputs>["inputs"],
): Promise<GenericProofStructStruct> {
const circuitName = "vc_and_disclose_aadhaar";
@@ -530,7 +526,7 @@ export async function generateVcAndDiscloseAadhaarProof(
}
export async function generateVcAndDiscloseKycProof(
inputs: ReturnType<typeof generateKycDiscloseInput>,
inputs: ReturnType<typeof generateKycDiscloseInputFromDummy>,
): Promise<GenericProofStructStruct> {
const circuitName = "vc_and_disclose_kyc";
const circuitArtifacts = vcAndDiscloseCircuitsKyc;

View File

@@ -1,5 +1,5 @@
import { Signer } from "ethers";
import type { PassportData } from "@selfxyz/common/utils/types";
import type { PassportData } from "@selfxyz/new-common/src/foundation/types/document";
import type { PublicSignals, Groth16Proof } from "snarkjs";
@@ -37,7 +37,7 @@ import {
IdentityRegistryKycImplV1,
} from "../../typechain-types";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/new-common/src/foundation/constants/identity";
export type PassportProof = IIdentityVerificationHubV1.PassportProofStruct;
export type RegisterCircuitProof = IRegisterCircuitVerifier.RegisterCircuitProofStruct;

View File

@@ -3,16 +3,21 @@ import { ethers } from "hardhat";
import { generateVcAndDiscloseAadhaarProof, getSMTs } from "../utils/generateProof";
import { poseidon2 } from "poseidon-lite";
import { BigNumberish } from "ethers";
import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts/forbiddenCountries";
import { Country3LetterCode } from "@selfxyz/common/constants/countries";
import { getPackedForbiddenCountries } from "@selfxyz/new-common/src/blockchain/forbiddenCountries";
import { Country3LetterCode } from "@selfxyz/new-common/src/data/countries";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { AADHAAR_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
import { calculateUserIdentifierHash } from "@selfxyz/common";
import { prepareAadhaarDiscloseTestData } from "@selfxyz/common";
import { AADHAAR_ATTESTATION_ID } from "@selfxyz/new-common/src/foundation/constants/identity";
import { calculateUserIdentifierHash } from "@selfxyz/new-common/src/crypto/identity";
import path from "path";
import { createSelector } from "@selfxyz/common/utils/aadhaar/constants";
import { formatInput } from "@selfxyz/common/utils/circuits/generateInputs";
import { createSelector } from "@selfxyz/new-common/src/documents/aadhaar/constants";
import { formatInput } from "@selfxyz/new-common/src/circuits/inputs/format";
import { generateAadhaarDiscloseInputs } from "@selfxyz/new-common/src/circuits/inputs/disclose-aadhaar";
import {
testDefaultQRData,
generateTestData,
testCustomData,
} from "@selfxyz/new-common/src/testing/genMockAadhaarData";
import fs from "fs";
const privateKeyPem = fs.readFileSync(
@@ -70,31 +75,26 @@ describe("Self Verification Flow V2 - Aadhaar", () => {
const actualScope = await deployedActors.testSelfVerificationRoot.scope();
scopeAsBigIntString = actualScope.toString();
const testData = prepareAadhaarDiscloseTestData(
privateKeyPem,
tree,
nameAndDob_smt,
nameAndYob_smt,
scopeAsBigIntString,
registerSecret,
userIdentifierHash.toString(),
createSelector([
"GENDER",
"NAME",
"YEAR_OF_BIRTH",
"MONTH_OF_BIRTH",
"DAY_OF_BIRTH",
"AADHAAR_LAST_4_DIGITS",
"STATE",
]).toString(),
const customQRData = generateTestData({
privKeyPem: privateKeyPem,
data: testCustomData,
name,
dateOfBirth,
dob: dateOfBirth,
gender,
pincode,
state,
undefined,
true,
);
});
const testData = generateAadhaarDiscloseInputs(customQRData.testQRData, registerSecret, {
merkletree: tree,
nameAndDob_smt: nameAndDob_smt,
nameAndYob_smt: nameAndYob_smt,
scope: scopeAsBigIntString,
fieldsToReveal: ["gender", "name", "date_of_birth", "id_number", "issuing_state"],
user_identifier: userIdentifierHash.toString(),
minimumAge: 0,
updateTree: true,
});
const aadhaarInputs = testData.inputs;
nullifier = testData.nullifier;
@@ -333,23 +333,26 @@ describe("Self Verification Flow V2 - Aadhaar", () => {
const differentActualScope = await differentScopeContract.scope();
const differentScopeAsBigIntString = differentActualScope.toString();
const aadhaarInputs = prepareAadhaarDiscloseTestData(
privateKeyPem,
tree,
nameAndDob_smt,
nameAndYob_smt,
differentScopeAsBigIntString,
registerSecret,
"123",
createSelector(["GENDER"]).toString(),
const customQRData = generateTestData({
privKeyPem: privateKeyPem,
data: testCustomData,
name,
dateOfBirth,
dob: dateOfBirth,
gender,
pincode,
state,
undefined,
true,
);
});
const aadhaarInputs = generateAadhaarDiscloseInputs(customQRData.testQRData, registerSecret, {
merkletree: tree,
nameAndDob_smt: nameAndDob_smt,
nameAndYob_smt: nameAndYob_smt,
scope: differentScopeAsBigIntString,
fieldsToReveal: ["gender"],
user_identifier: "123",
minimumAge: 0,
updateTree: true,
});
const differentScopeProof = await generateVcAndDiscloseAadhaarProof(aadhaarInputs.inputs);
@@ -498,23 +501,26 @@ describe("Self Verification Flow V2 - Aadhaar", () => {
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
const imt = new LeanIMT<bigint>(hashFunction, []);
const aadhaarInputs = prepareAadhaarDiscloseTestData(
privateKeyPem,
imt,
nameAndDob_smt,
nameAndYob_smt,
scopeAsBigIntString,
registerSecret,
userIdentifierHash.toString(),
createSelector(["GENDER"]).toString(),
const customQRData2 = generateTestData({
privKeyPem: privateKeyPem,
data: testCustomData,
name,
dateOfBirth,
dob: dateOfBirth,
gender,
pincode,
state,
undefined,
true,
);
});
const aadhaarInputs = generateAadhaarDiscloseInputs(customQRData2.testQRData, registerSecret, {
merkletree: imt,
nameAndDob_smt: nameAndDob_smt,
nameAndYob_smt: nameAndYob_smt,
scope: scopeAsBigIntString,
fieldsToReveal: ["gender"],
user_identifier: userIdentifierHash.toString(),
minimumAge: 0,
updateTree: true,
});
aadhaarInputs.inputs.currentDay = formatInput((+aadhaarInputs.inputs.currentDay[0] + 1).toString());
@@ -573,23 +579,26 @@ describe("Self Verification Flow V2 - Aadhaar", () => {
const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]);
const imt = new LeanIMT<bigint>(hashFunction, []);
const aadhaarInputs = prepareAadhaarDiscloseTestData(
privateKeyPem,
imt,
nameAndDob_smt,
nameAndYob_smt,
scopeAsBigIntString,
registerSecret,
userIdentifierHash.toString(),
createSelector(["GENDER"]).toString(),
const customQRData3 = generateTestData({
privKeyPem: privateKeyPem,
data: testCustomData,
name,
dateOfBirth,
dob: dateOfBirth,
gender,
pincode,
state,
undefined,
true,
);
});
const aadhaarInputs = generateAadhaarDiscloseInputs(customQRData3.testQRData, registerSecret, {
merkletree: imt,
nameAndDob_smt: nameAndDob_smt,
nameAndYob_smt: nameAndYob_smt,
scope: scopeAsBigIntString,
fieldsToReveal: ["gender"],
user_identifier: userIdentifierHash.toString(),
minimumAge: 0,
updateTree: true,
});
const commitment = aadhaarInputs.commitment;
const nullifier = aadhaarInputs.nullifier;
@@ -893,23 +902,26 @@ describe("Self Verification Flow V2 - Aadhaar", () => {
ofacEnabled: [false, false, false] as [boolean, boolean, boolean],
};
const aadhaarInputs = prepareAadhaarDiscloseTestData(
privateKeyPem,
tree,
nameAndDob_smt,
nameAndYob_smt,
scopeAsBigIntString,
registerSecret,
newUserIdentifierHash.toString(),
createSelector(["GENDER"]).toString(),
const customQRData4 = generateTestData({
privKeyPem: privateKeyPem,
data: testCustomData,
name,
dateOfBirth,
dob: dateOfBirth,
gender,
pincode,
state,
undefined,
true,
);
});
const aadhaarInputs = generateAadhaarDiscloseInputs(customQRData4.testQRData, registerSecret, {
merkletree: tree,
nameAndDob_smt: nameAndDob_smt,
nameAndYob_smt: nameAndYob_smt,
scope: scopeAsBigIntString,
fieldsToReveal: ["gender"],
user_identifier: newUserIdentifierHash.toString(),
minimumAge: 0,
updateTree: true,
});
const commitment = aadhaarInputs.commitment;
const nullifier = aadhaarInputs.nullifier;

View File

@@ -2,17 +2,17 @@ import { expect } from "chai";
import { ethers } from "hardhat";
import { generateVcAndDiscloseIdProof, getSMTs } from "../utils/generateProof";
import { poseidon2 } from "poseidon-lite";
import { generateCommitment } from "@selfxyz/common/utils/passports/passport";
import { generateCommitment } from "@selfxyz/new-common/src/documents/passport/commitment";
import { BigNumberish } from "ethers";
import { generateRandomFieldElement, getStartOfDayTimestamp } from "../utils/utils";
import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts";
import { countries } from "@selfxyz/common/constants/countries";
import { getPackedForbiddenCountries } from "@selfxyz/new-common/src/blockchain/forbiddenCountries";
import { countries } from "@selfxyz/new-common/src/data/countries";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { Country3LetterCode } from "@selfxyz/common/constants/countries";
import { Country3LetterCode } from "@selfxyz/new-common/src/data/countries";
import { createHash } from "crypto";
import { ID_CARD_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
import { genMockIdDocAndInitDataParsing } from "@selfxyz/common/utils/passports/genMockIdDoc";
import { ID_CARD_ATTESTATION_ID } from "@selfxyz/new-common/src/foundation/constants/identity";
import { genMockIdDocAndInitDataParsing } from "@selfxyz/new-common/src/testing/genMockIdDoc";
// Helper function to calculate user identifier hash (same as passport test)
function calculateUserIdentifierHash(userContextData: string): string {

View File

@@ -1,21 +1,18 @@
import {
calculateUserIdentifierHash,
hashEndpointWithScope,
KYC_ID_NUMBER_INDEX,
KYC_ID_NUMBER_LENGTH,
packBytesAndPoseidon,
} from "@selfxyz/common";
import { Country3LetterCode } from "@selfxyz/common/constants/countries";
import { calculateUserIdentifierHash } from "@selfxyz/new-common/src/crypto/identity";
import { hashEndpointWithScope } from "@selfxyz/new-common/src/crypto/scope";
import { KYC_ID_NUMBER_INDEX, KYC_ID_NUMBER_LENGTH } from "@selfxyz/new-common/src/documents/kyc/constants";
import { packBytesAndPoseidon } from "@selfxyz/new-common/src/crypto/hash/poseidon";
import { Country3LetterCode } from "@selfxyz/new-common/src/data/countries";
import { DeployedActorsV2 } from "../utils/types";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { ethers } from "hardhat";
import { expect } from "chai";
import { generateKycDiscloseInput } from "@selfxyz/common";
import { generateKycDiscloseInputFromDummy } from "@selfxyz/new-common/src/circuits/inputs/disclose-kyc";
import { getSMTs } from "../utils/generateProof";
import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts/forbiddenCountries";
import { getPackedForbiddenCountries } from "@selfxyz/new-common/src/blockchain/forbiddenCountries";
import { BigNumberish } from "ethers";
import { generateVcAndDiscloseKycProof } from "../utils/generateProof";
import { KYC_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
import { KYC_ATTESTATION_ID } from "@selfxyz/new-common/src/foundation/constants/identity";
import { poseidon2 } from "poseidon-lite";
// KYC circuit indices - matches CircuitConstantsV2.getDiscloseIndices(KYC_ID_CARD)
@@ -58,7 +55,7 @@ describe("Self Verification Flow V2 - KYC", () => {
const LeanIMT = await import("@openpassport/zk-kit-lean-imt").then((mod) => mod.LeanIMT);
tree = new LeanIMT<bigint>((a, b) => poseidon2([a, b]), []);
const testInputs = generateKycDiscloseInput(
const testInputs = generateKycDiscloseInputFromDummy(
false,
nameAndDob_smt,
nameAndYob_smt,
@@ -718,7 +715,7 @@ describe("Self Verification Flow V2 - KYC", () => {
await deployedActors.testSelfVerificationRoot.setVerificationConfig(verificationConfigV2);
const inputs = generateKycDiscloseInput(
const inputs = generateKycDiscloseInputFromDummy(
false,
nameAndDob_smt,
nameAndYob_smt,

View File

@@ -3,14 +3,14 @@ import { ethers } from "hardhat";
import { ATTESTATION_ID } from "../utils/constants";
import { generateVcAndDiscloseProof, getSMTs } from "../utils/generateProof";
import { poseidon2 } from "poseidon-lite";
import { generateCommitment } from "@selfxyz/common/utils/passports/passport";
import { generateCommitment } from "@selfxyz/new-common/src/documents/passport/commitment";
import { BigNumberish } from "ethers";
import { generateRandomFieldElement, getStartOfDayTimestamp } from "../utils/utils";
import { getPackedForbiddenCountries } from "@selfxyz/common/utils/contracts";
import { countries } from "@selfxyz/common/constants/countries";
import { getPackedForbiddenCountries } from "@selfxyz/new-common/src/blockchain/forbiddenCountries";
import { countries } from "@selfxyz/new-common/src/data/countries";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { Country3LetterCode } from "@selfxyz/common/constants/countries";
import { Country3LetterCode } from "@selfxyz/new-common/src/data/countries";
import { createHash } from "crypto";
// Helper function to format date for passport (YYMMDD format)

View File

@@ -2,8 +2,8 @@ import { expect } from "chai";
import { ethers } from "hardhat";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants/constants";
import { ID_CARD_ATTESTATION_ID, PASSPORT_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/new-common/src/foundation/constants/identity";
import { ID_CARD_ATTESTATION_ID, PASSPORT_ATTESTATION_ID } from "@selfxyz/new-common/src/foundation/constants/identity";
describe("Hub Other Functions Test", function () {
this.timeout(0);

View File

@@ -2,8 +2,10 @@ import { expect } from "chai";
import { ethers } from "hardhat";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { AADHAAR_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
import { prepareAadhaarRegisterTestData } from "@selfxyz/common";
import { AADHAAR_ATTESTATION_ID } from "@selfxyz/new-common/src/foundation/constants/identity";
import { generateAadhaarRegisterInputs } from "@selfxyz/new-common/src/circuits/inputs/register-aadhaar";
import { generateTestData, testCustomData } from "@selfxyz/new-common/src/testing/genMockAadhaarData";
import forge from "node-forge";
import path from "path";
import { generateRandomFieldElement } from "../utils/utils";
import { generateRegisterAadhaarProof } from "../utils/generateProof";
@@ -59,16 +61,28 @@ describe("Aadhaar Registration test", function () {
let registerSecret: string;
before(async () => {
aadhaarData = prepareAadhaarRegisterTestData(
privateKeyPem,
pubkeyPem,
"1234",
"Sumit Kumar",
"01-01-1984",
"M",
"110051",
"WB",
);
const customQRData = generateTestData({
privKeyPem: privateKeyPem,
data: testCustomData,
name: "Sumit Kumar",
dob: "01-01-1984",
gender: "M",
pincode: "110051",
state: "WB",
});
const pubKey = forge.pki.publicKeyFromPem(pubkeyPem);
const modulusHex = (pubKey as forge.pki.rsa.PublicKey).n.toString(16);
const pubKeyBigInt = BigInt("0x" + modulusHex);
const qrDataBigInt = BigInt(customQRData.testQRData);
const { convertBigIntToByteArray, decompressByteArray } = await import("@anon-aadhaar/core");
const qrDataBytes = convertBigIntToByteArray(qrDataBigInt);
const decodedData = decompressByteArray(qrDataBytes);
const signatureBytes = decodedData.slice(decodedData.length - 256, decodedData.length);
const signature = BigInt("0x" + Buffer.from(signatureBytes).toString("hex"));
aadhaarData = generateAadhaarRegisterInputs(customQRData.testQRData, "1234", { pubKey: pubKeyBigInt, signature });
registerSecret = generateRandomFieldElement();
@@ -154,17 +168,32 @@ describe("Aadhaar Registration test", function () {
const latestBlock = await ethers.provider.getBlock("latest");
const blockTimestamp = latestBlock!.timestamp;
const newAadhaarData = prepareAadhaarRegisterTestData(
privateKeyPem,
pubkeyPem,
"1234",
"Sumit Kumar",
"01-01-1984",
"M",
"110051",
"WB",
(blockTimestamp - 10 * 60).toString(),
);
const customQRData = generateTestData({
privKeyPem: privateKeyPem,
data: testCustomData,
name: "Sumit Kumar",
dob: "01-01-1984",
gender: "M",
pincode: "110051",
state: "WB",
timestamp: ((blockTimestamp - 10 * 60) * 1000).toString(),
});
const pubKey = forge.pki.publicKeyFromPem(pubkeyPem);
const modulusHex = (pubKey as forge.pki.rsa.PublicKey).n.toString(16);
const pubKeyBigInt = BigInt("0x" + modulusHex);
const qrDataBigInt = BigInt(customQRData.testQRData);
const { convertBigIntToByteArray, decompressByteArray } = await import("@anon-aadhaar/core");
const qrDataBytes = convertBigIntToByteArray(qrDataBigInt);
const decodedData = decompressByteArray(qrDataBytes);
const signatureBytes = decodedData.slice(decodedData.length - 256, decodedData.length);
const signature = BigInt("0x" + Buffer.from(signatureBytes).toString("hex"));
const newAadhaarData = generateAadhaarRegisterInputs(customQRData.testQRData, "1234", {
pubKey: pubKeyBigInt,
signature,
});
const newRegisterProof = await generateRegisterAadhaarProof(registerSecret, newAadhaarData.inputs);
await expect(deployedActors.hub.registerCommitment(attestationIdBytes32, 0n, newRegisterProof)).to.not.be
@@ -177,17 +206,32 @@ describe("Aadhaar Registration test", function () {
const latestBlock = await ethers.provider.getBlock("latest");
const blockTimestamp = latestBlock!.timestamp;
const newAadhaarData = prepareAadhaarRegisterTestData(
privateKeyPem,
pubkeyPem,
"1234",
"Sumit Kumar",
"01-01-1984",
"M",
"110051",
"WB",
(blockTimestamp - 30 * 60).toString(),
);
const customQRData = generateTestData({
privKeyPem: privateKeyPem,
data: testCustomData,
name: "Sumit Kumar",
dob: "01-01-1984",
gender: "M",
pincode: "110051",
state: "WB",
timestamp: (blockTimestamp - 30 * 60).toString(),
});
const pubKey = forge.pki.publicKeyFromPem(pubkeyPem);
const modulusHex = (pubKey as forge.pki.rsa.PublicKey).n.toString(16);
const pubKeyBigInt = BigInt("0x" + modulusHex);
const qrDataBigInt = BigInt(customQRData.testQRData);
const { convertBigIntToByteArray, decompressByteArray } = await import("@anon-aadhaar/core");
const qrDataBytes = convertBigIntToByteArray(qrDataBigInt);
const decodedData = decompressByteArray(qrDataBytes);
const signatureBytes = decodedData.slice(decodedData.length - 256, decodedData.length);
const signature = BigInt("0x" + Buffer.from(signatureBytes).toString("hex"));
const newAadhaarData = generateAadhaarRegisterInputs(customQRData.testQRData, "1234", {
pubKey: pubKeyBigInt,
signature,
});
const newRegisterProof = await generateRegisterAadhaarProof(registerSecret, newAadhaarData.inputs);
await expect(

View File

@@ -4,14 +4,15 @@ import { generateRandomFieldElement } from "../utils/utils";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { generateDscProof, generateRegisterIdProof } from "../utils/generateProof";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants/constants";
import serialized_dsc_tree from "@selfxyz/common/pubkeys/serialized_dsc_tree.json";
import {
CIRCUIT_CONSTANTS,
DscVerifierId,
RegisterVerifierId,
ID_CARD_ATTESTATION_ID,
PASSPORT_ATTESTATION_ID,
} from "@selfxyz/common/constants/constants";
import { genMockIdDocAndInitDataParsing } from "@selfxyz/common/utils/passports/genMockIdDoc";
} from "@selfxyz/new-common/src/foundation/constants/identity";
import serialized_dsc_tree from "@selfxyz/new-common/src/data/serialized_dsc_tree.json";
import { CIRCUIT_CONSTANTS } from "@selfxyz/new-common/src/foundation/constants/circuit";
import { genMockIdDocAndInitDataParsing } from "@selfxyz/new-common/src/testing/genMockIdDoc";
describe("ID Registration test", function () {
this.timeout(0);

View File

@@ -1,8 +1,8 @@
import { ethers } from "hardhat";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { KYC_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
import { generateMockKycRegisterInput } from "@selfxyz/common/utils/kyc/generateInputs";
import { KYC_ATTESTATION_ID } from "@selfxyz/new-common/src/foundation/constants/identity";
import { generateMockKycRegisterInputs } from "@selfxyz/new-common/src/circuits/inputs/register-kyc";
import { generateRegisterKycProof } from "../utils/generateProof";
import { expect } from "chai";
@@ -87,7 +87,7 @@ describe("KYC Registration test", function () {
before(async () => {
registerSecret = "12345";
kycData = await generateMockKycRegisterInput(undefined, true, registerSecret);
kycData = await generateMockKycRegisterInputs(undefined, true, registerSecret);
registerProof = await generateRegisterKycProof(registerSecret, kycData);
// Deploy and set mock GCP JWT verifier

View File

@@ -4,10 +4,14 @@ import { generateRandomFieldElement } from "../utils/utils";
import { deploySystemFixturesV2 } from "../utils/deploymentV2";
import { DeployedActorsV2 } from "../utils/types";
import { generateDscProof, generateRegisterProof } from "../utils/generateProof";
import { DscVerifierId, RegisterVerifierId } from "@selfxyz/common/constants/constants";
import serialized_dsc_tree from "@selfxyz/common/pubkeys/serialized_dsc_tree.json";
import { CIRCUIT_CONSTANTS, PASSPORT_ATTESTATION_ID } from "@selfxyz/common/constants/constants";
import { genMockIdDocAndInitDataParsing } from "@selfxyz/common/utils/passports/genMockIdDoc";
import {
DscVerifierId,
RegisterVerifierId,
PASSPORT_ATTESTATION_ID,
} from "@selfxyz/new-common/src/foundation/constants/identity";
import serialized_dsc_tree from "@selfxyz/new-common/src/data/serialized_dsc_tree.json";
import { CIRCUIT_CONSTANTS } from "@selfxyz/new-common/src/foundation/constants/circuit";
import { genMockIdDocAndInitDataParsing } from "@selfxyz/new-common/src/testing/genMockIdDoc";
describe("Passport Registration test", function () {
this.timeout(0);

View File

@@ -1,12 +1,17 @@
{
"compilerOptions": {
"target": "es2020",
"module": "node16",
"moduleResolution": "Node16",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@selfxyz/new-common/src/*": ["../new-common/src/*"],
"@selfxyz/new-common": ["../new-common/src/index.ts"]
}
}
}

13
new-common/.prettierrc Normal file
View File

@@ -0,0 +1,13 @@
{
"arrowParens": "avoid",
"bracketSameLine": false,
"bracketSpacing": true,
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"useTabs": false,
"semi": true,
"endOfLine": "auto",
"printWidth": 100,
"parser": "typescript"
}

63
new-common/package.json Normal file
View File

@@ -0,0 +1,63 @@
{
"name": "@selfxyz/new-common",
"version": "0.0.1",
"description": "Restructured constants and utils for self sdks",
"license": "MIT",
"author": "@Selfxyz Team",
"type": "module",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.cjs",
"types": "./dist/esm/index.d.ts"
},
"./src/*": {
"import": "./dist/esm/src/*.js",
"require": "./dist/cjs/src/*.cjs",
"types": "./dist/esm/src/*.d.ts"
},
"./src/data/*.json": "./src/data/*.json"
},
"main": "./dist/cjs/index.cjs",
"module": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts",
"scripts": {
"build": "tsup && yarn build:types",
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
"format": "npx prettier --write 'src/**/*.ts'",
"format:check": "npx prettier --check 'src/**/*.ts'",
"test": "vitest run",
"types": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@anon-aadhaar/core": "npm:@selfxyz/anon-aadhaar-core@^0.0.1",
"@openpassport/zk-kit-imt": "^0.0.5",
"@openpassport/zk-kit-lean-imt": "^0.0.6",
"@openpassport/zk-kit-smt": "^0.0.1",
"@zk-kit/baby-jubjub": "^1.0.3",
"@zk-kit/eddsa-poseidon": "^1.1.0",
"asn1js": "^3.0.7",
"elliptic": "^6.5.5",
"ethers": "^6.13.4",
"hash.js": "^1.1.7",
"i18n-iso-countries": "^7.13.0",
"js-sha1": "^0.7.0",
"js-sha256": "^0.11.0",
"js-sha512": "^0.9.0",
"node-forge": "^1.3.1",
"pkijs": "^3.3.3",
"poseidon-lite": "^0.3.0",
"uuid": "^13.0.0"
},
"devDependencies": {
"@types/elliptic": "^6.4.18",
"@types/node-forge": "^1.3.11",
"@types/uuid": "^11.0.0",
"tsup": "^8.5.0",
"typescript": "^5.9.3",
"vitest": "^2.1.8"
},
"engines": {
"node": ">=22 <23"
}
}

Some files were not shown because too many files have changed in this diff Show More