Skip to content


After having presented a Request to the user's wallet, the wallet will process the request and generate a proof that is sent back to the Verifier. The proof must be verified in order to authenticate the user. Let us see how to execute this verification.

The proof verification always follows the same flow independently of the Request type presented in the previous step by the Verifier, whether it is a basic auth or a query-based request.

Unpack the proof

import (

tokenBytes, err := io.ReadAll(req.Body)
const getRawBody = require('raw-body')

const raw = await getRawBody(req);
const tokenStr = raw.toString().trim();

req is the post request sent by the wallet in response to the Auth Request posed by the Verifier. This unpacks the proof sent by the wallet.

Initiate the verifier

var verificationKeyloader = &loaders.FSKeyLoader{Dir: keyDIR}
resolver := state.ETHResolver{
    RPCUrl:          ethURL,
    ContractAddress: common.HexToAddress(contractAddress),

resolvers := map[string]pubsignals.StateResolver{
    resolverPrefix: resolver,

verifier, err := auth.NewVerifier(
        verificationKeyloader, resolvers, auth.WithIPFSGateway("<gateway url>"))
const ethStateResolver = new resolver.EthStateResolver(

const resolvers: resolver.Resolvers = {
    ['polygon:mumbai']: ethStateResolver,

const verifier = await Verifier.newVerifier(
    stateResolver: resolvers,
    circuitsDir: path.join(__dirname, './circuits-dir'),
    ipfsGatewayURL:"<gateway url>"

This creates a resolver which is used to fetch the identity state from the State Smart Contract and a verification key loader which is used to fetch the verification keys necessary to verify a zero-knowledge proof. Eventually, it returns an instance of a Verifier. To set up a verifier, different parameters need to be passed:

  • circuitsDir is the path where the public verification keys for Iden3 circuits are located (such as "./circuits"). If not folder is set './circuits' folder is used. The verification key folder can be found here. Path to the circuit file is constructed from ${circuitsDir}/${circuitId}/verification_key.json,
  • hierarchical structure for files in curuits folder is mandatory
  • e.g. --circuits
  • -----circuitId
  • ---------file

  • ethURL is the URL of your RPC node provider such as "" for Polygon Mumbai.

  • contractAddress is the address of the identity state Smart Contract. On Polygon Mumbai, it is 0x134B1BE34911E39A8397ec6289782989729807a4.
  • resolverPrefix is the prefix of the resolver. For Polygon Mumbai it is "polygon:mumbai"
  • ipfsGatewayURL can be your IPFS gateway or public one. You can path your loader or IPFS node also.

Execute the verification

authResponse, err := verifier.FullVerify(
let authResponse: protocol.AuthorizationResponseMessage;

const opts: VerifyOpts = {
    AcceptedStateTransitionDelay: 5 * 60 * 1000, // 5 minute

authResponse = await verifier.fullVerify(tokenStr, authRequest, opts);

Execute the verification. It verifies that the proof shared by the user satisfies the criteria set by the Verifier inside the initial request. tokenBytes contains the proof generated by the user's wallet. authRequest is the request previously presented to that specific user. AcceptedStateTransitionDelay(time.Minute*5)) is the delay accepted by the Verifier. By setting it to 5 minutes, as in this case, the Verifier accepts a proof that verifies the validity of a credential against a state, as stated in the Smart Contract, which is up to 5 minutes old.

An example of the usage of this API can be found here (GO) and here (JS)

Verification - Under the Hood

The auth library provides a simple handler to extract all the necessary metadata from the proof and execute all the verifications needed. The verification procedure that is happening behind the scenes involves the following steps:

Zero-Knowledge Proof Verification

Starting from the circuit-specific public verification key, the proof, and the public inputs provided by the user, it is possible to verify the proof. In this case, the proof verification involves:

Verification of On-chain Identity States

Starting from the DID of the user, the State is fetched from the blockchain and compared to the state provided as input to the proof; this is done to check whether the user is the actual "owner" of the state used to generate the proof or not. It is important to note here that there is no gas cost associated with the verification as the VerifyState method just reads the identity state of the user on-chain without making any operations/smart contract calls. The same verification is performed for the Issuer's Identity State.

In this part, it is also verified that the requested credential has not been revoked by the Issuer.

Verification of Circuit Public Inputs

This involves a verification based on the public inputs of the circuits used to generate the proof. These must match the rules requested by the Verifier inside the Auth Request. For example, the query and the credential schema used by the user to generate the proof must match the Auth Request:

  • The message signed by the user must match the one passed to the user inside the auth request.
  • The rules such as the query or the credential schema used to generate the proof must match the ones included inside the auth request.

This "off-circuit" verification is important because a user can potentially modify the query and present a valid proof. A user born after 2000-12-31 shouldn't pass the check. But if they generate a proof using a query input "$lt": 20010101, the Verifier would see it as a valid proof. By doing verification of the public inputs of the circuit, the Verifier is able to detect malicious actors.

At the end of the workflow:

  • The web client is able to authenticate the user using its DID ID after having established that the user controls that identity and satisfies the query presented in the auth request.
  • The user is able to log into the platform without disclosing any personal information to the client except for its DID.