Verify Proofs

This section demonstrates how to perform verification.

First, you need to determine what your verifier will request, keeping in mind the principle of minimum disclosure. In other words, your verifier should request the least amount of information it needs in order to perform it's job.

In this tutorial, verifier1 will request the following information from user1:

  • proof that the user is in possession of the credential which was issued by issuer1;
  • the value of the attr1 attribute in the credential;
  • proof that the user's value of the attr3 attribute is greater than or equal to 5.

Verifier1 does NOT request the following information from the user:

  • the value of the attr2 attribute;
  • the exact value of the attr3 attribute.

Verifier initialization

Verifier1 must first determine the ID of issuer1's credential definition.

In the real world, this may have been posted on issuer1's web-site or is otherwise publicly available. This is similar to determining the public key of a certificate authority in the PKI world, except that a credential definition ID is even more permanent in that it written to the ledger.

However, in a test environment where the ledger may be destroyed and recreated, you may want to dynamically look up this information. In the next section, we show how the verifier can dynamically look up the credential definition ID.

Dynamically looking up a credential definition ID

A credential definition ID is associated with a single issuer. In order for a verifier to lookup the credential definition ID from the issuer, the verifier must have a connection with the issuer. Furthermore, the verifier's connection to the issuer must have at least one custom property.

For example, the following shows how verifier1 creates a connection offer for issuer1 with a custom property named "trustedIssuer". The name and value of the property can be anything, but they should be appropriately named to reflect its purpose.

curl -u verifier1:verifier1pw -X POST -d '{"properties":{"trustedIssuer":"true"}}' $URL/api/v1/connections -H 'Content-Type: application/json' -o offer.json

Similarly, custom properties may be specified when the connection offer is accepted, though it not required for this tutorial. The following command accepts the connection offer as demonstrated earlier.

curl -u issuer1:issuer1pw -X POST -d @offer.json $URL/api/v1/connections -H 'Content-Type: application/json'

Now that the verifier's side of the connection to the issuer is labeled with a property ("trustedIssuer" in this case), we can use this label to retrieve a credential definition id from the issuer's agent as follows:

curl -u verifier1:verifier1pw $URL/api/v1/credential_definitions?route=trustedIssuer:true

Note that although you are talking directly to the verifier's agent (because you used -u verifier1:verifier1pw), your are gathering information from an agent to which the verifier has a relationship (because you specified the route=trustedIssuer:true query parameter). This causes the verifier agent to look through all of its connections with a custom property of "trustedIssuer" and value of "true" and recursively sends requests to those agents.

The response from this call will contain the responses from all agents which it called, which was only issuer1 in this case.

The route query parameter is actually more general in two ways:

  1. It is also supported for the /api/v1/credential_schemas call.
  2. The value can be a comma-separated list of property names and values, resulting in multiple levels of collection.

For example, if route=prop1:val1,prop2:val2, the first agent called looks for connections with prop1=val1.

Each agent called then looks for connections with prop2=val2 custom properties, etc.

Let <cred-def-id> be the value of the credential definition ID (i.e. the value of the id field in the previous response) for the remainder of the tutorial.

Creating a proof schema

At initialization time, a verifier may optionally create a proof schema. The ID of the proof schema can then be referenced by the verifier each time a verification occurs. Or, if you do not want to create a proof schema at initialization time, you could just specify the proof request each time a verification occurs.

The following command creates a proof schema for verifier1:

curl -u verifier1:verifier1pw -X POST -d @proof_schema.json $URL/api/v1/proof_schemas -H 'Content-Type: application/json'

where proof_schema.json contains content similar to the following. Note that the two occurrences of <cred-def-id> below must be replaced with the actual value as determined in the previous section.

{
  "name": "proof-schema1",
  "version": "1.0",
  "requested_attributes": {
    "attr1_referent": {
      "name": "attr1",
      "restrictions": [{"cred_def_id": "<cred-def-id>"}]
    },
    "self_attested_attr1_referent": {
      "name": "self-attested-attr1"
    }
  },
  "requested_predicates": {
    "predicate1_referent": {
      "name": "attr3",
      "p_type": ">=",
      "p_value": 5,
      "restrictions": [{"cred_def_id": "<cred-def-id>"}]
    }
  }
}

