Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

Setting Up On-Chain Identity Verification

A step-by-step technical guide for developers to integrate decentralized identity (DID) and verifiable credentials into a security token stack for regulatory compliance.
Chainscore © 2026
introduction
INTRODUCTION

Setting Up On-Chain Identity Verification

A guide to implementing and understanding decentralized identity verification using blockchain primitives.

On-chain identity verification is the process of cryptographically proving and managing personal or entity attributes on a public blockchain. Unlike traditional systems controlled by central authorities, this approach uses decentralized identifiers (DIDs) and verifiable credentials (VCs) to give users control over their data. Protocols like the World Wide Web Consortium (W3C) standards provide the foundational framework, while blockchains like Ethereum, Polygon, and Solana serve as the immutable registry and verification layer. This shift enables trustless interactions for KYC, access control, and reputation systems without relying on a single point of failure.

The core technical components involve three main actors: the issuer (an entity that attests to a claim), the holder (the user who owns the credential), and the verifier (the service that needs proof). A common implementation uses Soulbound Tokens (SBTs) or non-transferable NFTs to represent credentials. For example, a university could issue an SBT to a graduate's wallet address as a tamper-proof diploma. When applying for a job, the graduate can generate a zero-knowledge proof (ZKP) from this SBT to prove they hold a degree without revealing the specific university or graduation date, using a system like Semaphore or Sismo.

To set up a basic verification flow, you'll need to interact with smart contracts and identity libraries. A typical stack includes the Ethereum Attestation Service (EAS) for creating on- or off-chain attestations, or Ceramic Network for managing decentralized data streams. For developers, the process often starts by integrating an SDK like ethr-did or veramo. The following code snippet shows how to create a simple DID document using Veramo: const identifier = await agent.didManagerCreate({ alias: 'user1', provider: 'did:ethr:goerli' });. This creates a DID anchored to the Goerli testnet, which can then be linked to verifiable credentials.

Key considerations for implementation include privacy, gas costs, and user experience. While storing data fully on-chain is transparent, it can compromise privacy and be expensive. Solutions involve storing credential hashes on-chain with the actual data in decentralized storage like IPFS or Arweave. Furthermore, managing private keys for DIDs remains a UX hurdle; integrating with smart contract wallets or social recovery via protocols like Safe{Wallet} and ERC-4337 account abstraction can help. It's also critical to design revocation mechanisms, often handled through smart contract allowlists or timestamp-based expiries.

Real-world applications are expanding across DeFi, DAOs, and social platforms. In DeFi, on-chain identity can enable under-collateralized lending based on creditworthiness attested by other protocols. DAOs use it for sybil-resistant governance, where one-person-one-vote is enforced via unique identity proofs. Projects like Gitcoin Passport aggregate credentials from various web2 and web3 sources to compute a trust score for grant eligibility. As the ecosystem matures, interoperability across chains and standards is being driven by cross-chain messaging protocols like LayerZero and Wormhole, allowing a credential issued on Polygon to be verified on Avalanche.

Getting started requires choosing the right protocol for your use case. For attestations, explore EAS or Proof of Humanity. For flexible credential management, consider Veramo or Spruce ID's Kepler. Always begin development on a testnet, thoroughly audit any smart contract handling identity data, and prioritize user consent in data sharing flows. The ultimate goal is to build systems that are not only secure and decentralized but also practical and accessible for end-users, moving beyond proof-of-concept to mainstream adoption.

prerequisites
PREREQUISITES

Setting Up On-Chain Identity Verification

Before implementing on-chain identity, you need a foundational understanding of key Web3 concepts and tools. This guide outlines the essential knowledge and setup required.

On-chain identity verification moves beyond simple wallet addresses to establish a persistent, verifiable digital identity anchored on a blockchain. Unlike traditional systems, it leverages decentralized identifiers (DIDs) and verifiable credentials (VCs). A DID is a unique, self-sovereign identifier (e.g., did:ethr:0xabc123...) controlled by the user's private key, not a central authority. Verifiable Credentials are tamper-proof attestations (like a university degree or KYC proof) issued by trusted entities and cryptographically signed, which can be presented to verifiers without revealing unnecessary personal data.

To work with this technology stack, you must be comfortable with core Web3 development tools. Essential prerequisites include: a working knowledge of Ethereum or other EVM-compatible chains, proficiency with JavaScript/TypeScript and Node.js, and experience with wallet interaction libraries like ethers.js or viem. You should understand how to sign messages and transactions using a wallet's private key, as this is fundamental to proving control over a DID. Familiarity with IPFS is also beneficial for storing credential metadata or schemas in a decentralized manner.

