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 Voter Identity Verification with Decentralized IDs

A technical tutorial for implementing a voter identity layer using Decentralized Identifiers (DIDs) and Verifiable Credentials (VCs) for blockchain-based voting systems.
Chainscore © 2026
introduction
GUIDE

Introduction to DID-Based Voter Authentication

Decentralized Identifiers (DIDs) offer a secure, user-controlled alternative to traditional voter authentication. This guide explains how to set up a basic DID-based verification system for digital voting applications.

Traditional online voting systems rely on centralized identity providers, creating single points of failure and privacy concerns. Decentralized Identifiers (DIDs) are a W3C standard that give users control over their digital identity without a central registry. A DID is a unique URI (e.g., did:ethr:0xabc123...) that points to a DID Document containing public keys and service endpoints. For voter authentication, this means credentials are issued to and verified from a user's personal wallet or agent, not a central database.

The core components of a DID-based voting system are the issuer, holder, and verifier. A trusted entity (issuer) provides Verifiable Credentials (VCs)—digitally signed attestations like "is a citizen"—to the voter (holder). The voter stores these in their digital wallet. When accessing a voting dApp (verifier), they present a Verifiable Presentation (VP), proving they hold valid credentials without revealing unnecessary personal data. This process uses cryptographic proofs like digital signatures or zero-knowledge proofs.

To implement basic verification, you can use libraries like did-jwt-vc for Ethereum or didkit for cross-chain compatibility. First, a voter's wallet generates a DID. A government issuer then creates a signed VC. The voting platform, as a verifier, checks the VC's signature against the issuer's public key in the DID Document. Here's a simplified code snippet for verification using did-jwt-vc:

javascript
const { verifyCredential } = require('did-jwt-vc');
const verifiedVC = await verifyCredential(vcJwt, { resolver: didResolver });
if (verifiedVC.verified) {
  // Grant voting access
}

Key advantages of this model include user sovereignty (individuals control their data), reduced fraud through cryptographic verification, and privacy preservation via selective disclosure. Voters can prove they are eligible without revealing their full identity or creating a correlatable database of voters. However, challenges remain, such as ensuring the initial identity proofing (how the issuer verifies the holder is real) is robust and designing revocation mechanisms for lost credentials.

For production systems, consider integrating with existing DID methods like did:ethr (Ethereum), did:key, or did:web. Frameworks like Veramo provide pluggable architecture for managing DIDs, VCs, and interactions across different protocols. The next step is to explore sybil-resistance techniques, such as linking to Proof of Humanity or BrightID, and implementing anonymous voting protocols like MACI to separate identity verification from vote casting, ensuring a fully private and secure digital voting process.

prerequisites
FOUNDATION

Prerequisites and System Architecture

Before implementing voter identity verification, you must establish the core components of your decentralized identity (DID) system. This section outlines the required tools, infrastructure, and architectural patterns.

The foundation of a decentralized voter verification system is a DID method. This protocol defines how identities are created, resolved, updated, and deactivated on a specific blockchain or network. Common choices include did:ethr for Ethereum-compatible chains (using ERC-1056), did:key for simple key pairs, or did:web for web-hosted documents. You must also select a Verifiable Credentials (VC) data model standard, typically the W3C's Verifiable Credentials Data Model, which defines the structure for attestations like proof-of-citizenship or proof-of-age.

Your system's architecture will involve several key actors and components. The Issuer (e.g., a government agency or trusted KYC provider) creates and signs VCs for users. The Holder (the voter) stores these credentials in a digital wallet, such as MetaMask with Snap capabilities or a specialized wallet like SpruceID's Sign-In with Ethereum. The Verifier (your voting dApp) requests and validates these credentials. A critical architectural decision is whether to use on-chain verification (storing credential hashes or zero-knowledge proofs on-chain) or off-chain verification (using signed JSON-LD VCs verified client-side), which impacts gas costs, privacy, and complexity.

For development, you'll need a Node.js environment (v18+) and package manager like npm or yarn. Essential libraries include did-resolver and ethr-did-resolver for resolving DIDs, vc-js or veramo for creating and verifying credentials, and a web3 library like ethers.js or viem for blockchain interaction. For testing, configure a local blockchain instance using Hardhat or Foundry, and consider using test credentials from platforms like SpruceID's test suite or Trinsic's sandbox. Always manage private keys for test issuers using environment variables, never hardcoding them.

