Verifiable Credentials and Verifiable Presentations: issuing, signing and verification

Table of contents


Incremental creation and verification of Verifiable Credentials

The client-sdk exposes a VerifiableCredential class that is useful to incrementally create valid Verifiable Credentials of any type, sign them and verify them. Once the credential is initialized, you can sequentially call the different methods provided by the class to add contexts, types, issuance dates and everything else.

Building a Verifiable Credential

The first step to build a Verifiable Credential is to initialize it, we can do that using the VerifiableCredential class constructor which takes a credentialId as sole argument:

let vc = new VerifiableCredential("http://example.edu/credentials/2803");

You now have an unsigned Verifiable Credential in the vc variable! This Credential isn't signed since we only just initialized it. It brings however some useful defaults to make your life easier.

>    vc.context
<-   ["https://www.w3.org/2018/credentials/v1"]
>    vc.issuanceDate
<-   "2020-04-14T14:48:48.486Z"
>    vc.type
<-   ["VerifiableCredential"]
>    vc.credentialSubject
<-   []

The default context is an array with "https://www.w3.org/2018/credentials/v1" as first element. This is required by the VCDMv1 specs so having it as default helps ensure your Verifiable Credentials will be valid in the end.

A similar approach was taken on the type property, where the default is an array with "VerifiableCredential" already populated. This is also required by the specs. The subject property is required to exist, so this is already initialized for you as well although it is empty for now. Finally the issuanceDate is also set to the moment you initialized the VerifiableCredential object. You can change this later if desired but it helps having it in the right format from the get go.

We could also have checked those defaults more easily by checking the Verifiable Credential's JSON representation.

This can be achieved by calling the toJSON() method on it:

>    vc.toJSON()
<-   {
       "@context": [ "https://www.w3.org/2018/credentials/v1" ],
       "credentialSubject": [],
       "id": "http://example.edu/credentials/2803",
       "type": [
         "VerifiableCredential"
       ],
       "issuanceDate": "2020-04-14T14:48:48.486Z"
     }

An interesting thing to note here is the transformation happening to some of the root level keys in the JSON representation of a VerifiableCredential object.

For example context gets transformed into @context and subject into credentialSubject.

This is to ensure compliance with the Verifiable Credential Data Model specs while at the same time providing you with a clean interface to the VerifiableCredential class in your code.

Once your Verifiable Credential has been initialized, you can proceed to use the rest of the building functions to define it completely before finally signing it.

Adding a Context

A context can be added with the addContext method. It accepts a single argument context which can either be a string (in which case it needs to be a valid URI), or an object:

>   vc.addContext('https://www.w3.org/2018/credentials/examples/v1')
>   vc.context
<-  [
      'https://www.w3.org/2018/credentials/v1',
      'https://www.w3.org/2018/credentials/examples/v1'
    ])

Adding a Type

A type can be added with the addType function. It accepts a single argument type that needs to be a string:

>   vc.addType('AlumniCredential')
>   vc.type
<-  [
      'VerifiableCredential',
      'AlumniCredential'
    ]

Adding a Subject

A subject can be added with the addSubject function. It accepts a single argument subject that needs to be an object with an id property:

>   vc.addSubject({ id: 'did:dock:123qwe123qwe123qwe', alumniOf: 'Example University' })
>   vc.credentialSubject
<-  {id: 'did:dock:123qwe123qwe123qwe', alumniOf: 'Example University'}

Setting a Status

A status can be set with the setStatus function. It accepts a single argument status that needs to be an object with an id property:

>   vc.setStatus({ id: "https://example.edu/status/24", type: "CredentialStatusList2017" })
>   vc.status
<-  {
        "id": "https://example.edu/status/24",
        "type": "CredentialStatusList2017"
    }

Setting the Issuance Date

The issuance date is set by default to the datetime you first initialize your VerifiableCredential object.

This means that you don't necessarily need to call this method to achieve a valid Verifiable Credential (which are required to have an issuanceDate property).