Several protocols and standards form the backbone of on-chain identity. The World Wide Web Consortium (W3C) defines the core specifications for DIDs and Verifiable Credentials. In the Ethereum ecosystem, ERC-725 and ERC-735 are key standards for managing identity and claims on-chain. For practical development, explore SDKs from projects like SpruceID, which offers the did:key and did:ethr methods and the Sign-In with Ethereum (SIWE) standard, or Veramo, a modular framework for creating and verifying credentials. Setting up a local test environment with Hardhat or Foundry is crucial for testing your integration before mainnet deployment.

A typical implementation flow involves three parties: the Holder (user), the Issuer (entity issuing credentials), and the Verifier (entity requiring proof). The Holder creates a DID. An Issuer signs a credential with the Holder's DID as the subject. The Holder then stores this credential, often in a secure wallet or agent. When a Verifier requests proof, the Holder presents a selective disclosure, generating a cryptographically verifiable proof without exposing the raw credential data. This preserves privacy while enabling trust.

Start by experimenting with existing tools. Use the SpruceID credential-3 playground to issue and verify sample credentials. Deploy a simple ERC-725 smart contract to a testnet to understand the on-chain component. Write a script using the Veramo CLI or SDK to create a DID and request a test credential from a demo issuer. This hands-on approach will solidify the concepts of key management, signing, and verification that are critical for building production-ready identity systems.

architecture-overview
SYSTEM ARCHITECTURE OVERVIEW

Setting Up On-Chain Identity Verification

This guide explains the core architectural patterns for implementing decentralized identity verification on-chain, focusing on privacy, interoperability, and user sovereignty.

On-chain identity verification moves beyond simple wallet addresses to establish verifiable credentials and decentralized identifiers (DIDs). Unlike traditional systems, this architecture is user-centric: individuals control their own identity data, storing proofs in a personal data vault (like a wallet) and presenting only the minimal, necessary verification to applications. The core components include the Identity Registry (a smart contract mapping DIDs to public keys), Verifiable Credential Issuers (trusted entities that sign claims), and Verifiers (dApps that request and validate proofs). This separation of concerns is fundamental to the Self-Sovereign Identity (SSI) model.

The verification flow typically involves three parties. First, a user obtains a credential, such as proof of age or KYC status, from an Issuer. The Issuer signs this credential cryptographically and the user stores it. When a Verifier (e.g., a lending protocol) requires proof, it sends a request. The user's wallet constructs a zero-knowledge proof (ZKP) or a simple signed presentation, demonstrating they hold a valid credential without revealing the underlying data. This presentation is sent on-chain for the Verifier's smart contract to validate, often by checking the Issuer's signature against a trusted registry.

Key architectural decisions involve choosing a DID method and credential format. The W3C DID specification provides standards, with popular implementations like did:ethr (anchored to Ethereum) or did:key. For credentials, the W3C Verifiable Credentials Data Model is the standard, often implemented with JSON-LD or simpler JWT formats. Storage is critical: credentials should never be stored fully on-chain due to privacy and cost. Instead, IPFS, Ceramic Network, or the user's local device hold the data, while on-chain registries store only essential hashes, DIDs, and revocation lists.

For developers, implementing this starts with selecting a toolkit. Libraries like SpruceID's didkit, Veramo, or Ethereum's ethr-did resolver simplify creating and managing DIDs. A basic smart contract for an identity registry might manage a mapping of DIDs to controllers and delegate keys. When integrating verification, use established standards like EIP-712 for structured data signing to ensure user comprehension. Always design for selective disclosure, allowing users to prove specific attributes (e.g., "over 18") instead of exposing their full birthdate.

Security and privacy are paramount. Architectures must include efficient revocation mechanisms, such as smart contract-based revocation registries or accumulator-based schemes like Semaphore. Avoid patterns that create on-chain correlation; using ZKPs generated by circuits (e.g., with Circom or Halo2) is best for sensitive data. Furthermore, consider gasless verification via meta-transactions or off-chain proof verification with on-chain settlement to improve user experience. The ultimate goal is a system where trust is minimized, user data is protected, and verification is a seamless, interoperable process across the decentralized web.

key-concepts
ON-CHAIN IDENTITY

Core Concepts and Standards