A typical data flow begins with the user's wallet generating a DID. The user then authenticates with an Issuer's service, receives a signed VC, and stores it locally. When interacting with the voting dApp, the dApp presents a Presentation Request, specifying the required credential type (e.g., CitizenshipCredential). The user's wallet creates a Verifiable Presentation, selectively disclosing proof, and sends it to the verifier. Your dApp's backend or smart contract logic must then cryptographically verify the presentation's signature and check that the issuer's DID is on a trusted registry.

key-concepts
SETTING UP VOTER IDENTITY VERIFICATION

Core Concepts for Implementation

Implementing secure, privacy-preserving identity verification is foundational for on-chain governance. This guide covers the key tools and standards for integrating decentralized IDs (DIDs) into voting systems.

06

Smart Contract Verification Patterns

On-chain voting contracts need to verify voter identity attestations efficiently and securely.

  • Pattern 1: Registry Contract: Maintain an on-chain registry (mapping) of verified DIDs. An off-chain relayer checks VCs and calls a permissioned function to add the DID.
  • Pattern 2: Signature Verification: Require voters to submit a signature from their DID's key alongside a verified attestation UID from EAS. The contract verifies the signature and checks the attestation's validity on-chain.
  • Gas Optimization: Store only minimal data on-chain (e.g., attestation UIDs or commitment hashes). Use EAS's on-chain schemas and SchemaRegistry for standardized, gas-efficient checks.
  • Example: A vote() function that requires require(isVerifiedDID(msg.sender), "Not verified");
< 100k
Gas for EAS on-chain check
issuer-setup
FOUNDATIONAL SETUP

Step 1: Setting Up the Government Issuer

This step establishes the trusted root of authority for issuing verifiable credentials to citizens, forming the bedrock of a decentralized voter identity system.

The Government Issuer is a specialized Decentralized Identifier (DID) that acts as the root trust anchor in the system. Unlike a standard user DID, it is controlled by a government entity and its cryptographic signature is what makes issued credentials authoritative and verifiable. You must first create this issuer DID on a Verifiable Data Registry (VDR), such as a public blockchain (e.g., Ethereum, Polygon) or a dedicated ledger like Sovrin. This creates a permanent, decentralized record of the issuer's public key, which anyone can query to verify signatures.

Once the DID is created on the VDR, you define its DID Document. This JSON-LD document, published at the DID's endpoint, declares the issuer's public keys, authentication mechanisms, and service endpoints. Crucially, you will configure a Verification Method—typically a public key—specifically for issuing credentials. This key is used to sign the Verifiable Credentials (VCs) that will be issued to citizens. The integrity of this entire system depends on securing the corresponding private key, which should be managed via hardware security modules or multi-party computation.

With the issuer DID established, you define the credential schema. A schema is a template that standardizes the data structure of the credentials you will issue. For voter identity, this schema would define fields like fullName, dateOfBirth, nationalIdNumber, and registeredConstituency. You publish this schema to a trusted schema registry, such as those provided by the Hyperledger Indy ecosystem or the Ethereum Attestation Service (EAS). Publishing a schema creates a unique Schema ID (e.g., schema:gov:voter-id:1.0), ensuring all issued credentials follow the same, verifiable format.

The final preparatory step is to create a Credential Definition. This is a cryptographic commitment to a specific schema by your issuer DID. In systems like Indy, it generates a Credential Definition ID and publishes a public key specifically for the blind signing of credentials based on that schema. In W3C-compliant systems, it involves creating and publishing a credential manifest that describes how to request a credential from your issuer. This step formally links your issuer's authority to the voter ID schema, enabling the actual issuance process.

voter-wallet-integration
DECENTRALIZED IDENTITY

Step 2: Voter Wallet and Credential Storage

This step explains how to establish a secure, self-sovereign voter identity using a crypto wallet and decentralized identifiers (DIDs).