However, if you need to change this date you can use the setIssuanceDate method. It takes a single argument issuanceDate that needs to be a string with a valid ISO formatted datetime:

>   vc.issuanceDate
<-  "2020-04-14T14:48:48.486Z"
>   vc.setIssuanceDate("2019-01-01T14:48:48.486Z")
>   vc.issuanceDate
<-  "2019-01-01T14:48:48.486Z"

Setting an Expiration Date

An expiration date is not set by default as it isn't required by the specs. If you wish to set one, you can use the setExpirationDate method.

It takes a single argument expirationDate that needs to be a string with a valid ISO formatted datetime:

>   vc.setExpirationDate("2029-01-01T14:48:48.486Z")
>   vc.expirationDate
<-  "2029-01-01T14:48:48.486Z"

Signing a Verifiable Credential

Once you've crafted your Verifiable Credential it is time to sign it. This can be achieved with the sign method.

It requires a keyDoc parameter (an object with the params and keys you'll use for signing) and it also accepts a boolean compactProof that determines whether you want to compact the JSON-LD or not:

>   await vc.sign(keyDoc)

Please note that signing is an async process. Once done, your vc object will have a new proof field:

>   vc.proof
<-  {
        type: "EcdsaSecp256k1Signature2019",
        created: "2020-04-14T14:48:48.486Z",
        jws: "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..MEQCIAS8ZNVYIni3oShb0TFz4SMAybJcz3HkQPaTdz9OSszoAiA01w9ZkS4Zx5HEZk45QzxbqOr8eRlgMdhgFsFs1FnyMQ",
        proofPurpose: "assertionMethod",
        verificationMethod: "https://gist.githubusercontent.com/faustow/13f43164c571cf839044b60661173935/raw"
    }

Verifying a Verifiable Credential

Once your Verifiable Credential has been signed you can proceed to verify it with the verify method. The verify method takes an object of arguments, and is optional.

If you've used DIDs you need to pass a resolver for them. You can also use the booleans compactProof (to compact the JSON-LD).

If your credential has uses the credentialStatus field, the credential will be checked not to be revoked unless you pass skipRevocationCheck flag.

>   const result = await vc.verify({ ... })
>   result
<-  {
      verified: true,
      results: [
        {
          proof: [
            {
                '@context': 'https://w3id.org/security/v2',
                type: "EcdsaSecp256k1Signature2019",
                created: "2020-04-14T14:48:48.486Z",
                jws: "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..MEQCIAS8ZNVYIni3oShb0TFz4SMAybJcz3HkQPaTdz9OSszoAiA01w9ZkS4Zx5HEZk45QzxbqOr8eRlgMdhgFsFs1FnyMQ",
                proofPurpose: "assertionMethod",
                verificationMethod: "https://gist.githubusercontent.com/faustow/13f43164c571cf839044b60661173935/raw"
            }
          ],
          verified: true
        }
      ]
    }

Please note that the verification is an async process that returns an object when the promise resolves. A boolean value for the entire verification process can be checked at the root level verified property.


Incremental creation and verification of Verifiable Presentations

The client-sdk exposes a VerifiablePresentation class that is useful to incrementally create valid Verifiable Presentations of any type, sign them and verify them.

Once the presentation is initialized, you can sequentially call the different methods provided by the class to add contexts, types, holders and credentials.

Building a Verifiable Presentation

The first step to build a Verifiable Presentation is to initialize it, we can do that using the VerifiablePresentation class constructor which takes an id as sole argument:

let vp = new VerifiablePresentation("http://example.edu/credentials/1986");

You now have an unsigned Verifiable Presentation in the vp variable!

This Presentation isn't signed since we only just initialized it. It brings however some useful defaults to make your life easier.

>    vp.context
<-   ["https://www.w3.org/2018/credentials/v1"]
>    vp.type
<-   ["VerifiablePresentation"]
>    vp.credentials
<-   []

The default context is an array with "https://www.w3.org/2018/credentials/v1" as first element. This is required by the VCDMv1 specs so having it as default helps ensure your Verifiable Presentations will be valid in the end.

A similar approach was taken on the type property, where the default is an array with "VerifiablePresentation" already populated. This is also required by the specs.

The credentials property is required to exist, so this is already initialized for you as well although it is empty for now.

We could also have checked those defaults more easily by checking the Verifiable Presentation's JSON representation.

This can be achieved by calling the toJSON() method on it:

>    vp.toJSON()
<-   {
       "@context": [ "https://www.w3.org/2018/credentials/v1" ],
       "id": "http://example.edu/credentials/1986",
       "type": [
         "VerifiablePresentation"
       ],
       "verifiableCredential": [],
     }

An interesting thing to note here is the transformation happening to some of the root level keys in the JSON representation of a VerifiablePresentation object.

For example context gets transformed into @context and credentials into verifiableCredential. This is to ensure compliance with the Verifiable Credentials Data Model specs while at the same time providing you with a clean interface to the VerifiablePresentation class in your code.

Once your Verifiable Presentation has been initialized, you can proceed to use the rest of the building functions to define it completely before finally signing it.

Adding a Context

A context can be added with the addContext method. It accepts a single argument context which can either be a string (in which case it needs to be a valid URI), or an object

>   vp.addContext('https://www.w3.org/2018/credentials/examples/v1')
>   vp.context
<-  [
      'https://www.w3.org/2018/credentials/v1',
      'https://www.w3.org/2018/credentials/examples/v1'
    ])

