Self sdk and playground (#210)

Co-authored-by: nicoshark <i.am.nicoshark@gmail.com>
This commit is contained in:
turboblitz
2025-02-25 12:08:01 -08:00
committed by GitHub
parent 0aff8628fc
commit 51e2fc15fa
23 changed files with 1575 additions and 4178 deletions

View File

@@ -1,199 +1,245 @@
# How to use this SDK
## Install
You can install with this command
```
npm i @selfxyz/core
# @selfxyz/core
SDK for verifying passport proofs from Self.
## Installation
You can install with this command:
```bash
npm install @selfxyz/core
# or
yarn add @selfxyz/core
```
## Initialize
You should have CELO_RPC_URL and SCOPE in your environment or somewhere in your code.
## Initialization
Initialize the verifier with your RPC URL and application scope:
```typescript
import { SelfBackendVerifier } from "@selfxyz/core";
const selfBackendVerifier = new SelfBackendVerifier(
process.env.CELO_RPC_URL as string,
process.env.SCOPE as string,
process.env.CELO_RPC_URL as string, // e.g., 'https://forno.celo.org'
process.env.SCOPE as string, // Your application's unique scope. Should be the same as when initializing SelfApp
);
```
## Setup
You can setup which data you want to verify in this sdk
## Configuration
You can configure which verification rules to apply:
```typescript
// Set minimum age verification
// Set minimum age verification (must be between 10-100)
selfBackendVerifier.setMinimumAge(20);
// Set nationality verification
selfBackendVerifier.setNationality('France')
// Set exclude countries verification
// At most 40
selfBackendVerifier.excludeCountries('Country Name1', 'Country Name2', 'Coutry Name3', 'etc...');
// Enable if you want to do passport number ofac check
// Default false
selfBackendVerifier.setNationality('France');
// Set excluded countries verification (max 40 countries)
selfBackendVerifier.excludeCountries('Iran', 'North Korea', 'Russia', 'Syria');
// Enable passport number OFAC check (default: false)
selfBackendVerifier.enablePassportNoOfacCheck();
// Enable if you want to do name and date of birth ofac check
// Default false
// Enable name and date of birth OFAC check (default: false)
selfBackendVerifier.enableNameAndDobOfacCheck();
// Enable if you want to do name and year of birth ofac check
// Default false
// Enable name and year of birth OFAC check (default: false)
selfBackendVerifier.enableNameAndYobOfacCheck();
```
## Verification
You can do the verification with this
Verify a proof with the received proof and public signals:
```typescript
const result = await selfBackendVerifier.verify(
request.body.proof,
request.body.publicSignals
);
const result = await selfBackendVerifier.verify(proof, publicSignals);
```
## Result
Result from the verify function is like this
## Extracting User Identifier
You can extract the user identifier from the public signals:
```typescript
import { getUserIdentifier } from "@selfxyz/core";
const userId = await getUserIdentifier(publicSignals);
```
This allows linking proofs with verification requests generated by `@selfxyz/qrcode`.
## Verification Result
The `verify` method returns a detailed verification result:
```typescript
export interface SelfVerificationResult {
// Check if the whole verification is succeeded
isValid: boolean;
isValidDetails: {
// Verifies that the proof is generated under the expected scope.
isValidScope: boolean;
// Checks that the attestation identifier in the proof matches the expected value.
isValidAttestationId: boolean;
// Verifies the cryptographic validity of the proof.
isValidProof: boolean;
// Ensures that the revealed nationality is correct (when nationality verification is enabled).
isValidNationality: boolean;
};
// User Identifier which is included in the proof
userId: string;
// Application name which is showed as scope
application: string;
// A cryptographic value used to prevent double registration or reuse of the same proof.
nullifier: string;
// Revealed data by users
credentialSubject: {
// Merkle root which is used to generate proof.
merkle_root?: string;
// Proved identity type, for passport this value is fixed as 1.
attestation_id?: string;
// Date when the proof is generated
current_date?: string;
// Revealed issuing state in the passport
issuing_state?: string;
// Revealed name in the passport
name?: string;
// Revealed passport number in the passport
passport_number?: string;
// Revealed nationality in the passport
nationality?: string;
// Revealed date of birth in the passport
date_of_birth?: string;
// Revealed gender in the passport
gender?: string;
// Revealed expiry date in the passport
expiry_date?: string;
// Result of older than
older_than?: string;
// Result of passport number ofac check
passport_no_ofac?: string;
// Result of name and date of birth ofac check
name_and_dob_ofac?: string;
// Result of name and year of birth ofac check
name_and_yob_ofac?: string;
};
proof: {
// Proof which is used for this verification
value: {
proof: Groth16Proof;
publicSignals: PublicSignals;
};
// Overall verification status
isValid: boolean;
// Detailed validation statuses
isValidDetails: {
isValidScope: boolean; // Proof was generated for the expected scope
isValidAttestationId: boolean; // Attestation ID matches expected value
isValidProof: boolean; // Cryptographic validity of the proof
isValidNationality: boolean; // Nationality check (when enabled)
};
// User identifier from the proof
userId: string;
// Application scope
application: string;
// Cryptographic nullifier to prevent reuse
nullifier: string;
// Revealed data from the passport
credentialSubject: {
merkle_root?: string; // Merkle root used for proof generation
attestation_id?: string; // Identity type (1 for passport)
current_date?: string; // Proof generation timestamp
issuing_state?: string; // Passport issuing country
name?: string; // User's name
passport_number?: string; // Passport number
nationality?: string; // User's nationality
date_of_birth?: string; // Date of birth
gender?: string; // Gender
expiry_date?: string; // Passport expiry date
older_than?: string; // Age verification result
passport_no_ofac?: boolean; // Passport OFAC check result.
// Gives true if the user passed the check (is not on the list),
// false if the check was not requested or if the user is in the list
name_and_dob_ofac?: boolean; // Name and DOB OFAC check result
name_and_yob_ofac?: boolean; // Name and birth year OFAC check result
};
// Original proof data
proof: {
value: {
proof: any;
publicSignals: any;
};
};
// Error information if verification failed
error?: any;
}
```
## How to return the result in your api implementation
This backend SDK is designed to be used with APIs managed by third parties, and it communicates with Self's managed relayer to enable smooth usage of applications provided by Self.
## API Implementation Example
When using it:
1. Set the endpoint of the API that imports this backend SDK in SelfAppBuilder
```typescript
const selfApp = new SelfAppBuilder({
appName: "Application name",
scope: "Application id",
endpoint: "API endpoint which imports this backend sdk",
logoBase64: logo,
userId,
disclosures: {
name: true,
nationality: true,
date_of_birth: true,
passport_number: true,
minimumAge: 20,
excludedCountries: [
"Exclude countries which you want"
],
ofac: true,
}
}).build();
```
2. This API needs to return values in the following format:
```typescript
response: {
200: t.Object({
status: t.String(),
result: t.Boolean(),
}),
500: t.Object({
status: t.String(),
result: t.Boolean(),
message: t.String(),
}),
},
```
Bit more explanation to the values in the return value.
status: Indicates that the API processing has completed successfully
result: Contains the verification result from the SelfBackendVerifier
message: Represents the error details when an error occurs
Here is the little example to implement the api.
Here's an example of implementing an API endpoint that uses the SDK:
```typescript
try {
const result = await selfBackendVerifier.verify(
request.body.proof,
request.body.publicSignals
);
return {
status: "success",
result: result.isValid,
};
} catch (error) {
return {
status: "error",
import { NextApiRequest, NextApiResponse } from 'next';
import { getUserIdentifier, SelfBackendVerifier, countryCodes } from '@selfxyz/core';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
try {
const { proof, publicSignals } = req.body;
if (!proof || !publicSignals) {
return res.status(400).json({ message: 'Proof and publicSignals are required' });
}
// Extract user ID from the proof
const userId = await getUserIdentifier(publicSignals);
console.log("Extracted userId:", userId);
// Initialize and configure the verifier
const selfBackendVerifier = new SelfBackendVerifier(
'https://forno.celo.org',
'my-application-scope'
);
// Configure verification options
selfBackendVerifier.setMinimumAge(18);
selfBackendVerifier.excludeCountries(
countryCodes.IRN, // Iran
countryCodes.PRK // North Korea
);
selfBackendVerifier.enableNameAndDobOfacCheck();
// Verify the proof
const result = await selfBackendVerifier.verify(proof, publicSignals);
if (result.isValid) {
// Return successful verification response
return res.status(200).json({
status: 'success',
result: true,
credentialSubject: result.credentialSubject
});
} else {
// Return failed verification response
return res.status(400).json({
status: 'error',
result: false,
message: 'Verification failed',
details: result.isValidDetails
});
}
} catch (error) {
console.error('Error verifying proof:', error);
return res.status(500).json({
status: 'error',
result: false,
message: error instanceof Error ? error.message : "Unknown error",
};
message: error instanceof Error ? error.message : 'Unknown error'
});
}
} else {
return res.status(405).json({ message: 'Method not allowed' });
}
}
```
# When you run the tests
## Working with Country Codes
First you need to copy the abi files to the sdk/core/src/abi folder.
The SDK provides a `countryCodes` object for referencing ISO country codes:
```
cd ../sdk/core
yarn run copy-abi
```typescript
import { countryCodes } from '@selfxyz/core';
// Examples of usage
const iranCode = countryCodes.IRN; // "Iran"
const northKoreaCode = countryCodes.PRK; // "North Korea"
// Use in excludeCountries
selfBackendVerifier.excludeCountries(
countryCodes.IRN,
countryCodes.PRK,
countryCodes.SYR
);
```
Then you need to run the local hardhat node.
## Integration with SelfQRcode
```
cd contracts
npx hardhat node
This backend SDK is designed to work with the `@selfxyz/qrcode` package. When configuring your QR code, set the verification endpoint to point to your API that uses this SDK:
```typescript
import { SelfAppBuilder } from '@selfxyz/qrcode';
const selfApp = new SelfAppBuilder({
appName: 'My Application',
scope: 'my-application-scope',
endpoint: 'https://my-api.com/api/verify', // Your API using SelfBackendVerifier
logoBase64: myLogoBase64,
userId,
disclosures: {
name: true,
nationality: true,
date_of_birth: true,
passport_number: true,
minimumAge: 20,
excludedCountries: ["IRN", "PRK"],
ofac: true,
},
}).build();
```
Then you need to run the tests in the contract dir.
## Example
```
yarn run test:sdkcore:local
```
For a more advanced implementation example, see the [playground](https://github.com/selfxyz/playground/blob/main/pages/api/verify.ts).

View File

@@ -1,3 +1,5 @@
import { SelfBackendVerifier } from './src/SelfBackendVerifier';
import { getUserIdentifier } from './src/utils/utils';
import { countryCodes } from '../../common/src/constants/constants';
export { SelfBackendVerifier };
export { SelfBackendVerifier, getUserIdentifier, countryCodes };

View File

@@ -1,9 +1,9 @@
{
"name": "@openpassport/core",
"version": "0.0.24",
"name": "@selfxyz/core",
"version": "0.0.3",
"repository": {
"type": "git",
"url": "https://github.com/zk-passport/openpassport"
"url": "https://github.com/celo-org/self"
},
"license": "MIT",
"author": "turnoffthiscomputer",
@@ -21,31 +21,15 @@
"install-sdk": "cd ../common && yarn && cd ../sdk && yarn",
"lint": "prettier --check .",
"prepublishOnly": "npm run build",
"publish": "npm publish --access public",
"test": "yarn ts-mocha -p ./tsconfig.json tests/openPassportVerifier.test.ts --exit"
"publish": "npm publish --access public"
},
"dependencies": {
"@openpassport/zk-kit-imt": "^0.0.5",
"@openpassport/zk-kit-lean-imt": "^0.0.6",
"@openpassport/zk-kit-smt": "^0.0.1",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
"elliptic": "^6.5.7",
"ethers": "^6.13.5",
"fs": "^0.0.1-security",
"js-sha1": "^0.7.0",
"js-sha256": "^0.11.0",
"js-sha512": "^0.9.0",
"msgpack-lite": "^0.1.26",
"next": "^14.2.8",
"node-forge": "https://github.com/remicolin/forge",
"pako": "^2.1.0",
"pkijs": "^3.2.4",
"poseidon-lite": "^0.2.0",
"snarkjs": "^0.7.4",
"uuid": "^10.0.0",
"zlib": "^1.0.5"
"snarkjs": "^0.7.4"
},
"devDependencies": {
"@types/chai": "^4.3.6",
@@ -57,15 +41,9 @@
"@types/node-forge": "^1.3.5",
"@types/pako": "^2.0.3",
"@types/snarkjs": "^0.7.8",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
"chai": "^4.3.8",
"chai-as-promised": "^7.1.1",
"dotenv": "^16.4.5",
"mocha": "^10.3.0",
"prettier": "^3.3.3",
"ts-loader": "^9.5.1",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},

View File

@@ -5,28 +5,39 @@ import { ethers } from 'ethers';
import { PublicSignals } from 'snarkjs';
import {
countryCodes,
countryNames,
getCountryCode,
} from '../../../common/src/constants/constants';
import type { SelfVerificationResult } from '../../../common/src/utils/selfAttestation';
import { castToScope } from '../../../common/src/utils/circuits/uuid';
import { castToScope, castToUserIdentifier, UserIdType } from '../../../common/src/utils/circuits/uuid';
import { CIRCUIT_CONSTANTS, revealedDataTypes } from '../../../common/src/constants/constants';
import { packForbiddenCountriesList } from '../../../common/src/utils/contracts/formatCallData';
type CountryCode = (typeof countryCodes)[keyof typeof countryCodes];
export class SelfBackendVerifier {
protected scope: string;
protected attestationId: number = 1;
protected user_identifier_type: UserIdType = 'uuid';
protected targetRootTimestamp: { enabled: boolean; value: number } = {
enabled: false,
value: 0,
};
protected nationality: { enabled: boolean; value: (typeof countryNames)[number] } = {
protected nationality: {
enabled: boolean;
value: CountryCode;
} = {
enabled: false,
value: '' as (typeof countryNames)[number],
value: '' as CountryCode,
};
protected minimumAge: { enabled: boolean; value: string } = { enabled: false, value: '18' };
protected excludedCountries: { enabled: boolean; value: (typeof countryNames)[number][] } = {
protected minimumAge: { enabled: boolean; value: string } = {
enabled: false,
value: '18',
};
protected excludedCountries: {
enabled: boolean;
value: CountryCode[];
} = {
enabled: false,
value: [],
};
@@ -34,20 +45,22 @@ export class SelfBackendVerifier {
protected nameAndDobOfac: boolean = false;
protected nameAndYobOfac: boolean = false;
protected registryContract: any;
protected verifyAllContract: any;
protected registryContract: ethers.Contract;
protected verifyAllContract: ethers.Contract;
constructor(rpcUrl: string, scope: string) {
constructor(
rpcUrl: string,
scope: string,
user_identifier_type: UserIdType = 'uuid'
) {
const provider = new ethers.JsonRpcProvider(rpcUrl);
this.registryContract = new ethers.Contract(REGISTRY_ADDRESS, registryAbi, provider);
this.verifyAllContract = new ethers.Contract(VERIFYALL_ADDRESS, verifyAllAbi, provider);
this.scope = scope;
this.user_identifier_type = user_identifier_type;
}
public async verify(
proof: any,
publicSignals: PublicSignals
): Promise<SelfVerificationResult> {
public async verify(proof: any, publicSignals: PublicSignals): Promise<SelfVerificationResult> {
const excludedCountryCodes = this.excludedCountries.value.map((country) =>
getCountryCode(country)
);
@@ -69,7 +82,10 @@ export class SelfBackendVerifier {
ofacEnabled: [this.passportNoOfac, this.nameAndDobOfac, this.nameAndYobOfac],
vcAndDiscloseProof: {
a: proof.a,
b: [[proof.b[0][1], proof.b[0][0]],[proof.b[1][1], proof.b[1][0]]],
b: [
[proof.b[0][1], proof.b[0][0]],
[proof.b[1][1], proof.b[1][0]],
],
c: proof.c,
pubSignals: publicSignals,
},
@@ -92,6 +108,11 @@ export class SelfBackendVerifier {
const currentRoot = await this.registryContract.getIdentityCommitmentMerkleRoot();
const timestamp = await this.registryContract.rootTimestamps(currentRoot);
const user_identifier = castToUserIdentifier(
BigInt(publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX]),
this.user_identifier_type
);
let result: any;
try {
result = await this.verifyAllContract.verifyAll(timestamp, vcAndDiscloseHubProof, types);
@@ -104,18 +125,18 @@ export class SelfBackendVerifier {
isValidProof: false,
isValidNationality: false,
},
userId: publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX],
userId: user_identifier,
application: this.scope,
nullifier: publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NULLIFIER_INDEX],
credentialSubject: null,
credentialSubject: {},
proof: {
value: {
proof: proof,
publicSignals: publicSignals,
},
},
error: error
}
error: error,
};
}
let isValidNationality = true;
@@ -136,10 +157,10 @@ export class SelfBackendVerifier {
date_of_birth: result[0][revealedDataTypes.date_of_birth],
gender: result[0][revealedDataTypes.gender],
expiry_date: result[0][revealedDataTypes.expiry_date],
older_than: result[0][revealedDataTypes.older_than],
passport_no_ofac: result[0][revealedDataTypes.passport_no_ofac],
name_and_dob_ofac: result[0][revealedDataTypes.name_and_dob_ofac],
name_and_yob_ofac: result[0][revealedDataTypes.name_and_yob_ofac],
older_than: result[0][revealedDataTypes.older_than].toString(),
passport_no_ofac: result[0][revealedDataTypes.passport_no_ofac].toString() === '1',
name_and_dob_ofac: result[0][revealedDataTypes.name_and_dob_ofac].toString() === '1',
name_and_yob_ofac: result[0][revealedDataTypes.name_and_yob_ofac].toString() === '1',
};
const attestation: SelfVerificationResult = {
@@ -150,7 +171,7 @@ export class SelfBackendVerifier {
isValidProof: result[1],
isValidNationality: isValidNationality,
},
userId: publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX],
userId: user_identifier,
application: this.scope,
nullifier: publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_NULLIFIER_INDEX],
credentialSubject: credentialSubject,
@@ -160,7 +181,7 @@ export class SelfBackendVerifier {
publicSignals: publicSignals,
},
},
error: result[2]
error: result[2],
};
return attestation;
@@ -177,12 +198,12 @@ export class SelfBackendVerifier {
return this;
}
setNationality(country: (typeof countryNames)[number]): this {
setNationality(country: CountryCode): this {
this.nationality = { enabled: true, value: country };
return this;
}
excludeCountries(...countries: (typeof countryNames)[number][]): this {
excludeCountries(...countries: CountryCode[]): this {
if (countries.length > 40) {
throw new Error('Number of excluded countries cannot exceed 40');
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,294 +1,294 @@
export const verifyAllAbi = [
{
"inputs": [
inputs: [
{
"internalType": "address",
"name": "hubAddress",
"type": "address"
internalType: 'address',
name: 'hubAddress',
type: 'address',
},
{
"internalType": "address",
"name": "registryAddress",
"type": "address"
}
internalType: 'address',
name: 'registryAddress',
type: 'address',
},
],
"stateMutability": "nonpayable",
"type": "constructor"
stateMutability: 'nonpayable',
type: 'constructor',
},
{
"inputs": [
inputs: [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
internalType: 'address',
name: 'owner',
type: 'address',
},
],
"name": "OwnableInvalidOwner",
"type": "error"
name: 'OwnableInvalidOwner',
type: 'error',
},
{
"inputs": [
inputs: [
{
"internalType": "address",
"name": "account",
"type": "address"
}
internalType: 'address',
name: 'account',
type: 'address',
},
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
name: 'OwnableUnauthorizedAccount',
type: 'error',
},
{
"anonymous": false,
"inputs": [
anonymous: false,
inputs: [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
indexed: true,
internalType: 'address',
name: 'previousOwner',
type: 'address',
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
indexed: true,
internalType: 'address',
name: 'newOwner',
type: 'address',
},
],
"name": "OwnershipTransferred",
"type": "event"
name: 'OwnershipTransferred',
type: 'event',
},
{
"inputs": [],
"name": "hub",
"outputs": [
inputs: [],
name: 'hub',
outputs: [
{
"internalType": "contract IIdentityVerificationHubV1",
"name": "",
"type": "address"
}
internalType: 'contract IIdentityVerificationHubV1',
name: '',
type: 'address',
},
],
"stateMutability": "view",
"type": "function"
stateMutability: 'view',
type: 'function',
},
{
"inputs": [],
"name": "owner",
"outputs": [
inputs: [],
name: 'owner',
outputs: [
{
"internalType": "address",
"name": "",
"type": "address"
}
internalType: 'address',
name: '',
type: 'address',
},
],
"stateMutability": "view",
"type": "function"
stateMutability: 'view',
type: 'function',
},
{
"inputs": [],
"name": "registry",
"outputs": [
inputs: [],
name: 'registry',
outputs: [
{
"internalType": "contract IIdentityRegistryV1",
"name": "",
"type": "address"
}
internalType: 'contract IIdentityRegistryV1',
name: '',
type: 'address',
},
],
"stateMutability": "view",
"type": "function"
stateMutability: 'view',
type: 'function',
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
inputs: [],
name: 'renounceOwnership',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
"inputs": [
inputs: [
{
"internalType": "address",
"name": "hubAddress",
"type": "address"
}
internalType: 'address',
name: 'hubAddress',
type: 'address',
},
],
"name": "setHub",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
name: 'setHub',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
"inputs": [
inputs: [
{
"internalType": "address",
"name": "registryAddress",
"type": "address"
}
internalType: 'address',
name: 'registryAddress',
type: 'address',
},
],
"name": "setRegistry",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
name: 'setRegistry',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
"inputs": [
inputs: [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
internalType: 'address',
name: 'newOwner',
type: 'address',
},
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
name: 'transferOwnership',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
"inputs": [
inputs: [
{
"internalType": "uint256",
"name": "targetRootTimestamp",
"type": "uint256"
internalType: 'uint256',
name: 'targetRootTimestamp',
type: 'uint256',
},
{
"components": [
components: [
{
"internalType": "bool",
"name": "olderThanEnabled",
"type": "bool"
internalType: 'bool',
name: 'olderThanEnabled',
type: 'bool',
},
{
"internalType": "uint256",
"name": "olderThan",
"type": "uint256"
internalType: 'uint256',
name: 'olderThan',
type: 'uint256',
},
{
"internalType": "bool",
"name": "forbiddenCountriesEnabled",
"type": "bool"
internalType: 'bool',
name: 'forbiddenCountriesEnabled',
type: 'bool',
},
{
"internalType": "uint256[4]",
"name": "forbiddenCountriesListPacked",
"type": "uint256[4]"
internalType: 'uint256[4]',
name: 'forbiddenCountriesListPacked',
type: 'uint256[4]',
},
{
"internalType": "bool[3]",
"name": "ofacEnabled",
"type": "bool[3]"
internalType: 'bool[3]',
name: 'ofacEnabled',
type: 'bool[3]',
},
{
"components": [
components: [
{
"internalType": "uint256[2]",
"name": "a",
"type": "uint256[2]"
internalType: 'uint256[2]',
name: 'a',
type: 'uint256[2]',
},
{
"internalType": "uint256[2][2]",
"name": "b",
"type": "uint256[2][2]"
internalType: 'uint256[2][2]',
name: 'b',
type: 'uint256[2][2]',
},
{
"internalType": "uint256[2]",
"name": "c",
"type": "uint256[2]"
internalType: 'uint256[2]',
name: 'c',
type: 'uint256[2]',
},
{
"internalType": "uint256[21]",
"name": "pubSignals",
"type": "uint256[21]"
}
internalType: 'uint256[21]',
name: 'pubSignals',
type: 'uint256[21]',
},
],
"internalType": "struct IVcAndDiscloseCircuitVerifier.VcAndDiscloseProof",
"name": "vcAndDiscloseProof",
"type": "tuple"
}
internalType: 'struct IVcAndDiscloseCircuitVerifier.VcAndDiscloseProof',
name: 'vcAndDiscloseProof',
type: 'tuple',
},
],
"internalType": "struct IIdentityVerificationHubV1.VcAndDiscloseHubProof",
"name": "proof",
"type": "tuple"
internalType: 'struct IIdentityVerificationHubV1.VcAndDiscloseHubProof',
name: 'proof',
type: 'tuple',
},
{
"internalType": "enum IIdentityVerificationHubV1.RevealedDataType[]",
"name": "types",
"type": "uint8[]"
}
internalType: 'enum IIdentityVerificationHubV1.RevealedDataType[]',
name: 'types',
type: 'uint8[]',
},
],
"name": "verifyAll",
"outputs": [
name: 'verifyAll',
outputs: [
{
"components": [
components: [
{
"internalType": "string",
"name": "issuingState",
"type": "string"
internalType: 'string',
name: 'issuingState',
type: 'string',
},
{
"internalType": "string[]",
"name": "name",
"type": "string[]"
internalType: 'string[]',
name: 'name',
type: 'string[]',
},
{
"internalType": "string",
"name": "passportNumber",
"type": "string"
internalType: 'string',
name: 'passportNumber',
type: 'string',
},
{
"internalType": "string",
"name": "nationality",
"type": "string"
internalType: 'string',
name: 'nationality',
type: 'string',
},
{
"internalType": "string",
"name": "dateOfBirth",
"type": "string"
internalType: 'string',
name: 'dateOfBirth',
type: 'string',
},
{
"internalType": "string",
"name": "gender",
"type": "string"
internalType: 'string',
name: 'gender',
type: 'string',
},
{
"internalType": "string",
"name": "expiryDate",
"type": "string"
internalType: 'string',
name: 'expiryDate',
type: 'string',
},
{
"internalType": "uint256",
"name": "olderThan",
"type": "uint256"
internalType: 'uint256',
name: 'olderThan',
type: 'uint256',
},
{
"internalType": "uint256",
"name": "passportNoOfac",
"type": "uint256"
internalType: 'uint256',
name: 'passportNoOfac',
type: 'uint256',
},
{
"internalType": "uint256",
"name": "nameAndDobOfac",
"type": "uint256"
internalType: 'uint256',
name: 'nameAndDobOfac',
type: 'uint256',
},
{
"internalType": "uint256",
"name": "nameAndYobOfac",
"type": "uint256"
}
internalType: 'uint256',
name: 'nameAndYobOfac',
type: 'uint256',
},
],
"internalType": "struct IIdentityVerificationHubV1.ReadableRevealedData",
"name": "",
"type": "tuple"
internalType: 'struct IIdentityVerificationHubV1.ReadableRevealedData',
name: '',
type: 'tuple',
},
{
"internalType": "bool",
"name": "",
"type": "bool"
internalType: 'bool',
name: '',
type: 'bool',
},
{
"internalType": "string",
"name": "",
"type": "string"
}
internalType: 'string',
name: '',
type: 'string',
},
],
"stateMutability": "view",
"type": "function"
}
];
stateMutability: 'view',
type: 'function',
},
];

View File

@@ -1,4 +1,7 @@
import { CIRCUIT_CONSTANTS } from '../../../../common/src/constants/constants';
import { castToUserIdentifier, UserIdType } from '../../../../common/src/utils/circuits/uuid';
import { BigNumberish } from 'ethers';
import { PublicSignals } from 'snarkjs';
export function parseSolidityCalldata<T>(rawCallData: string, _type: T): T {
const parsed = JSON.parse('[' + rawCallData + ']');
@@ -13,3 +16,10 @@ export function parseSolidityCalldata<T>(rawCallData: string, _type: T): T {
pubSignals: parsed[3].map((x: string) => x.replace(/"/g, '')) as BigNumberish[],
} as T;
}
export async function getUserIdentifier(publicSignals: PublicSignals, user_identifier_type: UserIdType = 'uuid'): Promise<string> {
return castToUserIdentifier(
BigInt(publicSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX]),
user_identifier_type
);
}

Binary file not shown.

View File

@@ -1,17 +0,0 @@
import React from 'react';
import { SelfAttestation, SelfVerifier } from '@openpassport/core';
import { UserIdType } from '../../common/src/utils/circuits/uuid';
interface OpenPassportQRcodeProps {
appName: string;
userId: string;
userIdType: UserIdType;
selfVerifier: SelfVerifier;
onSuccess: (attestation: SelfAttestation) => void;
websocketUrl?: string;
size?: number;
}
declare const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps>;
export { OpenPassportQRcode, OpenPassportQRcodeProps };

View File

@@ -1,127 +1,193 @@
# Installation
# @selfxyz/qrcode
A React component for generating QR codes for Self passport verification.
## Installation
```bash
yarn add @selfxyz/sdk
npm install @selfxyz/qrcode
# or
yarn add @selfxyz/qrcode
```
# Generate a QR code
## Basic Usage
### Create an AppType type object:
### 1. Import the SelfQRcodeWrapper component
```typescript
import { AppType } from '@selfxyz/sdk';
const appName = '🤠 Cowboy App';
const scope = 'cowboyApp';
const userID = 'user1234';
const sessionID = uuidv4();
```tsx
import SelfQRcodeWrapper, { SelfApp, SelfAppBuilder } from '@selfxyz/qrcode';
import { v4 as uuidv4 } from 'uuid';
```
const cowboyApp: AppType = {
name: appName,
scope,
userId: userID,
sessionId: sessionID,
circuit: 'prove',
arguments: {
disclosureOptions: { older_than: '18', nationality: 'France' },
### 2. Create a SelfApp instance using SelfAppBuilder
```tsx
// Generate a unique user ID
const userId = uuidv4();
// Create a SelfApp instance using the builder pattern
const selfApp = new SelfAppBuilder({
appName: "My App",
scope: "my-app-scope",
endpoint: "https://myapp.com/api/verify",
logoBase64: "base64EncodedLogo", // Optional
userId,
// Optional disclosure requirements
disclosures: {
// DG1 disclosures
issuing_state: true,
name: true,
nationality: true,
date_of_birth: true,
passport_number: true,
gender: true,
expiry_date: true,
// Custom verification rules
minimumAge: 18,
excludedCountries: ["IRN", "PRK"],
ofac: true,
},
};
}).build();
```
| Parameter | Optional | Description |
| ----------- | -------- | ------------------------------------------------------------- |
| `scope` | M | The scope of your application, is unique for each application |
| `name` | M | Name of the application |
| `userId` | M | User ID |
| `sessionId` | M | Session ID |
| `circuit` | M | Circuit to use, only `prove` is available for now |
| `arguments` | O | Optional disclosure options, based on passport attributes |
### 3. Render the QR code component
### Display the QR code
Use the appType object defined above to generate a QR code.
The generated QR code is an `HTML element` that you can display in your app.
```typescript
import { QRCodeGenerator } from '@selfxyz/sdk';
// [...] define cowboyApp as described above
const qrCode: HTMLElement = await QRCodeGenerator.generateQRCode(cowboyApp);
```tsx
function MyComponent() {
return (
<SelfQRcodeWrapper
selfApp={selfApp}
onSuccess={() => {
console.log('Verification successful');
// Perform actions after successful verification
}}
darkMode={false} // Optional: set to true for dark mode
size={300} // Optional: customize QR code size (default: 300)
/>
);
}
```
# Verify the proof
`SelfQRcodeWrapper` wraps `SelfQRcode` to prevent server-side rendering when using nextjs. When not using nextjs, `SelfQRcode` can be used instead.
## 1 Step flow
## SelfApp Configuration
To use the `OpenPassportVerifier`, import and initialize it as follows:
The `SelfAppBuilder` allows you to configure your application's verification requirements:
```typescript
import { OpenPassportVerifier } from '@selfxyz/sdk';
const verifier = new OpenPassportVerifier({
scope: 'cowboyApp',
requirements: [
['older_than', '18'],
['nationality', 'France'],
],
});
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `appName` | string | Yes | The name of your application |
| `scope` | string | Yes | A unique identifier for your application |
| `endpoint` | string | Yes | The endpoint that will verify the proof |
| `logoBase64` | string | No | Base64-encoded logo to display in the Self app |
| `userId` | string | Yes | Unique identifier for the user |
| `disclosures` | object | No | Disclosure and verification requirements |
### Disclosure Options
The `disclosures` object can include the following options:
| Option | Type | Description |
|--------|------|-------------|
| `issuing_state` | boolean | Request disclosure of passport issuing state |
| `name` | boolean | Request disclosure of the user's name |
| `nationality` | boolean | Request disclosure of nationality |
| `date_of_birth` | boolean | Request disclosure of birth date |
| `passport_number` | boolean | Request disclosure of passport number |
| `gender` | boolean | Request disclosure of gender |
| `expiry_date` | boolean | Request disclosure of passport expiry date |
| `minimumAge` | number | Verify the user is at least this age |
| `excludedCountries` | string[] | Array of country codes to exclude |
| `ofac` | boolean | Enable OFAC compliance check |
## Component Props
The `SelfQRcodeWrapper` component accepts the following props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `selfApp` | SelfApp | Yes | - | The SelfApp configuration object |
| `onSuccess` | () => void | Yes | - | Callback function executed on successful verification |
| `websocketUrl` | string | No | WS_DB_RELAYER | Custom WebSocket URL for verification |
| `size` | number | No | 300 | QR code size in pixels |
| `darkMode` | boolean | No | false | Enable dark mode styling |
| `children` | React.ReactNode | No | - | Custom children to render |
## Complete Example
Here's a complete example of how to implement the Self QR code in a React application:
```tsx
'use client';
import React, { useState, useEffect } from 'react';
import SelfQRcodeWrapper, { SelfApp, SelfAppBuilder } from '@selfxyz/qrcode';
import { v4 as uuidv4 } from 'uuid';
function VerificationPage() {
const [userId, setUserId] = useState<string | null>(null);
useEffect(() => {
// Generate a user ID when the component mounts
setUserId(uuidv4());
}, []);
if (!userId) return null;
// Create the SelfApp configuration
const selfApp = new SelfAppBuilder({
appName: "My Application",
scope: "my-application-scope",
endpoint: "https://myapp.com/api/verify",
userId,
disclosures: {
// Request passport information
name: true,
nationality: true,
date_of_birth: true,
// Set verification rules
minimumAge: 18,
excludedCountries: ["IRN", "PRK", "RUS"],
ofac: true,
},
}).build();
return (
<div className="verification-container">
<h1>Verify Your Identity</h1>
<p>Scan this QR code with the Self app to verify your identity</p>
<SelfQRcodeWrapper
selfApp={selfApp}
onSuccess={() => {
// Handle successful verification
console.log("Verification successful!");
// Redirect or update UI
}}
size={350}
/>
<p className="text-sm text-gray-500">
User ID: {userId.substring(0, 8)}...
</p>
</div>
);
}
export default VerificationPage;
```
### Parameters for `OpenPassportVerifier`
## Example
| Parameter | Optional | Description |
| --------------- | -------- | --------------------------------------------------------------------------------- |
| `scope` | M | The scope of your application, is unique for each application. |
| `attestationId` | O | The ID of the attestation, defaults to `PASSPORT_ATTESTATION_ID`. |
| `requirements` | O | An array of requirements, each an array with an attribute and its expected value. |
| `rpcUrl` | O | The RPC URL to connect to the blockchain, defaults to `DEFAULT_RPC_URL`. |
| `dev_mode` | O | Allow users with generated passport to pass the verification. |
For a more comprehensive and interactive example, please refer to the [playground](https://github.com/selfxyz/playground/blob/main/app/page.tsx).
### Verify the proof
## Verification Flow
The function fired from the OpenPassport app will send an `OpenPassportVerifierInputs` object.
1. Your application displays the QR code to the user
2. The user scans the QR code with the Self app
3. The Self app guides the user through the passport verification process
4. The proof is generated and sent to your verification endpoint
5. Upon successful verification, the `onSuccess` callback is triggered
```typescript
const result: OpenPassportVerifierReport = await verifier.verify(openPassportVerifierInputs);
```
From the `result` object, you can inspect the validity of any submitted attribute.
To check the overall validity of the proof, you can inspect the `valid` attribute.
```typescript
require(result.valid);
```
Nullifier and user identifier are accessible from the `result` object.
```typescript
const nullifier: number = result.nullifier;
const user_identifier: number = result.user_identifier;
```
## 2 Steps flow
### 🚧 Work in progress 🚧
# Development
Install the dependencies
```bash
yarn install-sdk
```
## Tests
To run the tests, you need to download the circuits and the zkey files from the AWS s3 bucket.
This script will also compile the circuits to generate the wasm files.
Make sure that the circuits in the circuits folder are up to date with the AWS zkey files.
```bash
yarn download-circuits
```
Then run the tests with the following command:
```bash
yarn test
```
The QR code component displays the current verification status with an LED indicator and changes its appearance based on the verification state.

View File

@@ -16,15 +16,16 @@ const QRCodeSVG = dynamic(() => import('qrcode.react').then((mod) => mod.QRCodeS
ssr: false,
});
interface OpenPassportQRcodeProps {
interface SelfQRcodeProps {
selfApp: SelfApp;
onSuccess: () => void;
websocketUrl?: string;
size?: number;
darkMode?: boolean;
children?: React.ReactNode;
}
// Create a wrapper component that handles client-side rendering
const OpenPassportQRcodeWrapper: React.FC<OpenPassportQRcodeProps> = (props) => {
const SelfQRcodeWrapper = (props: SelfQRcodeProps) => {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
@@ -33,16 +34,16 @@ const OpenPassportQRcodeWrapper: React.FC<OpenPassportQRcodeProps> = (props) =>
if (!isClient) {
return null;
}
return <OpenPassportQRcode {...props} />;
return <SelfQRcode {...props} />;
};
// Your existing OpenPassportQRcode component
const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
const SelfQRcode = ({
selfApp,
onSuccess,
websocketUrl = WS_DB_RELAYER,
size = 300,
}) => {
darkMode = false,
}: SelfQRcodeProps) => {
const [proofStep, setProofStep] = useState(QRcodeSteps.WAITING_FOR_MOBILE);
const [proofVerified, setProofVerified] = useState(false);
const [sessionId] = useState(uuidv4());
@@ -116,6 +117,8 @@ const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
<QRCodeSVG
value={generateUniversalLink()}
size={size}
bgColor={darkMode ? '#000000' : '#ffffff'}
fgColor={darkMode ? '#ffffff' : '#000000'}
/>
);
}
@@ -128,7 +131,7 @@ const OpenPassportQRcode: React.FC<OpenPassportQRcodeProps> = ({
};
// Export the wrapper component as the default export
export default OpenPassportQRcodeWrapper;
export default SelfQRcodeWrapper;
// Also export other components/types that might be needed
export { OpenPassportQRcode, SelfApp, SelfAppBuilder };
export { SelfQRcode, SelfApp, SelfAppBuilder };

View File

@@ -1,7 +1,7 @@
import OpenPassportQRcodeWrapper, { OpenPassportQRcode, SelfApp, SelfAppBuilder } from './OpenPassportQRcode';
import SelfQRcodeWrapper, { SelfQRcode, SelfApp, SelfAppBuilder } from './SelfQRcode';
import { WebAppInfo } from './utils/websocket';
export default OpenPassportQRcodeWrapper;
export { OpenPassportQRcode, SelfApp, SelfAppBuilder };
export default SelfQRcodeWrapper;
export { SelfQRcode, SelfApp, SelfAppBuilder };
export type { WebAppInfo };

View File

@@ -1,9 +1,9 @@
{
"name": "@openpassport/qrcode2",
"version": "0.0.1",
"name": "@selfxyz/qrcode",
"version": "0.0.9",
"repository": {
"type": "git",
"url": "https://github.com/zk-passport/openpassport"
"url": "https://github.com/celo-org/self"
},
"license": "MIT",
"author": "turnoffthiscomputer",
@@ -17,58 +17,28 @@
],
"scripts": {
"build": "tsc",
"download-circuits": "cd ../circuits && ./scripts/download_circuits_from_aws.sh && cd ../sdk",
"format": "prettier --write .",
"install-sdk": "cd ../common && yarn && cd ../sdk && yarn",
"lint": "prettier --check .",
"prepublishOnly": "npm run build",
"test": "yarn ts-mocha -p ./tsconfig.json tests/openPassportVerifier.test.ts --exit"
"test": "yarn ts-mocha -p ./tsconfig.json tests/openPassportVerifier.test.ts --exit",
"publish": "npm publish --access public"
},
"dependencies": {
"@openpassport/core": "0.0.12",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
"elliptic": "^6.5.7",
"fs": "^0.0.1-security",
"js-sha1": "^0.7.0",
"js-sha256": "^0.11.0",
"js-sha512": "^0.9.0",
"lottie-react": "^2.4.0",
"msgpack-lite": "^0.1.26",
"next": "^14.2.8",
"node-forge": "https://github.com/remicolin/forge",
"pako": "^2.1.0",
"pkijs": "^3.2.4",
"poseidon-lite": "^0.2.0",
"qrcode.react": "^4.1.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-spinners": "^0.14.1",
"snarkjs": "^0.7.4",
"socket.io-client": "^4.8.1",
"uuid": "^10.0.0",
"zlib": "^1.0.5"
"uuid": "^10.0.0"
},
"devDependencies": {
"@openpassport/zk-kit-imt": "^0.0.5",
"@openpassport/zk-kit-lean-imt": "^0.0.6",
"@openpassport/zk-kit-smt": "^0.0.1",
"@types/chai": "^4.3.6",
"@types/chai-as-promised": "^7.1.8",
"@types/circomlibjs": "^0.1.6",
"@types/expect": "^24.3.0",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.19",
"@types/node-forge": "^1.3.5",
"@types/pako": "^2.0.3",
"@types/snarkjs": "^0.7.8",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
"chai": "^4.3.8",
"chai-as-promised": "^7.1.1",
"dotenv": "^16.4.5",
"ethers": "^6.13.0",
"mocha": "^10.3.0",
"prettier": "^3.3.3",
"ts-loader": "^9.5.1",

View File

@@ -16,7 +16,7 @@
"include": [
"index.ts",
"src/**/*",
"OpenPassportQRcode.tsx",
"SelfQRcode.tsx",
"common/**/*",
"circuits/**/*",
"circuits/**/*.json",

File diff suppressed because it is too large Load Diff

View File

@@ -12,12 +12,11 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/material": "^6.0.2",
"@openpassport/core": "^0.0.11",
"@openpassport/qrcode": "^0.0.14",
"axios": "^1.7.7",
"next": "14.2.8",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"uuid": "^11.1.0"
},
"devDependencies": {
"@types/node": "^20",

View File

@@ -1,7 +1,7 @@
'use client';
import { SelfAppBuilder } from '../../../../../qrcode/OpenPassportQRcode';
import OpenPassportQRcodeWrapper from '../../../../../qrcode/OpenPassportQRcode';
import { SelfAppBuilder } from '../../../../../qrcode/SelfQRcode';
import SelfQRcodeWrapper from '../../../../../qrcode/SelfQRcode';
import { v4 } from 'uuid';
import {logo} from './logo';
@@ -31,7 +31,7 @@ export default function Prove() {
return (
<div className="h-screen w-full bg-white flex flex-col items-center justify-center gap-4">
<OpenPassportQRcodeWrapper
<SelfQRcodeWrapper
selfApp={selfApp}
onSuccess={() => {
window.location.href = '/success';

File diff suppressed because it is too large Load Diff