Skip to main content

Onchain Verification

ZKPassport allows you to verify passport and national ID proofs directly on EVM chains. As of today, our verifier is only deployed on Ethereum Sepolia. If you need a specific chain, please reach out to us.

The following guide explains how to use ZKPassport proofs in your smart contracts.

Overview

Onchain verification enables your smart contracts to:

  • Verify user identity proofs without requiring centralized servers
  • Create unique identifiers linked to real-world identity
  • Apply age, nationality, or other ID-based restrictions to on-chain actions

Verifier Contract

ZKPassport maintains a deployed ZKPassportVerifier contract on Sepolia that handles all proof verification. Your contracts will interact with this verifier.

Integration Steps

1. Build your query for onchain verification

import { ZKPassport } from "@zkpassport/sdk";

const zkPassport = new ZKPassport("your-domain.com");

// Create a request with your app details
const queryBuilder = await zkPassport.request({
name: "Your App Name",
// A path to your app's logo
logo: "https://your-domain.com/logo.png",
// A description of the purpose of the request
purpose: "Doing something",
// Optional scope for the user's unique identifier
scope: "my-scope",
// To verify proofs on EVM chains, you need to set the mode to "compressed-evm"
mode: "compressed-evm",
});

// Build your query with the required attributes or conditions you want to verify
const {
url,
requestId,
onRequestReceived,
onGeneratingProof,
onProofGenerated,
onResult,
onReject,
onError,
} = queryBuilder
// Disclose the user's nationality
.disclose("nationality")
// Disclose the user's document type
.disclose("document_type")
// Verify the user's age is greater than or equal to 18
.gte("age", 18)
// Bind the user's address to the proof
.bind("user_address", "0x1234567890123456789012345678901234567890")
// Bind to the chain where the proof will be verified
.bind("chain", "ethereum_sepolia")
// Bind custom data to the proof
.bind("custom_data", "my-custom-data")
// Finalize the query
.done();

2. Get Verifier Contract Details

Use the SDK's getSolidityVerifierDetails method to get verifier contract details:

const {
// The address of the deployed verifier contract
address,
// The function name to call on the verifier contract
functionName,
// The ABI of the verifier contract
abi,
} = zkPassport.getSolidityVerifierDetails("ethereum_sepolia");

3. Generate Verification Parameters from Proof

When a user completes a verification in your app, use getSolidityVerifierParameters to prepare the parameters to pass to the verifier contract:

let proof: ProofResult;
// Use the proofResult from the onProofGenerated callback to get the proof
onProofGenerated((proofResult: ProofResult) => {
proof = proofResult;
});

onResult(
({
uniqueIdentifier,
verified,
result,
}: {
uniqueIdentifier: string;
verified: boolean;
result: QueryResult;
}) => {
if (!verified) {
// If the proof is not verified, save yourself some gas and return straight away
console.log("Proof is not verified");
return;
}

// Get the verification parameters
const verifierParams = zkPassport.getSolidityVerifierParameters({
proof: proof,
// Use the same scope as the one you specified with the request function
scope: "my-scope",
// Enable dev mode if you want to use mock passports, otherwise keep it false
devMode: false,
});

// Get the wallet provider
const walletProvider = await getWalletProvider();

// Verify the proof on-chain
// The function is defined in the next steps below
await verifyOnChain(
verifierParams,
walletProvider,
// Use the document type to determine if the proof is for an ID card or passport
result.document_type.disclose.result !== "passport"
);
}
);

4. Create Your Smart Contract

Create a contract that interacts with the verifier:

pragma solidity ^0.8.21;

/**
* @notice The data that can be bound to the proof
*/
struct BoundData {
// The address of the ID holder
userAddress: address;
// The chain id (block.chainid)
chainId: uint256;
// The custom data (encoded as ASCII string)
customData: string;
}

/**
* @notice The data that can be disclosed by the proof
*/
struct DisclosedData {
// The name of the ID holder (includes the angular brackets from the MRZ)
string name;
// The issuing country of the ID
string issuingCountry;
// The nationality of the ID holder
string nationality;
// The gender of the ID holder
string gender;
// The birth date of the ID holder
string birthDate;
// The expiry date of the ID
string expiryDate;
// The document number of the ID
string documentNumber;
// The type of the document
string documentType;
}