Adding a Type

A type can be added with the addType function. It accepts a single argument type that needs to be a string:

>   vp.addType('CredentialManagerPresentation')
>   vp.type
<-  [
      'VerifiablePresentation',
      'CredentialManagerPresentation'
    ]

Setting a Holder

Setting a Holder is optional and it can be achieved using the setHolder method. It accepts a single argument type that needs to be a string (a URI for the entity that is generating the presentation):

>   vp.setHolder('https://example.com/credentials/1234567890');
>   vp.holder
<-  'https://example.com/credentials/1234567890'

Adding a Verifiable Credential

Your Verifiable Presentations can contain one or more Verifiable Credentials inside.

Adding a Verifiable Credential can be achieved using the addCredential method. It accepts a single argument credential that needs to be an object (a valid, signed Verifiable Credential):

>   vp.addCredential(vc);
>   vp.credentials
<-  [
      {...}
    ]

Please note that the example was truncated to enhance readability.

Signing a Verifiable Presentation

Once you've crafted your Verifiable Presentation and added your Verifiable Credentials to it, it is time to sign it.

This can be achieved with the sign method. It requires a keyDoc parameter (an object with the params and keys you'll use for signing), and a challenge string for the proof.

It also accepts a domain string for the proof, a resolver in case you're using DIDs and a boolean compactProof that determines whether you want to compact the JSON-LD or not:

>   await vp.sign(
          keyDoc,
          'some_challenge',
          'some_domain',
        );

Please note that signing is an async process. Once done, your vp object will have a new proof field:

>   vp.proof
<-  {
      "type": "EcdsaSecp256k1Signature2019",
      "created": "2020-04-14T20:57:01Z",
      "challenge": "some_challenge",
      "domain": "some_domain",
      "jws": "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..MEUCIQCTTpivdcTKFDNdmzqe3l0nV6UjXgv0XvzCge--CTAV6wIgWfLqn_62U8jHkNSujrHFRmJ_ULj19b5rsNtjum09vbg",
      "proofPurpose": "authentication",
      "verificationMethod": "https://gist.githubusercontent.com/faustow/13f43164c571cf839044b60661173935/raw"
    }

Verifying a Verifiable Presentation

Once your Verifiable Presentation has been signed you can proceed to verify it with the verify method.

If you've used DIDs you need to pass a resolver for them. You can also use the booleans compactProof (to compact the JSON-LD).

If your credential uses the credentialStatus field, the credential will be checked to be not revoked unless you pass skipRevocationCheck. For the simplest cases you only need a challenge string and possibly a domain string:

>   const results = await vp.verify({ challenge: 'some_challenge', domain: 'some_domain' });
>   results
<-  {
      "presentationResult": {
        "verified": true,
        "results": [
          {
            "proof": {
              "@context": "https://w3id.org/security/v2",
              "type": "EcdsaSecp256k1Signature2019",
              "created": "2020-04-14T20:57:01Z",
              "challenge": "some_challenge",
              "domain": "some_domain",
              "jws": "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..MEUCIQCTTpivdcTKFDNdmzqe3l0nV6UjXgv0XvzCge--CTAV6wIgWfLqn_62U8jHkNSujrHFRmJ_ULj19b5rsNtjum09vbg",
              "proofPurpose": "authentication",
              "verificationMethod": "https://gist.githubusercontent.com/faustow/13f43164c571cf839044b60661173935/raw"
            },
            "verified": true
          }
        ]
      },
      "verified": true,
      "credentialResults": [
        {
          "verified": true,
          "results": [
            {
              "proof": {
                "@context": "https://w3id.org/security/v2",
                "type": "EcdsaSecp256k1Signature2019",
                "created": "2020-04-14T20:49:00Z",
                "jws": "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..MEUCIQCCCRuJbSUPePpOfkxsMJeQAqpydOFYWsA4cGiQRAR_QQIgehRZh8XE24hV0TPl5bMS6sNeKtC5rwZGfmflfY0eS-Y",
                "proofPurpose": "assertionMethod",
                "verificationMethod": "https://gist.githubusercontent.com/faustow/13f43164c571cf839044b60661173935/raw"
              },
              "verified": true
            }
          ]
        }
      ]
    }

Please note that the verification is an async process that returns an object when the promise resolves.

This object contains separate results for the verification processes of the included Verifiable Credentials and the overall Verifiable Presentation.

A boolean value for the entire verification process can be checked at the root level verified property.

Using DIDs

The examples shown above use different kinds of URIs as id property of different sections. It is worth mentioning that the use of DIDs is not only supported but also encouraged.

Their usage is very simple: create as many DIDs as you need and then use them instead of the URIs shown above.

For example when adding a subject to a Verifiable Credential here we're using a DID instead of a regular URI in the id property of the object:vc.addSubject({ id: 'did:dock:123qwe123qwe123qwe', alumniOf: 'Example University' }).

If you don't know how to create a DID there's a specific tutorial on DIDs you can read.

Bear in mind that you will need to provide a resolver method if you decide to use DIDs in your Verifiable Credentials or Verifiable Presentations. More on resolvers can be found in the tutorial on Resolvers.

Here's an example of issuing a Verifiable Credential using DIDs, provided that you've created and a DID that you store in issuerDID:

const issuerKey = getKeyDoc(
  issuerDID,
  dock.keyring.addFromUri(issuerSeed, null, "ed25519"),
  "Ed25519VerificationKey2018"
);
await vc.sign(issuerKey);
const verificationResult = await signedCredential.verify({
  resolver,
  compactProof: true,
});
console.log(verificationResult.verified); // Should print `true`

Creating a keyDoc

It can be seen from the above examples that signing of credentials and presentations require keypairs to be formatted into a keyDoc object.

There is a helper function to help with this formatting, it's called getKeyDoc and it is located in the vc helpers.

Its usage is very simple, it accepts a did string which is a DID in fully qualified form, a keypair object (generated by either using polkadot-js's keyring for Sr25519 and Ed25519 or keypair generated with generateEcdsaSecp256k1Keypair for curve secp256k1) and a type string containing the type of the provided key (one of the supported 'Sr25519VerificationKey2020', 'Ed25519VerificationKey2018' or 'EcdsaSecp256k1VerificationKey2019'):

const keyDoc = getKeyDoc(did, keypair, type);

Please check the example on the previous section or refer to the presenting integration tests for a live example.