A voter wallet is your primary tool for identity verification in a decentralized voting system. Unlike a traditional wallet, it doesn't just hold assets; it manages your Decentralized Identifier (DID) and Verifiable Credentials (VCs). A DID is a globally unique identifier, such as did:ethr:0xabc123..., that you control via cryptographic keys stored in your wallet. This creates a portable, self-sovereign identity that is not dependent on any central authority like a government or corporation.

To participate, you must first generate a DID. Using the Ethereum Attestation Service (EAS) or a similar framework, you can create an on-chain attestation that links your DID to a verified claim, like citizenship. The code snippet below shows a simplified example of generating a DID document using the ethr-did library:

javascript
const { EthrDID } = require('ethr-did');
const provider = /* your ethers provider */;
const wallet = /* your ethers wallet */;

const ethrDid = new EthrDID({
  identifier: wallet.address,
  chainNameOrId: 'sepolia',
  provider,
  registry: '0xdca7ef03e98e0dc2b855be647c39abe984fcf21b'
});

console.log('Your DID:', ethrDid.did);

Once your DID is established, you can receive Verifiable Credentials. These are tamper-proof digital certificates, often issued by a trusted entity (an Issuer), that attest to specific attributes about you. For voting, a crucial VC would be a proof of eligibility—attesting that you are a unique, verified member of the electorate. This credential is cryptographically signed by the Issuer and stored in your wallet, allowing you to present it without revealing unnecessary personal data.

The security model hinges on you, the holder, maintaining control. Your private keys, secured in the wallet, are required to sign transactions that prove ownership of your DID and to present your VCs. This is fundamentally different from centralized databases where your data is held by a third party. Best practices include using a hardware wallet for key storage and never sharing your seed phrase.

When you interact with a voting dApp, you will use your wallet (e.g., MetaMask) to connect. The dApp will request access to specific credentials, such as "Proof of Citizenship." Using protocols like Sign-In with Ethereum (SIWE) and W3C Verifiable Credentials, you can present a cryptographically verifiable proof of this claim without exposing your full identity or other credentials, enabling private yet provable eligibility checks.

This architecture ensures sybil resistance and privacy. Each eligible voter is mapped to a unique DID, preventing duplicate registrations. Meanwhile, zero-knowledge proofs (ZKPs) can be used with VCs to prove you hold a valid credential without revealing its contents, adding another layer of privacy for the actual voting act in subsequent steps.

on-chain-verification
IMPLEMENTATION

Step 3: On-Chain Verification Smart Contract

This guide details the development of a smart contract that verifies voter identity credentials stored in a Decentralized ID (DID) on-chain, a critical component for decentralized governance systems.

The core of on-chain verification is a smart contract that acts as a trust anchor. It defines the rules for accepting identity proofs and maintains a registry of verified addresses. For a DID-based system, the contract must be able to verify signatures from a user's DID controller and check the validity of their Verifiable Credentials (VCs). This typically involves implementing functions like verifyVoter(address user, bytes memory signature, bytes memory credentialProof) that validate the cryptographic proof linking the user's wallet to their government-issued ID credential stored in their DID document.

A common pattern is to use the EIP-712 standard for structured data signing. This allows users to sign a typed message (e.g., "I attest that I am a verified citizen") with their wallet private key, which is cryptographically linked to their DID. The smart contract can then recover the signer's address from this signature and compare it to the controller or verificationMethod listed in their on-chain DID document. Libraries like OpenZeppelin's ECDSA are essential for secure signature verification in Solidity.

The contract must also define and enforce verification criteria. For example, it might check that a presented credential: - Is issued by a trusted issuer (a specific DID). - Has not expired (validUntil timestamp). - Contains a credentialSubject.id that matches the user's DID. - Includes a type of "PermanentResidentCredential" or "PassportCredential". These checks ensure only legitimate, current credentials grant voting rights. Storing a mapping like mapping(address => uint256) public verificationExpiry allows the contract to track active verifications efficiently.

Here is a simplified Solidity snippet demonstrating the core verification logic using a hypothetical DIDRegistry contract interface:

