V1 to V2 Migration Guide

This guide helps you migrate from Self Protocol V1 to V2. V2 introduces multi-document support, dynamic configuration, and improved data structures.

Overview of Changes

What's New in V2

  • Multi-document support: E-Passports and EU ID Cards

  • Dynamic configuration: Switch configurations without redeployment

  • Enhanced data formats: Pre-extracted, human-readable outputs

  • User context data: Pass custom data through verification flow

  • Improved error handling: Detailed configuration mismatch reporting

Breaking Changes

  • Backend SDK constructor requires new parameters

  • Configuration methods replaced with interface

  • Verification method signature changed

  • Frontend requires disclosures object

  • Smart contract interfaces updated

Backend Migration

1. Update Dependencies

npm install @selfxyz/core@latest

2. Update Constructor

V1 (Old):

const verifier = new SelfBackendVerifier(
  "my-app-scope",
  "https://api.example.com/verify",
  false  // mock mode
);

V2 (New):

import { SelfBackendVerifier, AttestationId, UserIdType, IConfigStorage, AllIds } from '@selfxyz/core';

// Option 1: Use AllIds for all document types (recommended for most cases)
const allowedIds = AllIds;

// Option 2: Define specific allowed document types
// const allowedIds = new Map();
// allowedIds.set(AttestationId.E_PASSPORT, true);  // Accept passports
// allowedIds.set(AttestationId.EU_ID_CARD, true);  // Accept EU ID cards

// Implement configuration storage
class ConfigStorage implements IConfigStorage {
  async getConfig(configId: string) {
    // Return your verification requirements
    return {
      minimumAge: 18,
      excludedCountries: ['IRN', 'PRK'],
      ofac: true
    };
  }
  
  async getActionId(userIdentifier: string, userDefinedData?: string) {
    // Return config ID based on your logic
    return 'default_config';
  }
}

const verifier = new SelfBackendVerifier(
  "my-app-scope",
  "https://api.example.com/verify",
  false,                        // mock mode
  allowedIds,                   // NEW: allowed document types
  new ConfigStorage(),          // NEW: config storage
  UserIdType.UUID               // NEW: user ID type
);

3. Update Configuration

V1 (Old):

// Direct method calls
verifier.setMinimumAge(18);
verifier.excludeCountries('Iran', 'North Korea');
verifier.enablePassportNoOfacCheck();
verifier.enableNameOfacCheck();
verifier.enableDobOfacCheck();

V2 (New):

// Configuration via IConfigStorage implementation
class ConfigStorage implements IConfigStorage {
  async getConfig(configId: string) {
    // All configuration in one place
    return {
      minimumAge: 18,
      excludedCountries: ['IRN', 'PRK'],  // Use ISO 3-letter codes
      ofac: true  // Single boolean for all OFAC checks
    };
  }
}

4. Update Verification Method

V1 (Old):

