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 class and grade attributes in the credential;
  • proof that the user's value of the rank attribute is less than or equal to 10.

Verifier1 does NOT request the following information from the user:

  • the value of the instructor attribute;
  • the exact value of the rank 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": ["class", "grade"],
      "restrictions": [{"cred_def_id": "<cred-def-id>"}]
    }, 
    {
      "name": "phone_number"
    }
  ],
  "requested_predicates": [
    {
      "name": "rank",
      "p_type": "<=",
      "p_value": 10,
      "restrictions": [{"cred_def_id": "<cred-def-id>"}]
    }
  ],
  "cred_filter": [
    {
       "attr_name": "class",
       "attr_values": ["Math 100"]
       "exclude": true
    },
    {
       "attr_name": "class",
       "attr_values": ["Math *"]
    }
  ]
}

The following is a description of each of the sections in the proof schema.

  • name and version

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

  • requested_attributes

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 the class and grade attributes come from a single credential which was issued by a specific issuer which owns <cred-def-id>. This is most often what is intended.

WARNING: name verses names

Suppose that Faber college issues credentials with two attributes, class and grade, and that Alice has two credentials from Faber college in her wallet as follows:

{"class":"Math 100", "grade":"A"} {"class":"Math 200", "grade":"D"}

If requested_attributes was specified as follows, this would allow Alice to return a proof with a class of "Math 200" from the second credential and a grade of "A" from the first credential, thus misrepresenting herself.

"requested_attributes": [ { "name": "class", "restrictions": [{"cred_def_id": "<cred-def-id>"}] }, { "name": "grade", "restrictions": [{"cred_def_id": "<cred-def-id>"}] } ]

Therefore, you should always use the "names" format rather than "name" unless you are certain that you want to allow multiple attributes to come from different credentials.

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 phone_number attribute but it does not have to come from an issued credential; the prover may choose to either (a) manually provide the attribute value (i.e. not from a credential in the prover's wallet) or (b) if the prover does happen to have a credential with that attribute name in their wallet, this value may be used.

  • requested_predicates

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 rank must be less than or equal to 10.

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.

  7. cred_filter

The purpose of the cred_filter is to reduce the number of choices from which the prover can select. This reduction in choices can be based on the value of one or more attributes in a credential.

The value of cred_filter is an array of rules. The first rule matched determines whether the credential should be included or excluded from being seen by the prover. The fields of each element of the rule are:

  • attr_name is a required field and is the name of an attribute from the credential.
  • attr_values is a required field and is an array of strings. Each string in the array may be a regular expression. The rule is matched if a credential contains the attribute name and the value matches any of the regular expressions in the attr_values list.
  • exclude is an optional field and is a boolean field with a default value of false. If a rule matches, the value of exclude determines whether to include or exclude the credential from the prover's view.

In the example above, there are 2 rules:

  1. The first rule (shown again below) says to exclude credentials that have an attribute named "class" and a value of "Math 100".

    { "attr_name": "class", "attr_values": ["Math 100"] "exclude": true }

    1. The second rule (shown again below) says to include credentials that have an attribute named "class" and a value beginning with "Math ".

    { "attr_name": "class", "attr_values": ["Math *"] }

Note that format of a proof schema is the same as the format of a proof request. A proof schema is just a reusable and referencable proof request. You may choose to create proof schemas or simply dynamically specify the proof request for each verification.

Now that we have described the fields of a proof schema or proof request, let's see how this can be used to begin a verification.

Verifier-initiated verification

A verifier-initiated verification begins with the verifier sending a proof request to the prover.

Verifier1 creates a verification object in outbound_proof_request state which sends a proof request as follows:

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 as previously described.

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 with different attribute values. The credential(s) which satisfy parts of a proof request (and which are included by the cred_filter if present) are returned in the choices element of the verification. The prover may use the choices element to determine which attributes (or groups of attributes as specified in a "names" array) from which credentials to return in the proof.

At least one selection is required per section of the proof request. However, you may also return attributes from multiple credentials for a single proof request element.

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 the user does not delete the previous credential, the user's wallet will contain multiple credentials with identical contents.

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
Verifier-initiated verification invitation

Suppose a verifier wants to send a verification request to one or more users but does not know the users in advance. The verifier can create an verification invitation which contains a verification request.

For example, verifier1 creates an verification invitation with a verification request as follows:

curl -u $VERIFIER1 -X POST -d '{"attach":{"recipient":"invitee","use_connection":true,"verification_request":{"state": "outbound_proof_request", "proof_schema_id": "proof-schema1:1.0"}}' $URL/api/v1/invitations

Let equal the value of the "short_url" field in the response.

User1 processes the invitation containing the verification request as follows:

curl -u $USER1 -X PUT -d '{"url":"<invitation-short-url>"}' $URL/api/v1/invitation_processor

The remainder of the verification interaction continues as described above with user1 viewing verifications with inbound proof requests.

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-initiated verification invitation

User1 may create a verification invitation with a verification proposal as follows:

curl -u $USER1 -X POST -d '{"attach":{"recipient":"invitee","use_connection":true,"verification_proposal":{"state": "outbound_verification_request", "proof_schema_id": "proof-schema1:1.0"}}' $URL/api/v1/invitations

Let equal the value of the "short_url" field in the response.

Verifier1 processes the invitation containing the verification proposal as follows:

curl -u $VERIFIER1 -X PUT -d '{"url":"<invitation-short-url>"}' $URL/api/v1/invitation_processor

The remainder of the verification interaction continues as described above with verifier1 viewing the verification proposal.

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