From 5b019d84ba55b05743d4feb70afdd6476c037423 Mon Sep 17 00:00:00 2001 From: SondreB Date: Wed, 30 Nov 2022 16:21:54 +0100 Subject: [PATCH] Add ability to create and sign basic VCs --- examples/did-configuration.json | 2 +- examples/did-configuration2.json | 2 +- examples/vc.json | 30 +++++++++++++++++++ src/__tests__/examples.test.ts | 50 ++++++++++++++++++++++++++++++++ src/__tests__/vc.test.ts.ts | 50 ++++++++++++++++++++++++++++++++ src/identity.ts | 40 +++++++++++++++++++------ 6 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 examples/vc.json create mode 100644 src/__tests__/vc.test.ts.ts diff --git a/examples/did-configuration.json b/examples/did-configuration.json index 27c05c9..fb798c6 100644 --- a/examples/did-configuration.json +++ b/examples/did-configuration.json @@ -1,6 +1,6 @@ { "@context": "https://identity.foundation/.well-known/did-configuration/v1", "linked_dids": [ - "eyJraWQiOiJkaWQ6aXM6ZGQ2ZjRjZjNiMThhOGM2NmJmMzJiMzVhZGUxNWQxMDkyYTM2NmZjNzFlZDhkODEzOTVlOTI2OGVhOGMzNWZjNSNrZXkwIiwiYWxnIjoiRVMyNTZLIiwidHlwIjoiSldUIn0.eyJpYXQiOjE2NjkxNDg3MzUsImV4cCI6MjE0NzQ4MzY0NywidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL2lkZW50aXR5LmZvdW5kYXRpb24vLndlbGwta25vd24vZGlkLWNvbmZpZ3VyYXRpb24vdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmlzOmRkNmY0Y2YzYjE4YThjNjZiZjMyYjM1YWRlMTVkMTA5MmEzNjZmYzcxZWQ4ZDgxMzk1ZTkyNjhlYThjMzVmYzUiLCJvcmlnaW4iOiJodHRzOi8vbG9jYWxob3N0OjQyNTAifX0sIm5iZiI6MTY2OTE0ODczNSwic3ViIjoiZGlkOmlzOmRkNmY0Y2YzYjE4YThjNjZiZjMyYjM1YWRlMTVkMTA5MmEzNjZmYzcxZWQ4ZDgxMzk1ZTkyNjhlYThjMzVmYzUiLCJpc3MiOiJkaWQ6aXM6ZGQ2ZjRjZjNiMThhOGM2NmJmMzJiMzVhZGUxNWQxMDkyYTM2NmZjNzFlZDhkODEzOTVlOTI2OGVhOGMzNWZjNSJ9.tCCmgPcapltHD-J_T2I0kBujvHQ2du_0dMS1m2GATTz2dVa8zg7SbL6WrrIaaXtgYzF71bA7ZIDU-jfY7zOceA" + "eyJraWQiOiJkaWQ6aXM6ZGQ2ZjRjZjNiMThhOGM2NmJmMzJiMzVhZGUxNWQxMDkyYTM2NmZjNzFlZDhkODEzOTVlOTI2OGVhOGMzNWZjNSNrZXkwIiwiYWxnIjoiRVMyNTZLIiwidHlwIjoiSldUIn0.eyJpYXQiOjE2Njk4MjE2NjMsImV4cCI6MjE0NzQ4MzY0NywidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL2lkZW50aXR5LmZvdW5kYXRpb24vLndlbGwta25vd24vZGlkLWNvbmZpZ3VyYXRpb24vdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmlzOmRkNmY0Y2YzYjE4YThjNjZiZjMyYjM1YWRlMTVkMTA5MmEzNjZmYzcxZWQ4ZDgxMzk1ZTkyNjhlYThjMzVmYzUiLCJvcmlnaW4iOiJodHRzOi8vbG9jYWxob3N0OjQyNTAifX0sIm5iZiI6MTY2OTgyMTY2Mywic3ViIjoiZGlkOmlzOmRkNmY0Y2YzYjE4YThjNjZiZjMyYjM1YWRlMTVkMTA5MmEzNjZmYzcxZWQ4ZDgxMzk1ZTkyNjhlYThjMzVmYzUiLCJpc3MiOiJkaWQ6aXM6ZGQ2ZjRjZjNiMThhOGM2NmJmMzJiMzVhZGUxNWQxMDkyYTM2NmZjNzFlZDhkODEzOTVlOTI2OGVhOGMzNWZjNSJ9.YHFiAQw6qP2UFnjTWu-JI0jgSn0jN4y_GlJy3Um4tZJ1vwlH0FftS7xGACOOomaUQvMjf-Myht_cbqjda72_mw" ] } \ No newline at end of file diff --git a/examples/did-configuration2.json b/examples/did-configuration2.json index 043a546..844f137 100644 --- a/examples/did-configuration2.json +++ b/examples/did-configuration2.json @@ -1,6 +1,6 @@ { "@context": "https://identity.foundation/.well-known/did-configuration/v1", "linked_dids": [ - "eyJraWQiOiJkaWQ6aXM6MGYyNTRlNTVhMjYzM2Q0NjhlOTJhYTdkZDVhNzZjMGM5MTAxZmFiOGUyODJjOGMyMGIzZmVmZGUwZDY4ZjIxNyNrZXkwIiwiYWxnIjoiRVMyNTZLIiwidHlwIjoiSldUIn0.eyJpYXQiOjE2NjkxNDg3MzUsImV4cCI6MjE0NzQ4MzY0NywidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL2lkZW50aXR5LmZvdW5kYXRpb24vLndlbGwta25vd24vZGlkLWNvbmZpZ3VyYXRpb24vdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmlzOjBmMjU0ZTU1YTI2MzNkNDY4ZTkyYWE3ZGQ1YTc2YzBjOTEwMWZhYjhlMjgyYzhjMjBiM2ZlZmRlMGQ2OGYyMTciLCJvcmlnaW4iOiJodHRzOi8vbG9jYWxob3N0OjQyNTEifX0sIm5iZiI6MTY2OTE0ODczNSwic3ViIjoiZGlkOmlzOjBmMjU0ZTU1YTI2MzNkNDY4ZTkyYWE3ZGQ1YTc2YzBjOTEwMWZhYjhlMjgyYzhjMjBiM2ZlZmRlMGQ2OGYyMTciLCJpc3MiOiJkaWQ6aXM6MGYyNTRlNTVhMjYzM2Q0NjhlOTJhYTdkZDVhNzZjMGM5MTAxZmFiOGUyODJjOGMyMGIzZmVmZGUwZDY4ZjIxNyJ9.tLBGGai_IRHN2QF0mzpc-odsJo-jpO_wKi8rpwbAaSI5SvscLis35J8CAvCegQ00Ir58iu1PwpQBXU-8JZXnaA" + "eyJraWQiOiJkaWQ6aXM6MGYyNTRlNTVhMjYzM2Q0NjhlOTJhYTdkZDVhNzZjMGM5MTAxZmFiOGUyODJjOGMyMGIzZmVmZGUwZDY4ZjIxNyNrZXkwIiwiYWxnIjoiRVMyNTZLIiwidHlwIjoiSldUIn0.eyJpYXQiOjE2Njk4MjE2NjMsImV4cCI6MjE0NzQ4MzY0NywidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL2lkZW50aXR5LmZvdW5kYXRpb24vLndlbGwta25vd24vZGlkLWNvbmZpZ3VyYXRpb24vdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkRvbWFpbkxpbmthZ2VDcmVkZW50aWFsIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmlzOjBmMjU0ZTU1YTI2MzNkNDY4ZTkyYWE3ZGQ1YTc2YzBjOTEwMWZhYjhlMjgyYzhjMjBiM2ZlZmRlMGQ2OGYyMTciLCJvcmlnaW4iOiJodHRzOi8vbG9jYWxob3N0OjQyNTEifX0sIm5iZiI6MTY2OTgyMTY2Mywic3ViIjoiZGlkOmlzOjBmMjU0ZTU1YTI2MzNkNDY4ZTkyYWE3ZGQ1YTc2YzBjOTEwMWZhYjhlMjgyYzhjMjBiM2ZlZmRlMGQ2OGYyMTciLCJpc3MiOiJkaWQ6aXM6MGYyNTRlNTVhMjYzM2Q0NjhlOTJhYTdkZDVhNzZjMGM5MTAxZmFiOGUyODJjOGMyMGIzZmVmZGUwZDY4ZjIxNyJ9.6JcejON57gO7xqlk4xIVlT76kF9sjcdJuq1fS9bv6l7spUtIj8IYPOFhnCM269ch8fgOvLehHHrfR9L7ARosZQ" ] } \ No newline at end of file diff --git a/examples/vc.json b/examples/vc.json new file mode 100644 index 0000000..3c36070 --- /dev/null +++ b/examples/vc.json @@ -0,0 +1,30 @@ +{ + "header": { + "kid": "did:is:0f254e55a2633d468e92aa7dd5a76c0c9101fab8e282c8c20b3fefde0d68f217#key0", + "alg": "ES256K", + "typ": "JWT" + }, + "payload": { + "iat": 1669821664, + "exp": 2147483647, + "vc": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": [ + "VerifiableCredential", + "EmailVerification" + ], + "credentialSubject": { + "id": "did:is:0f254e55a2633d468e92aa7dd5a76c0c9101fab8e282c8c20b3fefde0d68f217", + "sameAs": "mail@mail.com" + } + }, + "nbf": 1669821664, + "sub": "did:is:0f254e55a2633d468e92aa7dd5a76c0c9101fab8e282c8c20b3fefde0d68f217", + "jti": "123", + "iss": "did:is:0f254e55a2633d468e92aa7dd5a76c0c9101fab8e282c8c20b3fefde0d68f217" + }, + "signature": "_BXoFYsrROeh7N3T4HbTWO8Io7nQrPQFUKNQTQ9BODIpmfYNT4gna4XMqSr5Ex7KZFod9xJodoRHE91Te6OBOA", + "data": "eyJraWQiOiJkaWQ6aXM6MGYyNTRlNTVhMjYzM2Q0NjhlOTJhYTdkZDVhNzZjMGM5MTAxZmFiOGUyODJjOGMyMGIzZmVmZGUwZDY4ZjIxNyNrZXkwIiwiYWxnIjoiRVMyNTZLIiwidHlwIjoiSldUIn0.eyJpYXQiOjE2Njk4MjE2NjQsImV4cCI6MjE0NzQ4MzY0NywidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkVtYWlsVmVyaWZpY2F0aW9uIl0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmlzOjBmMjU0ZTU1YTI2MzNkNDY4ZTkyYWE3ZGQ1YTc2YzBjOTEwMWZhYjhlMjgyYzhjMjBiM2ZlZmRlMGQ2OGYyMTciLCJzYW1lQXMiOiJtYWlsQG1haWwuY29tIn19LCJuYmYiOjE2Njk4MjE2NjQsInN1YiI6ImRpZDppczowZjI1NGU1NWEyNjMzZDQ2OGU5MmFhN2RkNWE3NmMwYzkxMDFmYWI4ZTI4MmM4YzIwYjNmZWZkZTBkNjhmMjE3IiwianRpIjoiMTIzIiwiaXNzIjoiZGlkOmlzOjBmMjU0ZTU1YTI2MzNkNDY4ZTkyYWE3ZGQ1YTc2YzBjOTEwMWZhYjhlMjgyYzhjMjBiM2ZlZmRlMGQ2OGYyMTcifQ" +} \ No newline at end of file diff --git a/src/__tests__/examples.test.ts b/src/__tests__/examples.test.ts index 2a91972..da3aaef 100644 --- a/src/__tests__/examples.test.ts +++ b/src/__tests__/examples.test.ts @@ -175,3 +175,53 @@ test('Generate Examples', async () => { save('did-document-operation-replace-6.txt', replacement); save('did-document-operation-replace-6.json', JSON.stringify(decodeJWT(replacement), null, 2)); }); + +test('Generate Verifiable Credential', async () => { + const tool = new BlockcoreIdentityTools(); + const privateKey = Uint8Array.from([ + 224, 238, 59, 150, 73, 84, 228, 234, 104, 62, 83, 160, 122, 31, 108, 129, 74, 29, 104, 195, 192, 81, 158, 11, 167, + 100, 217, 121, 110, 12, 178, 14, + ]); + + const signer = tool.getSigner(privateKey); + const publicKey = tool.getPublicKeyFromPrivateKey(privateKey); + const verificationMethod = tool.getVerificationMethod(publicKey); + const identity = new BlockcoreIdentity(verificationMethod); + + const kid = `${verificationMethod.controller}${verificationMethod.id}`; + const issuer = tool.getIssuer(identity.did, privateKey); + + const vc = await identity.verifiableCredential( + { id: 'did:is:0f254e55a2633d468e92aa7dd5a76c0c9101fab8e282c8c20b3fefde0d68f217', sameAs: 'mail@mail.com' }, + issuer, + kid, + '123', + 'EmailVerification', + ); + + console.log(vc); + + expect(vc != null).toBeTruthy(); + + save('vc.json', JSON.stringify(decodeJWT(vc), null, 2)); + + // const didDocument = identity.document({ + // service: [ + // { + // id: '#blockexplorer', + // type: 'BlockExplorer', + // serviceEndpoint: 'https://explorer.blockcore.net', + // }, + // ], + // }); + + // expect(didDocument != null).toBeTruthy(); + + // // The default pattern for key identifier is #key{keyIndex}. + // const kid = didDocument.id + '#key0'; + // const jws = await identity.sign( + // signer, + // { version: 0, iat: tool.getTimestampInSeconds(), didDocument: didDocument }, + // kid, + // ); +}); diff --git a/src/__tests__/vc.test.ts.ts b/src/__tests__/vc.test.ts.ts new file mode 100644 index 0000000..d59d846 --- /dev/null +++ b/src/__tests__/vc.test.ts.ts @@ -0,0 +1,50 @@ +import { OperationCanceledException } from 'typescript'; +import { BlockcoreIdentityTools, BlockcoreIdentity } from '../index'; + +test('Create Verifiable Credentials', async () => { + const tool = new BlockcoreIdentityTools(); + const privateKey = Uint8Array.from([ + 224, 238, 59, 150, 73, 84, 228, 234, 104, 62, 83, 160, 122, 31, 108, 129, 74, 29, 104, 195, 192, 81, 158, 11, 167, + 100, 217, 121, 110, 12, 178, 14, + ]); + + const signer = tool.getSigner(privateKey); + const publicKey = tool.getPublicKeyFromPrivateKey(privateKey); + const verificationMethod = tool.getVerificationMethod(publicKey); + const identity = new BlockcoreIdentity(verificationMethod); + + const kid = `${verificationMethod.controller}${verificationMethod.id}`; + const issuer = tool.getIssuer(identity.did, privateKey); + + const vc = await identity.verifiableCredential( + { id: 'did:is:0f254e55a2633d468e92aa7dd5a76c0c9101fab8e282c8c20b3fefde0d68f217', sameAs: 'mail@mail.com' }, + issuer, + kid, + '123', + 'EmailVerification', + ); + + console.log(vc); + + expect(vc != null).toBeTruthy(); + + // const didDocument = identity.document({ + // service: [ + // { + // id: '#blockexplorer', + // type: 'BlockExplorer', + // serviceEndpoint: 'https://explorer.blockcore.net', + // }, + // ], + // }); + + // expect(didDocument != null).toBeTruthy(); + + // // The default pattern for key identifier is #key{keyIndex}. + // const kid = didDocument.id + '#key0'; + // const jws = await identity.sign( + // signer, + // { version: 0, iat: tool.getTimestampInSeconds(), didDocument: didDocument }, + // kid, + // ); +}); diff --git a/src/identity.ts b/src/identity.ts index 3a358db..901d2de 100644 --- a/src/identity.ts +++ b/src/identity.ts @@ -57,6 +57,28 @@ export class BlockcoreIdentity { /** Generates a well known configuration for DID resolver host. */ public async configurationVerifiableCredential(domain: string, issuer: any, kid: string) { + return this.verifiableCredential( + { + id: this.did, + origin: domain, + }, + issuer, + kid, + null, + 'DomainLinkageCredential', + 'https://identity.foundation/.well-known/did-configuration/v1', + ); + } + + /** Generates a well known configuration for DID resolver host. */ + public async verifiableCredential( + claim: any, + issuer: any, + kid: string, + id: string | undefined | null, + type: string, + context: string | undefined | null = undefined, + ) { const date = new Date(); const expiredate = new Date(new Date().setFullYear(date.getFullYear() + 100)); let expiredateNumber = Math.floor(expiredate.getTime() / 1000); @@ -76,21 +98,21 @@ export class BlockcoreIdentity { nbf: currentDateNumber, sub: this.did, vc: { - '@context': [ - 'https://www.w3.org/2018/credentials/v1', - 'https://identity.foundation/.well-known/did-configuration/v1', - ], - type: ['VerifiableCredential', 'DomainLinkageCredential'], - credentialSubject: { - id: this.did, - origin: domain, - }, + '@context': context + ? ['https://www.w3.org/2018/credentials/v1', context] + : ['https://www.w3.org/2018/credentials/v1'], + type: ['VerifiableCredential', type], + credentialSubject: claim, //"expirationDate": expiredate.toISOString(), //"issuanceDate": date.toISOString(), //"issuer": this.id, }, }; + if (id) { + vcPayload.jti = id; + } + const vcJwt = await createVerifiableCredentialJwt(vcPayload, issuer, { header: { kid: kid } }); return vcJwt;