app.post('/api/verify', async (req, res) => {
  const { proof, publicSignals } = req.body;
  
  try {
    const isValid = await verifier.verify(proof, publicSignals);
    res.json({ valid: isValid });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

V2 (New):

app.post('/api/verify', async (req, res) => {
  const { attestationId, proof, pubSignals, userContextData } = req.body;
  
  try {
    const result = await verifier.verify(
      attestationId,      // NEW: 1 for passport, 2 for EU ID
      proof,
      pubSignals,
      userContextData     // NEW: hex-encoded context data
    );
    
    if (result.isValidDetails.isValid) {
      res.json({
        status: 'success',
        result: true,
        credentialSubject: result.discloseOutput,
        documentType: attestationId === 1 ? 'passport' : 'eu_id_card'
      });
    } else {
      res.status(400).json({
        status: 'error',
        result: false,
        details: result.isValidDetails
      });
    }
  } catch (error) {
    if (error.name === 'ConfigMismatchError') {
      res.status(400).json({
        status: 'error',
        message: 'Configuration mismatch',
        issues: error.issues
      });
    } else {
      res.status(500).json({ error: error.message });
    }
  }
});

5. Handle New Response Format

V1 Response:

// Simple boolean
true/false

V2 Response:

{
  attestationId: 1,              // Document type
  isValidDetails: {
    isValid: boolean,            // Overall result
    isOlderThanValid: boolean,   // Age check result
    isOfacValid: boolean         // OFAC check result
  },
  forbiddenCountriesList: [],    // Excluded countries
  discloseOutput: {              // Pre-extracted data
    nationality: "USA",
    minimumAge: "21",
    name: ["JOHN", "DOE"],
    dateOfBirth: "01-01-1990",
    issuingState: "USA",
    idNumber: "123456789",
    gender: "M",
    expiryDate: "01-01-2030",
    ofac: [true, true, true]
  },
  userData: {
    userIdentifier: "uuid-here",
    userDefinedData: "custom-data"
  }
}

Frontend Migration

1. Update Dependencies

npm install @selfxyz/qrcode@latest

2. Update QR Code Configuration

V1 (Old):

import { SelfAppBuilder } from '@selfxyz/qrcode';

const selfApp = new SelfAppBuilder({
  appName: "My App",
  scope: "my-app-scope",
  endpoint: "https://api.example.com/verify",
  userId: userId,
  logoBase64: logo
}).build();

V2 (New):

import { SelfAppBuilder } from '@selfxyz/qrcode';

const selfApp = new SelfAppBuilder({
  appName: "My App",
  scope: "my-app-scope",
  endpoint: "https://api.example.com/verify",
  userId: userId,
  logoBase64: logo,
  version: 2,                          // NEW: Specify V2
  userDefinedData: "custom-data",      // NEW: Optional custom data
  disclosures: {                       // NEW: Must match backend config
    // Verification rules
    minimumAge: 18,
    excludedCountries: ['IRN', 'PRK'],
    ofac: true,
    // Data fields to reveal
    name: true,
    nationality: true,
    dateOfBirth: true
  }
}).build();

3. Important: Disclosures Object

The disclosures object in V2 contains both verification rules and data fields:

disclosures: {
  // Verification rules (must match backend exactly)
  minimumAge: 18,                      // Age requirement
  excludedCountries: ['IRN', 'PRK'],   // ISO 3-letter codes
  ofac: true,                          // OFAC checking
  
  // Data fields to reveal
  name: true,                          // Full name
  nationality: true,                   // Nationality
  dateOfBirth: true,                   // Date of birth
  issuingState: true,                  // Issuing country
  idNumber: true,                      // Document number
  gender: true,                        // Gender
  expiryDate: true                     // Expiration date
}

Smart Contract Migration

1. Update Contract Inheritance

V1 (Old):

import { IIdentityVerificationHub } from "@selfxyz/contracts/interfaces/IIdentityVerificationHub.sol";

contract MyContract {
    IIdentityVerificationHub public hub;
    
    function verify(/* params */) external {
        // Direct hub integration
    }
}

V2 (New):

import { SelfVerificationRoot } from "@selfxyz/contracts/abstract/SelfVerificationRoot.sol";

contract MyContract is SelfVerificationRoot {
    constructor(
        address _hub,
        bytes32 _scope
    ) SelfVerificationRoot(_hub, _scope) {}
    
    // Override to handle verification results
    function customVerificationHook(
        GenericDiscloseOutputV2 memory output,
        bytes memory userData
    ) internal override {
        // Your business logic here
    }
    
    // Override to provide configuration ID
    function getConfigId(
        bytes32 destinationChainId,
        bytes32 userIdentifier,
        bytes memory userDefinedData
    ) public view override returns (bytes32) {
        // Generate your config ID at https://tools.self.xyz/
        // Default config ID: 0x7b6436b0c98f62380866d9432c2af0ee08ce16a171bda6951aecd95ee1307d61
        return 0x7b6436b0c98f62380866d9432c2af0ee08ce16a171bda6951aecd95ee1307d61;
    }
}

2. Update Hub Addresses

V2 Hub Addresses:

// Celo Mainnet
address constant HUB_V2 = 0xe57F4773bd9c9d8b6Cd70431117d353298B9f5BF;

// Celo Testnet (Alfajores)
address constant HUB_V2_STAGING = 0x68c931C9a534D37aa78094877F46fE46a49F1A51;

3. Handle New Data Structure

V1 Structure:

struct VcAndDiscloseVerificationResult {
    uint256 attestationId;
    uint256 scope;
    uint256 userIdentifier;
    uint256 nullifier;
    uint256[3] revealedDataPacked;   // Packed data
}

V2 Structure:

struct GenericDiscloseOutputV2 {
    bytes32 attestationId;           // Now bytes32
    uint256 userIdentifier;
    uint256 nullifier;
    string issuingState;             // Pre-extracted
    string[] name;                   // Pre-extracted array
    string idNumber;                 // Renamed from passportNumber
    string nationality;              // Pre-extracted
    string dateOfBirth;              // Pre-extracted format
    string gender;                   // Pre-extracted
    string expiryDate;               // Pre-extracted
    uint256 minimumAge;
    bool[3] ofac;                    // Bool array
    uint256[4] forbiddenCountriesListPacked;
}

Common Migration Issues

1. Configuration Mismatch

Problem: Frontend disclosures don't match backend configuration

ConfigMismatchError: Configuration mismatch

Solution: Ensure frontend and backend have identical settings:

// Frontend
disclosures: {
  minimumAge: 18,
  excludedCountries: ['IRN', 'PRK'],
  ofac: true
}

// Backend (in getConfig)
return {
  minimumAge: 18,
  excludedCountries: ['IRN', 'PRK'],
  ofac: true
};

2. Missing Attestation ID

Problem: Verification fails with missing attestation ID

Solution: Frontend must send attestation ID:

const requestBody = {
  attestationId: 1,  // 1 for passport, 2 for EU ID
  proof: proof,
  pubSignals: pubSignals,
  userContextData: userContextData
};

3. Invalid User Context Data

Problem: User context data validation fails

Solution: Ensure proper hex encoding:

// Create user context data (256 bytes total)
const userContextData = '0x' + '0'.repeat(512);  // 512 hex chars = 256 bytes

4. Document Type Not Allowed

Problem: "Attestation ID is not allowed" error

Solution: Add document type to allowedIds:

// Option 1: Use AllIds for all document types
const allowedIds = AllIds;

// Option 2: Define specific allowed document types
// const allowedIds = new Map();
// allowedIds.set(AttestationId.E_PASSPORT, true);  // Add passport
// allowedIds.set(AttestationId.EU_ID_CARD, true);  // Add EU ID card

Testing Your Migration

1. Test with Mock Passports

// Use staging/testnet for development
const verifier = new SelfBackendVerifier(
  "test-scope",
  "https://test.ngrok.app/verify",
  true,  // Enable mock mode
  allowedIds,
  configStorage,
  UserIdType.UUID
);

// Disable OFAC for mock passports
class ConfigStorage {
  async getConfig() {
    return {
      minimumAge: 18,
      ofac: false  // Must be false for mock passports
    };
  }
}

2. Test Both Document Types

// Test passport verification
const passportResult = await verifier.verify(
  AttestationId.E_PASSPORT,  // 1
  passportProof,
  passportSignals,
  userContextData
);

// Test EU ID card verification
const idCardResult = await verifier.verify(
  AttestationId.EU_ID_CARD,  // 2
  idCardProof,
  idCardSignals,
  userContextData
);

3. Verify Configuration Switching

class DynamicConfigStorage {
  async getConfig(configId: string) {
    switch(configId) {
      case 'strict':
        return { minimumAge: 21, ofac: true };
      case 'relaxed':
        return { minimumAge: 18, ofac: false };
      default:
        return { minimumAge: 18, ofac: true };
    }
  }
  
  async getActionId(userIdentifier: string, userDefinedData?: string) {
    // Select config based on user data
    return userDefinedData === 'premium' ? 'strict' : 'relaxed';
  }
}

Best Practices

1. Configuration Management

  • Store configurations in a database for easy updates

  • Version your configurations for rollback capability

  • Use meaningful config IDs (not just hashes)

  • Document configuration requirements

2. Error Handling

try {
  const result = await verifier.verify(/* params */);
} catch (error) {
  if (error.name === 'ConfigMismatchError') {
    // Log detailed issues for debugging
    console.error('Config issues:', error.issues);
    // Return user-friendly message
    return { error: 'Verification configuration error' };
  }
  // Handle other errors
}

3. Security Considerations

  • Always validate attestation IDs

  • Store and check nullifiers to prevent replay

  • Use appropriate scopes for different use cases

  • Never expose configuration details to frontend

4. Performance Optimization

  • Cache configuration objects

  • Reuse verifier instances

  • Batch verification requests when possible

  • Use connection pooling for RPC calls

Resources

Need Help?

If you encounter issues during migration:

  1. Review example implementations

  2. Report issues at GitHub Issues

Last updated