solidity
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract VoterVerifier {
    using ECDSA for bytes32;
    DIDRegistry public didRegistry;
    address public trustedIssuer;

    function verifyCredential(
        address user,
        bytes memory credentialProof,
        uint256 validUntil
    ) public {
        // 1. Recover signer from the EIP-712 proof
        bytes32 digest = _hashTypedData(credentialProof);
        address signer = digest.recover(signature);

        // 2. Check signer is the user's DID controller
        require(didRegistry.getController(user) == signer, "Invalid controller");

        // 3. Validate credential metadata (pseudo-code)
        Credential memory cred = abi.decode(credentialProof, (Credential));
        require(cred.issuer == trustedIssuer, "Untrusted issuer");
        require(cred.validUntil > block.timestamp, "Credential expired");
        require(cred.subject == user, "Subject mismatch");

        // 4. Record successful verification
        verificationExpiry[user] = validUntil;
    }
}

After deployment, this contract becomes the source of truth for the application's access control. A frontend dApp would guide a user through the flow: 1) Connect wallet, 2) Request a Verifiable Presentation from their identity wallet (e.g., SpruceID, Veramo), 3) Submit the proof to the VoterVerifier contract, and 4) Upon successful transaction, the user's address is registered. Subsequent governance contracts, such as a DAO's voting module, can then simply query voterVerifier.isVerified(voterAddress) to gate participation, creating a seamless and self-sovereign identity layer for on-chain governance.

VERIFICATION STRATEGIES

Credential Schema and Disclosure Trade-offs

Comparison of DID credential schemas for voter verification, balancing privacy, verification complexity, and on-chain requirements.

FeatureSelective Disclosure (W3C VC)ZK-SNARK ProofOn-Chain Attestation

Privacy Level

High

Maximum

Low

Verification Gas Cost

~50k gas

~450k gas

~25k gas

Schema Flexibility

High

Medium

Low

Requires Trusted Issuer

Proof Revocable Off-Chain

Interoperability (DIDComm, OIDC)

Typical Issuance Latency

< 2 sec

5-10 sec

< 1 sec

Suitable for Anonymous Voting

privacy-preserving-flow
TUTORIAL

Step 4: Implementing the Privacy-Preserving Authentication Flow

This guide details the technical implementation for verifying voter identity using decentralized IDs (DIDs) and zero-knowledge proofs (ZKPs) to protect personal data.

The core of the privacy-preserving flow is a zero-knowledge proof (ZKP) that allows a user to cryptographically prove they hold a valid credential—like citizenship or age—without revealing the underlying data. We implement this using the Circom language for circuit design and the SnarkJS library for proof generation and verification. The circuit logic defines the constraints: for example, proving a user's birth date is before a certain cutoff without disclosing the date itself. This proof, along with a Decentralized Identifier (DID) from a provider like SpruceID or Veramo, forms the basis of anonymous authentication.

First, integrate a DID wallet like MetaMask with Sign-In with Ethereum (SIWE) to establish a user's cryptographic identity. The frontend prompts the user to sign a SIWE message, generating a verifiable did:ethr identifier. This DID is the public anchor for the user's private credentials. Next, the user requests a Verifiable Credential (VC) from a trusted issuer—a smart contract or an off-chain service—attesting to their eligibility. The issuer signs a JSON-LD credential containing the user's DID and the attested claims, which is then stored privately in the user's wallet.

The user then generates the ZKP. Using a pre-compiled Circom circuit (e.g., citizenVerifier.circom), they create a witness from their private credential data and a public input, like the current election's Merkle root. Running SnarkJS (snarkjs groth16 prove) generates a proof.json file and a publicSignals.json file. The proof demonstrates credential validity, while the public signals only reveal the necessary output, such as a commitment hash. This proof is submitted to our authentication API endpoint alongside the user's DID and the public signals.

On the backend, a verifier smart contract on Ethereum or a zkSNARK verifier library validates the proof. The contract's verifyProof function checks the ZKP against the trusted verification key and the submitted public signals. A successful verification triggers the contract to mint a privacy-preserving voting token (an SBT) to the user's DID. This token, which contains no personal data, grants access to the voting dApp. The entire process ensures the application never sees or stores raw identity data, aligning with data minimization principles.