The combination of 'name' and 'version' must be unique for each proof schema.

The 'requested_attributes' section allows you to request the value of a specific attribute. All 'requested_attributes' must have an attribute name specified by the 'name' field.

The 'requested_predicates' section allows you to request a proof pertaining to an attribute without actually knowing the value of the attribute. The canonical example is asking someone if they are 21 years old or older without actually revealing their age. All 'requested_predicates' must have a 'name' field for the attribute name, a 'p_type' field which specifies the predicate operation, and a 'p_value' field which is the value to which to compare the attribute value. In the example above, the value of the attribute named 'attr3' must be greater than or equal to 5.

Note: Currently in Indy, 'p_type' MUST be >= and 'p_value' must be an integer.

For each requested attribute or predicate which is NOT self-attested, there must also be a 'restrictions' field which lists the criteria which must be true pertaining to the attribute or predicate. The value of the 'restrictions' field is an array. There is a logical OR between each element of the array, and a logical AND between all keys in a single element of the array.

For example, consider the following restrictions field:

'restrictions': [{'schema_name': 'myschema', 'schema_version': '1.0'}, {'cred_def_id': 'XXX'}]

This can be read as (schema_name == 'myschema' AND schema_version == '1.0') OR cred_def_id == 'XXX'.

The supported restriction operators are:

  1. cred_def_id which is the credential definition ID;
  2. schema_id which is the DID (not "id") of a credential schema;
  3. schema_issuer_did which is the DID of the schema issuer;
  4. schema_name which is the name of the schema;
  5. schema_version which is the value of the schema;
  6. issuer_did which is the DID of the issuer of the credential.

The "id" field in the response of the previous curl command is known as the <proof_schema_id>.

Now that verifier initialization is complete, there are two types of verifications to consider: verifier-initiated and user-initiated.

Verifier-initiated verification

A verifier-initiated verification begins with the verifier creating a proof request.

Verifier1 creates a proof request with the following command.

curl -u verifier1:verifier1pw -X POST -d '{"to": {"name": "user1"}, "state": "outbound_proof_request", "proof_schema_id": "proof-schema1:1.0"}' $URL/api/v1/verifications -H 'Content-Type: application/json'

The use of the "to" clause is the same as used when issuing a credential. In this example, we use "name", but you may also use "did" or "url" as previously mentioned.

Note that the value of the "proof_schema_id" field is the value of the id field when creating the proof schema with the previous command.

User1 views all proof requests as follows:

curl -u user1:user1pw $URL/api/v1/verifications?state=inbound_proof_request

Or user1 views this specific proof request as follows, where is the ID of the verification:

curl -u user1:user1pw $URL/api/v1/verifications/<id>

In either case, user1 views the proof_request.requested_attributes section to see the attributes being requested and proof_request.requested_predicates section to see the predicates being requested.

When you receive a proof request (i.e. a verification in 'inbound_proof_request' state), you may have multiple credentials in your wallet which can satisfy one or more parts of the proof request.
Multiple issuers may issue the same credential schema or a single issuer may issue a credential multiple times to the same user. The credential(s) which satisfy parts of a proof request are specified in the choices element of the verification.

When viewing a verification which is in 'inbound_proof_request' state, a 'choices' field is returned which enumerates the credentials in your wallet which match each part of the proof request. This can be used to allow the user to select which credentials to use when building the proof.

The choices section of a response is of the following form:

