mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
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:
@@ -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
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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)
|
||||
25
.github/actions/find-ios-simulator/action.yml
vendored
Normal file
25
.github/actions/find-ios-simulator/action.yml
vendored
Normal 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"
|
||||
206
.github/workflows/kmp-ci.yml
vendored
206
.github/workflows/kmp-ci.yml
vendored
@@ -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
|
||||
|
||||
316
.github/workflows/mobile-ci.yml
vendored
316
.github/workflows/mobile-ci.yml
vendored
@@ -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
|
||||
|
||||
5
.github/workflows/mobile-deploy.yml
vendored
5
.github/workflows/mobile-deploy.yml
vendored
@@ -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 }}
|
||||
|
||||
59
.github/workflows/mobile-sdk-demo-e2e.yml
vendored
59
.github/workflows/mobile-sdk-demo-e2e.yml
vendored
@@ -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
113
.github/workflows/rn-sdk-test-app-ci.yml
vendored
Normal 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
45
.github/workflows/swift-sdk-ci.yml
vendored
Normal 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
44
.github/workflows/webview-app-ci.yml
vendored
Normal 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
48
.github/workflows/webview-bridge-ci.yml
vendored
Normal 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
9
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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)$''',
|
||||
|
||||
26
AGENTS.md
26
AGENTS.md
@@ -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
|
||||
|
||||
|
||||
63
CLAUDE.md
63
CLAUDE.md
@@ -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:** 1k–3k LOC changed. Smaller is fine for focused fixes. If >3k, add a brief justification for why it can’t 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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
{
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
113
app/ios/MrzOcrCorrection.swift
Normal file
113
app/ios/MrzOcrCorrection.swift
Normal 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
|
||||
}
|
||||
}
|
||||
28
app/ios/MrzResultMapper.swift
Normal file
28
app/ios/MrzResultMapper.swift
Normal 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
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
6
app/src/assets/icons/google.svg
Normal file
6
app/src/assets/icons/google.svg
Normal 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 |
@@ -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;
|
||||
|
||||
@@ -71,6 +71,7 @@ export type AppRoutesParamList = {
|
||||
export type DevRoutesParamList = {
|
||||
CreateMock: undefined;
|
||||
MockDataDeepLink: undefined;
|
||||
SocialLoginDemo: undefined;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
||||
388
app/src/screens/dev/SocialLoginDemoScreen.tsx
Normal file
388
app/src/screens/dev/SocialLoginDemoScreen.tsx
Normal 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;
|
||||
@@ -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' }}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -111,6 +111,7 @@ describe('navigation', () => {
|
||||
'SaveRecoveryPhrase',
|
||||
'Settings',
|
||||
'ShowRecoveryPhrase',
|
||||
'SocialLoginDemo',
|
||||
'Splash',
|
||||
'StarfallPushCode',
|
||||
'WebView',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]']);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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]']);
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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-----`,
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
// });
|
||||
// });
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
143
contracts/test/utils/contractUtils.ts
Normal file
143
contracts/test/utils/contractUtils.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
13
new-common/.prettierrc
Normal 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
63
new-common/package.json
Normal 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
Reference in New Issue
Block a user