Foundational protocols and standards for establishing and verifying identity on public blockchains, enabling applications from decentralized credentials to Sybil resistance.

06

Soulbound Tokens (SBTs)

Soulbound Tokens are non-transferable NFTs that represent commitments, credentials, or affiliations. They are permanently "bound" to a wallet (a "Soul"), creating a persistent, composable social identity graph.

  • Concept Origin: Proposed by Vitalik Buterin in 2022.
  • Key Property: Non-transferability prevents credential selling but requires careful design for key recovery.
  • Applications: Educational degrees, employment history, DAO membership, and credit scores.
  • Example: Gitcoin Passport scores are represented as non-transferable stamps (SBT-like).
1M+
Gitcoin Passport Holders
step-1-issuer-integration
ONBOARDING

Step 1: Integrate with an Identity Issuer

The first step in establishing a verifiable on-chain identity is to integrate with a trusted issuer. This guide covers the initial setup using World ID, a popular protocol for privacy-preserving proof-of-personhood.

An identity issuer is a protocol or service that cryptographically attests to a specific claim about a user, such as their uniqueness or humanity, and issues a verifiable credential. For on-chain applications, World ID is a leading choice because it uses zero-knowledge proofs to verify a user is a unique human without revealing their personal data. Integration begins by installing the necessary SDKs. For a typical web app, you would add the @worldcoin/idkit package to your project using npm or yarn.

After installation, you must initialize the World ID widget in your application's frontend. This involves configuring the widget with your app's unique action_id and signal, which are used to prevent credential replay attacks across different actions within your dApp. The action_id should be a descriptive string for the specific user action you are gating (e.g., "vote-proposal-1"), while the signal can be an empty string or a user-specific nonce. Here is a basic React component setup example:

jsx
import { IDKitWidget } from '@worldcoin/idkit';

function VerificationButton() {
  return (
    <IDKitWidget
      app_id="app_staging_..." // Your app ID from the Developer Portal
      action="your-action-id"
      onSuccess={(proof) => console.log(proof)}
    >
      {({ open }) => <button onClick={open}>Verify with World ID</button>}
    </IDKitWidget>
  );
}

The verification process is handled client-side. When a user clicks the verify button, the World ID widget opens, guiding them through the proof generation using their World App or simulator. Upon successful verification, the onSuccess callback receives a proof object. This object contains the zero-knowledge proof and nullifier hash, which your backend must verify. Do not trust the client-side result alone. You must send this proof to your backend server for on-chain verification against the World ID smart contracts to ensure its validity and prevent fraud.

For backend verification, you need the @worldcoin/sdk package. The core function is verifyProof, which checks the proof's validity against the World ID contract on your desired chain (e.g., Optimism, Polygon, Ethereum). You must provide the proof from the frontend, your app_id, the action string, and the contract address for the World ID verifier. A successful verification returns true, confirming the user is a unique human who has not performed this specific action before. This result is what gates your application's functionality.

Finally, you should handle the verified user's identity in your system. The primary output you will use is the nullifier hash, a unique identifier derived from the user's World ID. It is consistent for the same user and action_id but reveals no personal information. Store this hash in your database to track which users have completed the gated action, ensuring each unique user can only perform it once. This completes the integration loop, enabling sybil-resistant features like one-person-one-vote governance, unique airdrop claims, or authenticated community access.

step-2-vc-presentation
ON-CHAIN VERIFICATION

Step 2: Handle Verifiable Presentation

This step explains how to process and verify a user's Verifiable Presentation (VP) on-chain to confirm their identity credentials.

A Verifiable Presentation (VP) is a cryptographically signed package, typically a JSON object, that a user submits to prove their identity claims. It contains one or more Verifiable Credentials (VCs) and is signed by the credential holder's Decentralized Identifier (DID). Your smart contract's primary job is to verify the digital signatures on the VP and the VCs within it, ensuring they were issued by trusted authorities and have not been tampered with. This process establishes a trustless verification of user attributes without needing to store personal data on-chain.

The core verification involves checking several cryptographic proofs. First, your contract must validate the VP's signature using the public key associated with the user's DID. Next, it must verify each contained VC's signature against the issuer's DID. For Ethereum-based DIDs (did:ethr, did:pkh), this means recovering the signer's address from the ECDSA signature (e.g., using ecrecover in Solidity). You must also check for revoked credentials by querying the issuer's revocation registry, often a smart contract or a verifiable data registry.

Here is a simplified Solidity function outline for verification:

