First Install with npm install
This repository provides a comprehensive example of DID:WEB and Verifiable Credentials using the Decentralized Identity Javascript libraries.
It also makes available code snippets and fully working examples on how to:
- Create a private/public key pair.
- Prepare and publish a DID:WEB
- Create and Verify an arbitrary JWT.
- Issue and Verify Verifiable Credentials and Verifiable Presentations.
Test the codebase live
Use the commands
- Generate keys:
npm run keys
- Create and validate JWT:
npm run jwt
- Create and validate VCs:
npm start
The Decentralized Identity library makes available the ES256KSigner
and EdDSASigner
signers which use a 32 byte secp256k1 private key
and a 64 byte Ed25519 secret key
, respectively.
We will start with the former and introduce the latter right after.
We will use the crypto
library to generate a random 32byte string and the elliptic
library to create its public key.
import crypto from 'crypto';
import elliptic from 'elliptic';
// Request a 32 byte key
const key = crypto.randomBytes(32).toString("hex");
const ec = new elliptic.ec('secp256k1');
const prv = ec.keyFromPrivate(key,'hex').getPublic();
To get all the values we need for our DID:WEB
we run:
npm run keys
Output:
> node keys.js
Key (hex): 8eb63d435de4d634bc5f3df79c361e9233f55c9c2fca097758eefb018c4c61df
Public (hex): 040ed6461efcae34042cc75e1af79a844edf770c20a68f80f5f9c648fed06ae7f47898b20b0e6d5ed790298a5a02acf705d5134505aa9c4aab60f8d3c4c35e849c
x (hex): 0ed6461efcae34042cc75e1af79a844edf770c20a68f80f5f9c648fed06ae7f4
y (hex): 7898b20b0e6d5ed790298a5a02acf705d5134505aa9c4aab60f8d3c4c35e849c
x (base64): DtZGHvyuNAQsx14a95qETt93DCCmj4D1+cZI/tBq5/Q=
y (base64): eJiyCw5tXteQKYpaAqz3BdUTRQWqnEqrYPjTxMNehJw=
-- kty: EC, crv: secp256k1
Important:
Keep the private
Key (hex)
in a safe location. We will use this string when we issue and sign credentials.
JWK is one of the public key representations DID
supports and what we will use for our DID:WEB
.
Use the base64
values from the above output and prepare the JSON structure as it follows:
{
"kty":"EC",
"crv":"secp256k1",
"x":"DtZGHvyuNAQsx14a95qETt93DCCmj4D1+cZI/tBq5/Q=",
"y":"eJiyCw5tXteQKYpaAqz3BdUTRQWqnEqrYPjTxMNehJw=",
}
To prepare our DID:WEB
, we need the JWK
structure we prepared in the previous step and an identifier. The identifier comes from the URL where we will make our DID
available.
For example, the did:web:skounis.github.io
resolves to:
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:web:skounis.github.io",
"verificationMethod": [
{
"id": "did:web:skounis.github.io#owner",
"type": "JsonWebKey2020",
"controller": "did:web:skounis.github.io",
"publicKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "7afa3a377b5808e4223dd62542a6e7e46ab0be95873464520193c1857ec2bb8f",
"y": "58b050b73f31f1b8b98c0b04513257433bdad2a51188642d8b0e515fbfb3125f"
}
}
],
"authentication": [
"did:web:skounis.github.io#owner"
],
"assertionMethod": [
"did:web:skounis.github.io#owner"
]
}
Use the above example and:
- Replace the
did:web:skounis.github.io
with thedid
that correspond to the correct domain. - Replace the
publicKeyJwk
part with the values we generated in the previous step - Save the file as
did.json
and make it publicly available under the/.well-known/did.json
path.
e.g.: https://skounis.github.io/.well-known/did.json
Before we move to the Verifiable Credentials, we will test our DID by singing and verifying arbitrary JWTs. We will use the did-jwt
library from Decentralized Identity.
First, we need to create a Signer
using the private
key (hex) we prepared before.
import { ES256KSigner, hexToBytes } from 'did-jwt';
const key = '8eb63d435de4d634bc5f3df79c361e9233f55c9c2fca097758eefb018c4c61df';
const signer = ES256KSigner(hexToBytes(key))
Then we use this signer
and create our JWT
import { createJWT } from 'did-jwt';
const jwt = await createJWT(
{ aud: 'did:web:skounis.github.io', name: 'Bob Smith' },
{ issuer: 'did:web:skounis.github.io', signer },
{ alg: 'ES256K' }
)
We can also decode and display a friendly version of the JWT
:
import { decodeJWT } from 'did-jwt';
const decoded = decodeJWT(jwt)
console.log('JWT Decoded:\n',decoded)
This process resolves the DID:WEB
and uses its public key to verify the signature of the JWT. If the signature verifies the code returns (and displays) the payload. If not it displayes an error message (exception).
We again use the did-resolver
and web-did-resolver
libraries from Decentralized Identity.
We create a webResolver
that uses the DID
identifier, in this case, did:web:skounis.github.io
and resolves its public keys.
The library detects the signing method, constructs the public key, and verifies the signature of the JWT.
import { verifyJWT } from 'did-jwt';
import { Resolver } from 'did-resolver'
import { getResolver } from 'web-did-resolver'
const webResolver = getResolver()
const resolver = new Resolver({
...webResolver
})
verifyJWT(jwt, {
resolver,
audience: 'did:web:skounis.github.io'
}).then(({ payload, doc, did, signer, jwt }) => {
console.log('Verified:\n', payload)
})
We have put all these together into the did-jwt.js
file that
- Prepares a
signer
- Creates a signed JWT
- Unpacks (decode) and displays the JWT
- Resolves the
DID:WEB
and verifies the JWT
We can test it by running the following:
npm run jwt
Output
> node did-jwt.js
//// JWT:
eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE2NzMwMDc0MDcsImF1ZCI6ImRpZDp3ZWI6c2tvdW5pcy5naXRodWIuaW8iLCJuYW1lIjoiQm9iIFNtaXRoIiwiaXNzIjoiZGlkOndlYjpza291bmlzLmdpdGh1Yi5pbyJ9.xeehr7dZocJABA5U2HZrhPvWn4c5Q4sWhz-AkFXQbHWl8KJw6V9fVa1VsG2WxdBC4RdkRJGHRmXarGoHSwiTHA
//// JWT Decoded:
{
header: { alg: 'ES256K', typ: 'JWT' },
payload: {
iat: 1673007407,
aud: 'did:web:skounis.github.io',
name: 'Bob Smith',
iss: 'did:web:skounis.github.io'
},
signature: 'xeehr7dZocJABA5U2HZrhPvWn4c5Q4sWhz-AkFXQbHWl8KJw6V9fVa1VsG2WxdBC4RdkRJGHRmXarGoHSwiTHA',
data: 'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE2NzMwMDc0MDcsImF1ZCI6ImRpZDp3ZWI6c2tvdW5pcy5naXRodWIuaW8iLCJuYW1lIjoiQm9iIFNtaXRoIiwiaXNzIjoiZGlkOndlYjpza291bmlzLmdpdGh1Yi5pbyJ9'
}
//// Verified:
{
iat: 1673007407,
aud: 'did:web:skounis.github.io',
name: 'Bob Smith',
iss: 'did:web:skounis.github.io'
}
With all the pieces in place, we are ready to work with actual W3C Verifiable Credentials and Presentations.
We will re-use the Signer we already created before to sign our credentials. This signer uses our private key.
Our Verifiable Credential needs a payload with a structure according to the W3C Verifiable Credentials Data Model v1.1.
const vcPayload = {
sub: 'did:web:skounis.github.io',
nbf: 1562950282,
vc: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiableCredential'],
credentialSubject: {
degree: {
type: 'BachelorDegree',
name: 'Baccalauréat en musiques numériques'
}
}
}
}
Before we move with the creation of the credentials, we need to prepare our issuer
object. A structure that carries our did
and the signer
we already prepared.
const issuer = {
did: 'did:web:skounis.github.io',
signer: signer
}
With the issuer
and the vcPayload
prepared, we use the did-jwt-vc
library and create the credential as follows:
import { createVerifiableCredentialJwt } from 'did-jwt-vc'
const vcJwt = await createVerifiableCredentialJwt(vcPayload, issuer)
console.log(vcJwt)
This gives us back our VC in the form of JWT.
Similarly to the Credentials, we need to prepare a payload and then use it for creating the signed Presentation.
import { createVerifiablePresentationJwt } from 'did-jwt-vc'
// const vpPayload: JwtPresentationPayload = {
const vpPayload = {
vp: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiablePresentation'],
verifiableCredential: [vcJwt],
foo: "bar"
}
}
const vpJwt = await createVerifiablePresentationJwt(vpPayload, issuer)
console.log(vpJwt)
In this step, we will use our code to verify the issued Verifiable Credential and its Presentation.
We will use their JWT representation as input, which is already stored in the variables vcJwt
and vpJwt
.
The process will.
- Decode (unpack) the
JWT
strings - Resolve the
did
, in our case thedid:web:skounis.github.io
, and construct our public key. - Verify the signature of the credentials and display their payload.
import { Resolver } from 'did-resolver'
import { getResolver } from 'web-did-resolver'
import { verifyCredential, verifyPresentation } from 'did-jwt-vc'
const resolver = new Resolver(getResolver())
const verifiedVC = await verifyCredential(vcJwt, resolver)
console.log(verifiedVC)
const verifiedVP = await verifyPresentation(vpJwt, resolver)
console.log(verifiedVP)
We have put all these together into the did-jwt-vc.js
file that
- Prepares a
signer
- Creates a Verifiable Credential and a Verifiable Presentation
- Resolves the
DID:WEB
and verifies their JWTs
We can test it by running:
npm run vc
Output
> node did-jwt-vc.js
//// Verifiable Credential:
eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjY2FsYXVyw6lhdCBlbiBtdXNpcXVlcyBudW3DqXJpcXVlcyJ9fX0sInN1YiI6ImRpZDp3ZWI6c2tvdW5pcy5naXRodWIuaW8iLCJuYmYiOjE1NjI5NTAyODIsImlzcyI6ImRpZDp3ZWI6c2tvdW5pcy5naXRodWIuaW8ifQ.HdDBT9mpvCnV-vCLEygF8s1X9cGoT-nZn0ac87HiOo0WLUOPy7l5ezPTKEjf7UT7B3GokPjWAgXAEoB2DDkrVw
//// Verifiable Presentation:
eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0owZVhBaU9pSktWMVFpZlEuZXlKMll5STZleUpBWTI5dWRHVjRkQ0k2V3lKb2RIUndjem92TDNkM2R5NTNNeTV2Y21jdk1qQXhPQzlqY21Wa1pXNTBhV0ZzY3k5Mk1TSmRMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZEY21Wa1pXNTBhV0ZzSWwwc0ltTnlaV1JsYm5ScFlXeFRkV0pxWldOMElqcDdJbVJsWjNKbFpTSTZleUowZVhCbElqb2lRbUZqYUdWc2IzSkVaV2R5WldVaUxDSnVZVzFsSWpvaVFtRmpZMkZzWVhWeXc2bGhkQ0JsYmlCdGRYTnBjWFZsY3lCdWRXM0RxWEpwY1hWbGN5SjlmWDBzSW5OMVlpSTZJbVJwWkRwM1pXSTZjMnR2ZFc1cGN5NW5hWFJvZFdJdWFXOGlMQ0p1WW1ZaU9qRTFOakk1TlRBeU9ESXNJbWx6Y3lJNkltUnBaRHAzWldJNmMydHZkVzVwY3k1bmFYUm9kV0l1YVc4aWZRLkhkREJUOW1wdkNuVi12Q0xFeWdGOHMxWDljR29ULW5abjBhYzg3SGlPbzBXTFVPUHk3bDVlelBUS0VqZjdVVDdCM0dva1BqV0FnWEFFb0IyRERrclZ3Il0sImZvbyI6ImJhciJ9LCJpc3MiOiJkaWQ6d2ViOnNrb3VuaXMuZ2l0aHViLmlvIn0.gZJZoR0TTgeAeE_YNYzpGA9tJOg0iLRFdU3uqRPSNjgXLwBYBacbzXSHWLewHExgx0ZiltmHV3dQXmzbUYzpsg
//// Verified Credentials:
{
verified: true,
payload: {
vc: { '@context': [Array], type: [Array], credentialSubject: [Object] },
sub: 'did:web:skounis.github.io',
nbf: 1562950282,
iss: 'did:web:skounis.github.io'
},
didResolutionResult: {
didDocument: {
'@context': [Array],
id: 'did:web:skounis.github.io',
verificationMethod: [Array],
authentication: [Array],
assertionMethod: [Array]
},
didDocumentMetadata: {},
didResolutionMetadata: { contentType: 'application/did+ld+json' }
},
issuer: 'did:web:skounis.github.io',
signer: {
id: 'did:web:skounis.github.io#owner',
type: 'JsonWebKey2020',
controller: 'did:web:skounis.github.io',
publicKeyJwk: {
kty: 'EC',
crv: 'secp256k1',
x: 'DtZGHvyuNAQsx14a95qETt93DCCmj4D1+cZI/tBq5/Q=',
y: 'eJiyCw5tXteQKYpaAqz3BdUTRQWqnEqrYPjTxMNehJw='
}
},
jwt: 'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjY2FsYXVyw6lhdCBlbiBtdXNpcXVlcyBudW3DqXJpcXVlcyJ9fX0sInN1YiI6ImRpZDp3ZWI6c2tvdW5pcy5naXRodWIuaW8iLCJuYmYiOjE1NjI5NTAyODIsImlzcyI6ImRpZDp3ZWI6c2tvdW5pcy5naXRodWIuaW8ifQ.HdDBT9mpvCnV-vCLEygF8s1X9cGoT-nZn0ac87HiOo0WLUOPy7l5ezPTKEjf7UT7B3GokPjWAgXAEoB2DDkrVw',
policies: { nbf: undefined, exp: undefined, iat: undefined },
verifiableCredential: {
credentialSubject: { degree: [Object], id: 'did:web:skounis.github.io' },
issuer: { id: 'did:web:skounis.github.io' },
type: [ 'VerifiableCredential' ],
'@context': [ 'https://www.w3.org/2018/credentials/v1' ],
issuanceDate: '2019-07-12T16:51:22.000Z',
proof: {
type: 'JwtProof2020',
jwt: 'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImRlZ3JlZSI6eyJ0eXBlIjoiQmFjaGVsb3JEZWdyZWUiLCJuYW1lIjoiQmFjY2FsYXVyw6lhdCBlbiBtdXNpcXVlcyBudW3DqXJpcXVlcyJ9fX0sInN1YiI6ImRpZDp3ZWI6c2tvdW5pcy5naXRodWIuaW8iLCJuYmYiOjE1NjI5NTAyODIsImlzcyI6ImRpZDp3ZWI6c2tvdW5pcy5naXRodWIuaW8ifQ.HdDBT9mpvCnV-vCLEygF8s1X9cGoT-nZn0ac87HiOo0WLUOPy7l5ezPTKEjf7UT7B3GokPjWAgXAEoB2DDkrVw'
}
}
}
//// Verified Presentation:
{
verified: true,
payload: {
vp: {
'@context': [Array],
type: [Array],
verifiableCredential: [Array],
foo: 'bar'
},
iss: 'did:web:skounis.github.io'
},
didResolutionResult: {
didDocument: {
'@context': [Array],
id: 'did:web:skounis.github.io',
verificationMethod: [Array],
authentication: [Array],
assertionMethod: [Array]
},
didDocumentMetadata: {},
didResolutionMetadata: { contentType: 'application/did+ld+json' }
},
issuer: 'did:web:skounis.github.io',
signer: {
id: 'did:web:skounis.github.io#owner',
type: 'JsonWebKey2020',
controller: 'did:web:skounis.github.io',
publicKeyJwk: {
kty: 'EC',
crv: 'secp256k1',
x: 'DtZGHvyuNAQsx14a95qETt93DCCmj4D1+cZI/tBq5/Q=',
y: 'eJiyCw5tXteQKYpaAqz3BdUTRQWqnEqrYPjTxMNehJw='
}
},
jwt: 'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0owZVhBaU9pSktWMVFpZlEuZXlKMll5STZleUpBWTI5dWRHVjRkQ0k2V3lKb2RIUndjem92TDNkM2R5NTNNeTV2Y21jdk1qQXhPQzlqY21Wa1pXNTBhV0ZzY3k5Mk1TSmRMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZEY21Wa1pXNTBhV0ZzSWwwc0ltTnlaV1JsYm5ScFlXeFRkV0pxWldOMElqcDdJbVJsWjNKbFpTSTZleUowZVhCbElqb2lRbUZqYUdWc2IzSkVaV2R5WldVaUxDSnVZVzFsSWpvaVFtRmpZMkZzWVhWeXc2bGhkQ0JsYmlCdGRYTnBjWFZsY3lCdWRXM0RxWEpwY1hWbGN5SjlmWDBzSW5OMVlpSTZJbVJwWkRwM1pXSTZjMnR2ZFc1cGN5NW5hWFJvZFdJdWFXOGlMQ0p1WW1ZaU9qRTFOakk1TlRBeU9ESXNJbWx6Y3lJNkltUnBaRHAzWldJNmMydHZkVzVwY3k1bmFYUm9kV0l1YVc4aWZRLkhkREJUOW1wdkNuVi12Q0xFeWdGOHMxWDljR29ULW5abjBhYzg3SGlPbzBXTFVPUHk3bDVlelBUS0VqZjdVVDdCM0dva1BqV0FnWEFFb0IyRERrclZ3Il0sImZvbyI6ImJhciJ9LCJpc3MiOiJkaWQ6d2ViOnNrb3VuaXMuZ2l0aHViLmlvIn0.gZJZoR0TTgeAeE_YNYzpGA9tJOg0iLRFdU3uqRPSNjgXLwBYBacbzXSHWLewHExgx0ZiltmHV3dQXmzbUYzpsg',
policies: { nbf: undefined, exp: undefined, iat: undefined },
verifiablePresentation: {
vp: { foo: 'bar' },
verifiableCredential: [ [Object] ],
holder: 'did:web:skounis.github.io',
type: [ 'VerifiablePresentation' ],
'@context': [ 'https://www.w3.org/2018/credentials/v1' ],
proof: {
type: 'JwtProof2020',
jwt: 'eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0owZVhBaU9pSktWMVFpZlEuZXlKMll5STZleUpBWTI5dWRHVjRkQ0k2V3lKb2RIUndjem92TDNkM2R5NTNNeTV2Y21jdk1qQXhPQzlqY21Wa1pXNTBhV0ZzY3k5Mk1TSmRMQ0owZVhCbElqcGJJbFpsY21sbWFXRmliR1ZEY21Wa1pXNTBhV0ZzSWwwc0ltTnlaV1JsYm5ScFlXeFRkV0pxWldOMElqcDdJbVJsWjNKbFpTSTZleUowZVhCbElqb2lRbUZqYUdWc2IzSkVaV2R5WldVaUxDSnVZVzFsSWpvaVFtRmpZMkZzWVhWeXc2bGhkQ0JsYmlCdGRYTnBjWFZsY3lCdWRXM0RxWEpwY1hWbGN5SjlmWDBzSW5OMVlpSTZJbVJwWkRwM1pXSTZjMnR2ZFc1cGN5NW5hWFJvZFdJdWFXOGlMQ0p1WW1ZaU9qRTFOakk1TlRBeU9ESXNJbWx6Y3lJNkltUnBaRHAzWldJNmMydHZkVzVwY3k1bmFYUm9kV0l1YVc4aWZRLkhkREJUOW1wdkNuVi12Q0xFeWdGOHMxWDljR29ULW5abjBhYzg3SGlPbzBXTFVPUHk3bDVlelBUS0VqZjdVVDdCM0dva1BqV0FnWEFFb0IyRERrclZ3Il0sImZvbyI6ImJhciJ9LCJpc3MiOiJkaWQ6d2ViOnNrb3VuaXMuZ2l0aHViLmlvIn0.gZJZoR0TTgeAeE_YNYzpGA9tJOg0iLRFdU3uqRPSNjgXLwBYBacbzXSHWLewHExgx0ZiltmHV3dQXmzbUYzpsg'
}
}
}