{
   "choices": {
      "attributes": {
         "attr1_referent": {
            "<from-credential1>": {
               "name": "first_name",
               "value": "user1",
               "cred_def_id": "Up36FJDNu3YGKvhTJAiZQU:3:CL:31:TAG1",
               "schema_id": "EDEuxdBQ_3zb6GzWKCNcyW4:2:Transcript:1.0"
            },
            "<from-credential2>": {
               "name": "first_name",
               "value": "user1",
               "cred_def_id": "Up36FJDNu3YGKvhTJAiZQU:3:CL:31:TAG1",
               "schema_id": "EDEuxdBQ3zb6GzWKCNcyW4:2:Transcript:1.0"
            }
         }  
      },
      "predicates": {
         "pred1_referent": {
            "<from-credential1>": {
               "predicate": "average GE 10",
               "cred_def_id": "Up36FJDNu3YGKvhTJAiZQU:3:CL:31:TAG1",
               "schema_id": "EDEuxdBQ3zb6GzWKCNcyW4:2:Transcript:1.0"
            }
         }
      }  
   }  
}

In the sample above, there are two choices for attribute attr1_referent: <from-credential1> and <from-credentiald2>. Each attribute choice contains a 'name' (attribute name), 'value' (attribute value), 'cred_def_id' (credential definition ID), and 'schema_id' (credential schema ID).

There is only a single choice for predicate <pred1_referent> above: <from-credential1>. Predicate choices contain a 'predicate' (string representation of the predicate), 'cred_def_id' (credential definition ID), and 'schema_id' (credential schema ID).

WARNING: If an issuer re-issues a credential with the same attribute values and the user does not delete the previous credential, the user's wallet will contain multiple credentials with identical contents. An enhancement request has been opened against Indy to add the issuance time stamp in order to distinguish between these credential instances. For now, you may select any credential.

The following demonstrates how user1 generates the proof for the proof request where <id> is the verification ID:

curl -u user1:user1pw -X PATCH -d @proof_gen.json $URL/api/v1/verifications/<id> -H 'Content-Type: application/json'

where proof_gen.json is a file of the following form:

{
  "state":"proof_generated",
  "self_attested_attributes": {
    "self_attested_attr1_referent": "myval"
  },
  "choices": {
    "attributes": {
      "attr1_referent": "<from-credential2>"
    },
    "predicates": {
      "pred1_referent": "<from-credential1>"
    }
  }
}

Note that the value provided by user1 for the "self_attested_attr1_referent" attribute is "myval", <from-credential2> was selected rather than <from-credential1> for the "attr1_referent" attribute, and <from-credential1> (the only choice) was selected for the "pred1_referent" predicate.

You may also omit the "choices" field completely, in which case a choice is randomly selected for you. In the future when Indy provides an issuance time stamp, the most recent time stamp will be selected.

curl -u user1:user1pw -X PATCH -d '{"state":"proof_generated"}' $URL/api/v1/verifications/<id> -H 'Content-Type: application/json'

Next, user1 decides to share the proof which was generated with verifier1 by changing the state to 'proof_shared' as follows:

curl -u user1:user1pw -X PATCH -d '{"state":"proof_shared"}' $URL/api/v1/verifications/<id> -H 'Content-Type: application/json'

The above request sends the proof to verifier1's agent, verifier1's agent verifies the proof and sends the result of the verification back to user1's agent. Assuming the proof was successfully verified by verifier1's agent, the state of this verification is changed to 'passed' for both verifier1 and user1.

You can check view the state of this verification for verifier1 as follows to see that it is 'passed':

curl -u verifier1:verifier1pw $URL/api/v1/verifications/<id>

To view all verifications which are not in a 'passed' state:

curl -u verifier1:verifier1pw $URL/api/v1/verifications?filter=\\{\"state\":\\{\"\$ne\":\"passed\"\\}\\}

For additional details on query operators, refer to the list of available options.

User-initiated verification

This section demonstrates the verification flow in which the user initiates the flow.

The only difference between user-initiated and verifier-initiated verifications is that the user-initiated begins with an additional initial step in which the user sends a verification request to the verifier.

User1 sends a verification request to verifier1 as follows:

curl -u user1:user1pw -X POST -d '{"state": "outbound_verification_request", "to": {"name": "verifier1"}, "proof_schema_id": "proof-schema1:1.0"}' $URL/api/v1/verifications -H 'Content-Type: application/json'

Verifier1 views all inbound verification requests and finds the one from user1, taking note of its id.

curl -u verifier1:verifier1pw $URL/api/v1/verifications?state=inbound_verification_request

Verifier1 issues a proof request to user1 based on its request, where <id> is the id value from the previous step.

curl -u verifier1:verifier1pw -X PATCH -d '{"state": "outbound_proof_request"}' $URL/api/v1/verifications/<id> -H 'Content-Type: application/json'

The remainder of the user-initiated verification is identical to the verifier-initiated verification, beginning at "User1 views all proof requests as follows".

User-specified Proof Requests

By default, the user MUST use the proof request specified by the verifier when generating a proof. However, it is not always possible for the verifier to know ahead of time which issuers and even which attributes it wants to accept.

Consider for example an employer who wants to hire the best candidate. The employer may want to allow candidates to decide what proofs they provide based on the candidate's degrees and work experience. In this case, the employer may not be able to reasonably create a proof request appropriate for all candidates.

Instead, the employer creates a proof request which can be treated as a suggestion by the candidate, and the candidate is then allowed to provide proof of what will best represent the candidate to the employer.

In order to allow a user (i.e. the candidate in the previous scenario) to provide a proof request, the verifier must specify allow_proof_request_override as shown below.

Verifier1 creates a proof request by specifying the proof schema and version and allow_proof_request_override option, and sends the request to user1.

curl -u verifier1:verifier1pw -X POST -d '{"to": {"name": "user1"}, "state": "outbound_proof_request", "proof_schema_id": "proof-schema1:1.0", "allow_proof_request_override": true}' $URL/api/v1/verifications -H 'Content-Type: application/json'

User1 views all proof requests as follows:

curl -u user1:user1pw $URL/api/v1/verifications?state=inbound_proof_request

Or user1 views this specific proof request as follows, where <id> is the value of the id field of the verification created above:

curl -u user1:user1pw $URL/api/v1/verifications/<id>

In either case, user1 views the proof_request.requested_attributes and proof_request.requested_predicates sections as previously mentioned.

However, since verifier1 specified the 'allow_proof_request_override' option, user1 can optionally specify the proof request from which the proof is generated. This overrides the proof request suggested by verifier1.

The following command demonstrates how to override the proof request and provide your own. Be sure to replace <id> with the actual "id" of the verification.

curl -u user1:user1pw -X PATCH -d @proof_gen.json $URL/api/v1/verifications/<id> -H 'Content-Type: application/json'

where proof_gen.json is a file is of the following format and both occurrences of <credential_definition_id> are replaced with the appropriate value.

{ 
  'state': 'proof_generated',
  'proof_request': {
     'name': 'user1-proof-request',
     'version': '1.0',
     'requested_attributes': {
       'attr1_referent': {
         'name': 'attr1'
         'restrictions': [{'cred_def_id': '<credential_definition_id>'}]
       }
     },
     'requested_predicates': {
       'predicate1_referent': {
         'name': 'attr3',
         'p_type': '>=',
         'p_value': 5,
         'restrictions': [{'cred_def_id': '<credential_definition_id>'}]
       }
     }
   }
}

Note that the format of the above file is the same as the proof_schema.json file described in the "Creating a proof schema" section.

Next, user1 views the generated proof and decides to share it with verifier1 by changing the state to 'proof_shared' as follows:

curl -u user1:user1pw -X PATCH -d '{"state":"proof_shared"}' $URL/api/v1/verifications/<id> -H 'Content-Type: application/json'

The above request sends the proof to verifier1's agent. Verifier1's agent verifies the cryptographic integrity of the proof and then sends the result of the verification back to user1's agent. Assuming the proof was successfully verified by verifier1's agent, the state of this verification is changed to 'passed' for both verifier1 and user1.

You can view the state of this verification for verifier1 as follows to see that it is 'passed':

# curl -u verifier1:verifier1pw $URL/api/v1/verifications/<id>

Or the state of this verification for user1 as follows to also see that it is 'passed':

# curl -u user1:user1pw $URL/api/v1/verifications/<id>

Now that you have performed both issuance and verification, let's demonstrate how to discover inbound requests which have been sent to your agent.