solidity
function verifyPresentation(
    bytes memory vp,
    address issuerRegistry
) public view returns (bool) {
    // 1. Decode VP JSON (requires off-chain helper or on-chain parser)
    // 2. Extract holder signature and recover signer address
    address recoveredHolder = ECDSA.recover(vpHash, vpSignature);
    require(recoveredHolder == msg.sender, "Invalid VP signature");
    // 3. For each VC in the VP:
    //    - Recover issuer address from VC signature
    //    - Check issuer is in trusted registry
    //    - Query issuerRegistry to ensure VC is not revoked
    // 4. Return true if all checks pass
}

In practice, full JSON-LD parsing on-chain is gas-intensive, so many projects use off-chain verifiers or zero-knowledge proofs to generate a succinct proof for on-chain validation.

Key considerations for production systems include selective disclosure, where users prove specific claims without revealing the entire credential (using zero-knowledge proofs like zk-SNARKs), and presentation expiration. Always check the VC's expirationDate and the VP's validUntil fields. Frameworks like Veramo or SpruceID's Kepler can handle much of this complexity off-chain, generating a verification receipt that your contract can easily check. The on-chain logic should be minimal, focused only on validating proofs and checking revocation status against a known registry.

After successful verification, your application logic can grant access. For example, a contract might mint a Soulbound Token (SBT) to the user's address as proof of their verified status, or unlock specific functions within a dApp. This step transforms a cryptographic proof of identity into a usable, persistent on-chain record, enabling Sybil-resistance, gated communities, and compliant DeFi interactions without relying on centralized KYC providers.

step-3-smart-contract-verifier
ON-CHAIN LOGIC

Step 3: Build the Permissioning Smart Contract

Implement the core access control logic that verifies user identity and permissions directly on the blockchain.

The permissioning smart contract is the on-chain authority for your application. It defines the rules of access by storing and verifying user credentials. A common pattern is to use a mapping that links a user's wallet address (like 0x123...) to a structured data type containing their verification status and role. For example, you might store a User struct with fields for isVerified (boolean), verificationTimestamp (uint256), and accessLevel (enum like NONE, BASIC, ADMIN). This contract becomes the single source of truth for who can perform which actions.

The contract's critical function is the verification check. This is typically a modifier or an internal require statement that guards other functions. For instance, a function to mint a token might start with require(users[msg.sender].isVerified, "Not verified");. This ensures the transaction reverts if the caller's address isn't in the verified state. You can extend this logic to check for specific roles or that verification was completed within a certain time window, enabling time-bound access.

To populate this on-chain state, you need setter functions that are callable only by an administrator or a trusted off-chain verifier. A function like verifyUser(address _user) would update users[_user].isVerified = true and set the timestamp. It's crucial to protect this function with an onlyOwner modifier or a more sophisticated multi-signature scheme to prevent unauthorized modifications. The OpenZeppelin Access Control library provides battle-tested contracts like Ownable and AccessControl for implementing these guards.

Consider gas efficiency and data storage. Storing data on-chain is expensive. Optimize by using uint256 for timestamps and packing boolean flags into bitmaps if you have many attributes. For complex identity attestations (like proof of KYC), you may store only a cryptographic hash of the credential on-chain and verify a zero-knowledge proof, keeping the private data off-chain. This pattern is used by protocols like Semaphore for anonymous signaling.

Finally, your frontend application will interact with this contract. After a user connects their wallet (like MetaMask), your dApp will call the contract's read function (e.g., getUserStatus(address)) to check their permissions before allowing them to proceed to gated features. This on-chain check is trustless and transparent; any user can independently verify the rules governing the application's access control.

ON-CHAIN VERIFICATION

Identity Provider Comparison

A comparison of popular solutions for linking real-world identity to blockchain addresses.

Feature / MetricWorld IDGitcoin PassportBrightID

Core Verification Method

Orb biometric scan

Aggregated web2 & web3 stamps

Social graph verification

Sybil Resistance Guarantee

Unique person proof

Score-based attestations

Peer-to-peer verification

On-Chain Proof Format

Semaphore ZK proof

Verifiable Credential (VC)

BrightID attestation

Gas Cost per Verification

~200k-400k gas

~50k-150k gas

~100k-250k gas

Primary Use Case

Universal proof-of-personhood

Sybil-resistant governance & grants

Community-based unique identity

Integration Complexity

Medium (ZK circuits)

Low (score API)

Medium (social graph)

