Verifiable Credentials and Verifiable Presentations: issuing, signing and verification
Table of contents
- Incremental creation and verification of VC
- Incremental creation and verification of VP
- Using DIDs
- Creating a keyDoc
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.