/**
* @notice The parameters for verifying a proof
* @dev this can be retrieved with the getSolidityVerifierParameters function in the SDK
*/
struct ProofVerificationParams {
bytes32 vkeyHash;
bytes proof;
bytes32[] publicInputs;
bytes committedInputs;
uint256[] committedInputCounts;
uint256 validityPeriodInSeconds;
string domain;
string scope;
bool devMode;
}

/**
* @notice The public interface for the ZKPassport verifier contract
*/
interface IZKPassportVerifier {
/**
* @notice Verifies a proof from ZKPassport
* @param params The proof verification parameters
* @return isValid True if the proof is valid, false otherwise
* @return uniqueIdentifier The unique identifier associated to the identity document that generated the proof
*/
function verifyProof(ProofVerificationParams calldata params) external returns (bool verified, bytes32 uniqueIdentifier);

/**
* @notice Verifies that the proof was generated for the given domain and scope
* @param publicInputs The public inputs of the proof
* @param domain The domain to check against
* @param scope The scope to check against
* @return True if the proof was generated for the given domain and scope, false otherwise
*/
function verifyScopes(bytes32[] calldata publicInputs, string calldata domain, string calldata scope) external view returns (bool);

// ===== Helper functions to get the information revealed by the proof =====

// ===== Retrieve the disclosed data =====

/**
* @notice Gets the data disclosed by the proof
* @param params The proof verification parameters
* @param isIDCard Whether the proof is from an ID card
* @return disclosedData The data disclosed by the proof
*/
function getDisclosedData(
ProofVerificationParams calldata params,
bool isIDCard
) external view returns (DisclosedData);


// ===== Retrieve the bound data =====

/**
* @notice Gets the data bound to the proof
* @param params The proof verification parameters
* @return boundData The data bound to the proof
*/
function getBoundData(ProofVerificationParams calldata params) external view returns (BoundData);

// ===== Age verification =====

/**
* @notice Checks if the age is above or equal to the given age
* @param minAge The age must be above or equal to this age
* @param params The proof verification parameters
* @return True if the age is above or equal to the given age, false otherwise
*/
function isAgeAboveOrEqual(
uint8 minAge,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the age is above the given age
* @param minAge The age must be above this age
* @param params The proof verification parameters
* @return True if the age is above the given age, false otherwise
*/
function isAgeAbove(
uint8 minAge,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the age is in the given range
* @param minAge The age must be greater than or equal to this age
* @param maxAge The age must be less than or equal to this age
* @param params The proof verification parameters
* @return True if the age is in the given range, false otherwise
*/
function isAgeBetween(
uint8 minAge,
uint8 maxAge,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the age is below or equal to the given age
* @param maxAge The age must be below or equal to this age
* @param params The proof verification parameters
* @return True if the age is below or equal to the given age, false otherwise
*/
function isAgeBelowOrEqual(
uint8 maxAge,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the age is below the given age
* @param maxAge The age must be below this age
* @param params The proof verification parameters
* @return True if the age is below the given age, false otherwise
*/
function isAgeBelow(
uint8 maxAge,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the age is equal to the given age
* @param age The age must be equal to this age
* @param params The proof verification parameters
* @return True if the age is equal to the given age, false otherwise
*/
function isAgeEqual(
uint8 age,
ProofVerificationParams calldata params
) external view returns (bool);

// ===== Birthdate comparison =====

/**
* @notice Checks if the birthdate is after or equal to the given date
* @param minDate The birthdate must be after or equal to this date
* @param params The proof verification parameters
* @return True if the birthdate is after or equal to the given date, false otherwise
*/
function isBirthdateAfterOrEqual(
uint256 minDate,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the birthdate is after the given date
* @param minDate The birthdate must be after this date
* @param params The proof verification parameters
* @return True if the birthdate is after the given date, false otherwise
*/
function isBirthdateAfter(
uint256 minDate,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the birthdate is between the given dates
* @param minDate The birthdate must be after or equal to this date
* @param maxDate The birthdate must be before or equal to this date
* @param params The proof verification parameters
* @return True if the birthdate is between the given dates, false otherwise
*/
function isBirthdateBetween(
uint256 minDate,
uint256 maxDate,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the birthdate is before or equal to the given date
* @param maxDate The birthdate must be before or equal to this date
* @param params The proof verification parameters
* @return True if the birthdate is before or equal to the given date, false otherwise
*/
function isBirthdateBeforeOrEqual(
uint256 maxDate,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the birthdate is before the given date
* @param maxDate The birthdate must be before this date
* @param params The proof verification parameters
* @return True if the birthdate is before the given date, false otherwise
*/
function isBirthdateBefore(
uint256 maxDate,
ProofVerificationParams calldata params
) public view returns (bool);

/**
* @notice Checks if the birthdate is equal to the given date
* @param date The birthdate must be equal to this date
* @param params The proof verification parameters
* @return True if the birthdate is equal to the given date, false otherwise
*/
function isBirthdateEqual(
uint256 date,
ProofVerificationParams calldata params
) external view returns (bool);

// ===== Expiry date comparison =====

/**
* @notice Checks if the expiry date is after or equal to the given date
* @param minDate The expiry date must be after or equal to this date
* @param params The proof verification parameters
* @return True if the expiry date is after or equal to the given date, false otherwise
*/
function isExpiryDateAfterOrEqual(
uint256 minDate,
ProofVerificationParams calldata params
) public view returns (bool);

/**
* @notice Checks if the expiry date is after the given date
* @param minDate The expiry date must be after this date
* @param params The proof verification parameters
* @return True if the expiry date is after the given date, false otherwise
*/
function isExpiryDateAfter(
uint256 minDate,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the expiry date is between the given dates
* @param minDate The expiry date must be after or equal to this date
* @param maxDate The expiry date must be before or equal to this date
* @param params The proof verification parameters
* @return True if the expiry date is between the given dates, false otherwise
*/
function isExpiryDateBetween(
uint256 minDate,
uint256 maxDate,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the expiry date is before or equal to the given date
* @param maxDate The expiry date must be before or equal to this date
* @param params The proof verification parameters
* @return True if the expiry date is before or equal to the given date, false otherwise
*/
function isExpiryDateBeforeOrEqual(
uint256 maxDate,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the expiry date is before the given date
* @param maxDate The expiry date must be before this date
* @param params The proof verification parameters
* @return True if the expiry date is before the given date, false otherwise
*/
function isExpiryDateBefore(
uint256 maxDate,
ProofVerificationParams calldata params
) external view returns (bool);

/**
* @notice Checks if the expiry date is equal to the given date
* @param date The expiry date must be equal to this date
* @param params The proof verification parameters
* @return True if the expiry date is equal to the given date, false otherwise
*/
function isExpiryDateEqual(
uint256 date,
ProofVerificationParams calldata params
) external view returns (bool);

// ===== Country inclusion =====

/**
* @notice Checks if the nationality is in the list of countries
* @param countryList The list of countries (needs to match exactly the list of countries in the proof)
* @param params The proof verification parameters
* @return True if the nationality is in the list of countries, false otherwise
*/
function isNationalityIn(
string[] memory countryList,
ProofVerificationParams calldata params
) external pure returns (bool);

/**
* @notice Checks if the issuing country is in the list of countries
* @param countryList The list of countries (needs to match exactly the list of countries in the proof)
* @param params The proof verification parameters
* @return True if the issuing country is in the list of countries, false otherwise
*/
function isIssuingCountryIn(
string[] memory countryList,
ProofVerificationParams calldata params
) external pure returns (bool);

// ===== Country exclusion =====

/**
* @notice Checks if the nationality is not in the list of countries
* @param countryList The list of countries (needs to match exactly the list of countries in the proof)
* Note: The list of countries must be sorted in alphabetical order
* @param params The proof verification parameters
* @return True if the nationality is not in the list of countries, false otherwise
*/
function isNationalityOut(
string[] memory countryList,
ProofVerificationParams calldata params
) external pure returns (bool);

/**
* @notice Checks if the issuing country is not in the list of countries
* @param countryList The list of countries (needs to match exactly the list of countries in the proof)
* Note: The list of countries must be sorted in alphabetical order
* @param params The proof verification parameters
* @return True if the issuing country is not in the list of countries, false otherwise
*/
function isIssuingCountryOut(
string[] memory countryList,
ProofVerificationParams calldata params
) external pure returns (bool);

// ===== Sanction checks =====
/**
* @notice Enforces that the proof checks against the expected sanction list(s)
* @param params The proof verification parameters
*/
function enforceSanctionsRoot(
ProofVerificationParams calldata params
) external view;
}

contract YourContract {
IZKPassportVerifier public zkPassportVerifier;

// Map users to their verified unique identifiers
mapping(address => bytes32) public userIdentifiers;

constructor(address _verifierAddress) {
zkPassportVerifier = IZKPassportVerifier(_verifierAddress);
}

function register(ProofVerificationParams calldata params, bool isIDCard) public returns (bytes32) {
// Verify the proof
(bool verified, bytes32 uniqueIdentifier) = zkPassportVerifier.verifyProof(params);
require(verified, "Proof is invalid");

// Check the proof was generated using your domain name (scope) and the subscope
// you specified
require(
zkPassportVerifier.verifyScopes(params.publicInputs, "your-domain.com", "my-scope"),
"Invalid scope"
);

// Check if the user is at least 18 years old
bool isAgeAboveOrEqual = zkPassportVerifier.isAgeAboveOrEqual(
18,
params
);

// Get the disclosed data to retrieve the nationality
DisclosedData memory disclosedData = zkPassportVerifier.getDisclosedData(
params,
isIDCard
);
// Alpha 3 code of the nationality (e.g. FRA, USA, etc.)
string memory nationality = disclosedData.nationality;

// Use the getBoundData function to get the data bound to the proof
BoundData memory boundData = zkPassportVerifier.getBoundData(params);
// Make sure the user's address is the one that is calling the contract
require(boundData.userAddress == msg.sender, "Not the expected sender");
// Make sure the chain id is the same as the one you specified in the query builder
require(boundData.chainId == block.chainid, "Invalid chain id");
// You could also check the custom data if you bound any to the proof
require(boundData.customData == "my-custom-data", "Invalid custom data");
// If you didn't specify any custom data, make sure the string is empty
// require(bytes(boundData.customData).length == 0, "Custom data should be empty");

// Store the unique identifier
userIdentifiers[msg.sender] = uniqueIdentifier;

return uniqueIdentifier;
}

// Your contract functionality using the verification
function restrictedFunction() public view {
require(userIdentifiers[msg.sender] != bytes32(0), "Not verified");
// Function logic for verified users
}
}

5. Use the SDK to connect your frontend and your smart contract

Connect your frontend to the smart contract using the SDK:

import { ZKPassport } from "@zkpassport/sdk";
import { createWalletClient, createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import { custom } from "viem/wallet";

async function verifyOnChain(proofResult, walletProvider, isIDCard) {
const zkPassport = new ZKPassport("your-domain.com");

// Get verification parameters
const verifierParams = zkPassport.getSolidityVerifierParameters({
proof: proofResult,
// Use the same scope as the one you specified with the request function
scope: "my-scope",
// Enable dev mode if you want to use mock passports, otherwise keep it false
devMode: false,
});

// Create wallet client
const walletClient = createWalletClient({
chain: sepolia,
transport: custom(walletProvider),
});

// Get the account
const [account] = await walletClient.getAddresses();

// Create a public client
const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});

// Call your contract with the verification parameters
const hash = await walletClient.writeContract({
address: YOUR_CONTRACT_ADDRESS,
abi: YOUR_CONTRACT_ABI,
functionName: "register",
args: [verifierParams, isIDCard],
account,
});

// Wait for the transaction
await publicClient.waitForTransactionReceipt({ hash });

console.log("Verification completed on-chain!");
}