For developers, key libraries include @spruceid/siwe for authentication, @veramo/core for credential management, and snarkjs for proof handling. A critical best practice is to use a trusted setup ceremony for the ZKP circuit to ensure security. Always audit the circuit logic for constraints that could inadvertently leak information. This architecture provides a robust template for any application requiring private credential verification, from voting to gated communities.

VOTER IDENTITY VERIFICATION

Common Implementation Issues and Troubleshooting

Implementing decentralized identity (DID) for voter verification presents unique challenges. This guide addresses the most frequent technical hurdles developers face when integrating systems like Verifiable Credentials, Soulbound Tokens, and on-chain attestations.

Smart contracts verify VCs by checking the digital signature and the credential status. Common failures include:

  • Expired or Revoked Credentials: The contract checks the credential's expirationDate and queries a Status List (like a revocation registry). Ensure your VC issuer's status endpoint is accessible and returns the correct JSON structure.
  • Invalid Proof Signature: The contract verifies the cryptographic proof attached to the VC. This often fails if the signing key (the issuer's DID) is not correctly resolved on-chain or if the proof format (e.g., Ed25519Signature2020) is not supported by your verifier library.
  • Schema Mismatch: The contract validates the VC's credentialSchema. The id and type in your VC must exactly match the schema registered in the contract. Use tools like @veramo/cli to validate your VC's structure before sending it on-chain.

Always test with a local blockchain first and inspect the contract's revert messages for specific failure codes.

VOTER IDENTITY

Frequently Asked Questions

Common technical questions and solutions for implementing decentralized identity verification in on-chain governance systems.

A Decentralized Identifier (DID) is a cryptographically verifiable identifier controlled by the user, not a central authority. For voting, it links a real-world identity to a blockchain address without exposing personal data.

How it works:

  1. A user generates a DID using a standard like W3C DID-Core.
  2. They obtain verifiable credentials (e.g., a proof of citizenship) from a trusted issuer, which are signed and stored in their digital wallet.
  3. To vote, the user presents a zero-knowledge proof (ZKP) derived from their credentials to the voting smart contract.
  4. The contract verifies the proof's validity and the issuer's signature, confirming eligibility without seeing the underlying data.

Protocols like Civic Pass, BrightID, or Polygon ID provide SDKs to integrate this flow, mapping a verified DID to a Sybil-resistant voting power.

conclusion
IMPLEMENTATION WRAP-UP

Conclusion and Next Steps

You have successfully configured a system for verifying voter identity using decentralized identifiers (DIDs). This guide covered the core components: DID creation, credential issuance, and on-chain verification.

The architecture you've built leverages self-sovereign identity principles, where users control their verifiable credentials (VCs) in a digital wallet like MetaMask or SpruceID. Issuers, such as a government entity or DAO, sign credentials containing claims (e.g., citizenship, residency) using their private key. The verifier smart contract, deployed on-chain, checks the credential's cryptographic proof against the issuer's public DID on a registry like Ethereum Name Service (ENS) or the ION network on Bitcoin. This creates a trustless verification flow without exposing personal data.

For production, consider these critical next steps. First, implement revocation checks using a status list (like a W3C Status List 2021) or a smart contract registry to invalidate credentials if a user's status changes. Second, enhance privacy with zero-knowledge proofs (ZKPs). Using libraries like Circom or SnarkJS, you can allow users to prove they hold a valid credential meeting certain criteria (e.g., "is over 18") without revealing the credential's contents, a pattern known as selective disclosure.

Explore advanced frameworks to streamline development. The Veramo SDK provides plugins for managing DIDs, issuing VCs, and interacting with multiple blockchain networks. For Ethereum-focused projects, Ethereum Attestation Service (EAS) offers a standardized schema registry and on-chain attestation model. Always audit your smart contracts, especially the signature verification logic, and conduct thorough testing with tools like Hardhat or Foundry to prevent vulnerabilities in the verification process.

The integration of DIDs and verifiable credentials is foundational for secure on-chain governance, Sybil-resistant airdrops, and compliant DeFi applications. By moving away from centralized KYC providers, you reduce custody risk and build more resilient, user-centric systems. Continue experimenting with emerging standards like W3C Decentralized Identifiers (DIDs) v1.0 and Verifiable Credentials Data Model v2.0 to ensure interoperability across the ecosystem.