Decentralization Level

Semi-centralized (Orb hardware)

Centralized aggregator, decentralized stamps

Decentralized (user-operated nodes)

Recurring Verification Needed

step-4-token-gating
IMPLEMENTATION

Step 4: Gate Token Transfers with Verification

This step integrates on-chain identity verification to control token transfers, ensuring only authorized users can interact with your token.

On-chain verification gates token transfers by checking a user's verified status before allowing a transaction to proceed. This is implemented by modifying your token's transfer functions—like transfer and transferFrom—to include a verification check. The core logic is a require statement that reverts the transaction if the sender's address is not found in a registry of verified identities. This registry is typically a mapping, such as mapping(address => bool) public isVerified;, managed by the contract owner or a decentralized oracle.

For example, a basic ERC-20 transfer function with gating would look like this:

solidity
function transfer(address to, uint256 amount) public override returns (bool) {
    require(isVerified[msg.sender], "Sender not verified");
    require(isVerified[to], "Recipient not verified");
    return super.transfer(to, amount);
}

This pattern ensures both the sender and recipient are verified, which is crucial for compliance-focused tokens. You can extend this to transferFrom for delegated transfers and mint/burn functions if applicable. The verification status should be updateable, often via a privileged setVerified function.

Consider gas efficiency and user experience. Performing on-chain checks for every transfer adds minimal overhead but is a constant cost. For scalability, you might implement a bulk verification function or use Merkle proofs for large, static allowlists. Always ensure the verification mechanism itself is secure; the function that updates the isVerified mapping must be properly access-controlled to prevent unauthorized modifications, typically restricted to the contract owner or a designated governance module.

Real-world use cases include security tokens (STOs) requiring KYC, membership NFTs for gated communities, or tokens for decentralized autonomous organizations (DAOs) that require voter verification. Protocols like Centrifuge use similar patterns for real-world asset tokenization. When implementing, audit the integration thoroughly, as incorrect gating logic can permanently lock user funds or be bypassed if the verification check is placed in the wrong part of the function flow.

To test your implementation, write comprehensive unit tests that simulate both successful transfers from verified addresses and failed transfers from unverified ones. Use a development framework like Foundry or Hardhat. A key edge case to test is the interaction with decentralized exchanges (DEXs): if your token is listed on a DEX like Uniswap, the pool contract itself must be verified to receive tokens, or you must exempt it from checks, which has compliance implications. Document these behaviors clearly for users and integrators.

ON-CHAIN IDENTITY

Common Implementation Issues

Addressing frequent technical hurdles and developer questions when integrating on-chain identity verification systems.

This error typically stems from a mismatch in the signature verification parameters. The most common causes are:

  • Incorrect Signing Domain: The EIP712Domain parameters (name, version, chainId, verifyingContract) used during signing must exactly match those defined in your contract. A different chain ID or contract address will cause failure.
  • Nonce Mismatch: Many systems use a replay-protection nonce. If the signer's current nonce doesn't match the one encoded in the signed message, verification will revert.
  • Message Digest Mismatch: Ensure the structured data hash (typeHash and encodeData) is computed identically off-chain and on-chain. Libraries like ethers.js _signTypedData and OpenZeppelin's EIP712 must align.

Debugging Tip: Log the recovered signer address from ecrecover or ECDSA.recover and compare it to the expected signer's address off-chain.

ON-CHAIN IDENTITY

Frequently Asked Questions

Common technical questions and solutions for developers implementing on-chain identity verification, covering standards, smart contracts, and integration patterns.

ERC-725 and ERC-734 are complementary standards for decentralized identity, often implemented together in a KeyManager pattern.

ERC-725 (Identity) defines a smart contract-based identity wallet. Its core function is to hold claims and keys. A claim is a verifiable statement about the identity, issued by a trusted third party (an ERC-735 Claim Holder).

ERC-734 (Key Manager) defines the permission system for that identity. It manages which Ethereum addresses (keys) can perform specific actions, like signing transactions or adding new claims. A key can have management permissions (adding/removing keys), execution permissions (signing), or claim signing permissions.

In practice, an ERC-725 identity contract's owner is set to an ERC-734 KeyManager contract. All operations on the identity must then be approved by a key with the correct permission, enabling multi-sig and complex recovery schemes. This separation of data (ERC-725) and logic (ERC-734) is a core design principle for secure, upgradeable identities.

How to Integrate On-Chain Identity for Tokenization | ChainScore Guides