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.

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.

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 -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": [
    {
      "names": ["attr1", "attr2],
      "restrictions": [{"cred_def_id": "<cred-def-id>"}]
    }, 
    {
      "name": "attr3"
    }
  ],
  "requested_predicates": [
    {
      "name": "attr4",
      "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 one or more attributes. All requested_attributes elements must contain either a name field (whose value is a string) or a names field (whose value is an array of strings). If the names field is used with a restrictions field, the value of all attribute names MUST come from a single credential. For example, the proof schema above requires that both attr1 and attr2 attributes come from a single credential which was issued by a specific issuer which owns <cred-def-id>.

Suppose however that requested_attributes was instead specified as follows:

  ...
  "requested_attributes": [
    {
      "name": "attr1",
      "restrictions": [{"cred_def_id": "<cred-def-id>"}]
    },
    {
      "name": "attr2",
      "restrictions": [{"cred_def_id": "<cred-def-id>"}]
    },
    {
      "name": "attr3"
    }
  ],
  ...

In this case, it would be acceptable for the value of attr1 come from one credential and the value of attr2 from another credential. So when requesting multiple attribute values, be sure to specify the proof schema or proof request in the correct format.

A self-attested attribute can be requested by omitting the restrictions field from a requested_attributes element. In the sample above, a verifier asks a prover for the value of the attr3 attribute but it does not have to come from an issued credential; the prover simply provides the value without any third-party attestation.

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.

Valid values for p_type are >, >=, <, or <= 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 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 -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 id, 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 $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 $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 -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 (as follows), in which case all matching choices are included in the generated proof.

curl -u $USER1 -X PATCH -d '{"state":"proof_generated", "self_attested_attributes": {"self_attested_attr1_referent": "myval"}}' $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 -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 $URL/api/v1/verifications/<id>

To view all verifications which are in the passed state:

curl -u $VERIFIER1 $URL/api/v1/verifications?state=passed

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 -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 $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 -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 -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 $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 $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 -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 -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 $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 $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.